diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..a319913a8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,64 @@ +name: 🐞 Bug Report +description: File a new bug report +title: 'Bug: ' +labels: [Bug] +body: + - type: markdown + attributes: + value: ':stop_sign: _For support questions, please visit the [Discord](https://discord.gg/mRPZuXx3At), [Telegram](https://t.me/stackwallet) or [Reddit](https://www.reddit.com/r/stackwallet/) instead._' + - type: checkboxes + attributes: + label: 'Is there an existing issue for this?' + description: 'Please [search :mag: the issues](https://github.com/cypherstack/stack_wallet/issues) to check if this bug has already been reported.' + options: + - label: 'I have searched the existing issues' + required: true + - type: textarea + attributes: + label: 'Current Behavior' + description: 'Describe the problem you are experiencing. **Please do not paste your logs here.** Screenshots are welcome.' + validations: + required: true + - type: textarea + attributes: + label: 'Expected Behavior' + description: 'Describe what you expect to happen instead.' + validations: + required: true + - type: textarea + attributes: + label: 'Reproduce Steps' + description: | + Please provide a the _smallest, complete steps_ that Stack Wallet's maintainers can run to reproduce the issue ([read more about what this entails](https://stackoverflow.com/help/minimal-reproducible-example)). Failing this, any sort of reproduction steps are better than nothing! + validations: + required: true + - type: textarea + attributes: + label: 'Environment' + description: 'Please provide the following information about your environment.' + value: | + - Operating system and version: + - Device platform and version: + - Real device or emulator/simulator: + validations: + required: true + - type: input + attributes: + label: 'Logs' + description: | + Create a [Gist](https://gist.github.com) which contains your _full_ Stack Wallet logs and link it here. + + :warning: _Remember to redact or remove any sensitive information!_ + placeholder: 'https://gist.github.com/...' + validations: + required: false + - type: textarea + attributes: + label: 'Further Information' + description: | + Links? References? Anything that will give us more context about the issue you are encountering! + validations: + required: false + - type: markdown + attributes: + value: ':stop_sign: _For support questions, please visit the [Discord](https://discord.gg/mRPZuXx3At), [Telegram](https://t.me/stackwallet) or [Reddit](https://www.reddit.com/r/stackwallet/) instead._' \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..c2c87271b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,37 @@ +name: Feature request +description: Suggest an idea for this project +title: 'FR: <title>' +labels: [Feature Request] +body: + - type: textarea + attributes: + label: 'Is Problem' + description: 'Is your feature request related to a problem? Please describe.' + value: | + A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + validations: + required: false + - type: textarea + attributes: + label: 'Solution' + description: 'Describe the solution you'd like.' + value: | + A clear and concise description of what you want to happen. + validations: + required: true + - type: textarea + attributes: + label: 'Alternatives' + description: 'Describe alternatives you've considered.' + value: | + A clear and concise description of any alternative solutions or features you've considered. + validations: + required: true + - type: textarea + attributes: + label: 'Context' + description: 'Additional context.' + value: | + Add any other context or screenshots about the feature request here. + validations: + required: false diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 4638e84dd..3704f1488 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -8,10 +8,13 @@ jobs: - name: Prepare repository uses: actions/checkout@v3 with: - flutter-version: '3.3.4' + flutter-version: '3.7.10' + channel: 'stable' + - name: Install Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.7.10' channel: 'stable' - - name: Install Flutter - uses: subosito/flutter-action@v2 - name: Setup | Rust uses: ATiltedTree/setup-rust@v1 with: @@ -23,6 +26,7 @@ jobs: run: | cargo install cargo-ndk rustup target add x86_64-unknown-linux-gnu + sudo apt clean sudo apt update sudo apt install -y unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake openjdk-8-jre-headless libgit2-dev clang libncurses5-dev libncursesw5-dev zlib1g-dev llvm sudo apt install -y debhelper libclang-dev cargo rustc opencl-headers libssl-dev ocl-icd-opencl-dev diff --git a/.gitignore b/.gitignore index a36824135..ed66fa960 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,9 @@ test/services/coins/particl/particl_wallet_test_parameters.dart coverage scripts/**/build /lib/external_api_keys.dart + +libcw_monero.dll +libcw_wownero.dll +libepic_cash_wallet.dll +libmobileliblelantus.dll +/libisar.so diff --git a/README.md b/README.md index 9d0f6ff6d..4c18c3181 100644 --- a/README.md +++ b/README.md @@ -3,12 +3,25 @@ # Stack Wallet Stack Wallet is a fully open source cryptocurrency wallet. With an easy to use user interface and quick and speedy transactions, this wallet is ideal for anyone no matter how much they know about the cryptocurrency space. The app is actively maintained to provide new user friendly features. -[![Playstore](https://bluewallet.io/img/play-store-badge.svg)](https://play.google.com/store/apps/details?id=com.cypherstack.stackwallet) +<a href="https://play.google.com/store/apps/details?id=com.cypherstack.stackwallet"> +<img width="250px" src="https://play.google.com/intl/en_us/badges/static/images/badges/en_badge_web_generic.png"></img> +</a> ## Feature List Highlights include: -- 5 Different cryptocurrencies +- 11 Different cryptocurrencies: + - [Bitcoin](https://bitcoin.org/en/) + - [Bitcoin Cash](https://bch.info/en/) + - [Dogecoin](https://dogecoin.com/) + - [Epic Cash](https://linktr.ee/epiccash) + - [Ethereum](https://ethereum.org/en/) + - [Firo](https://firo.org/) + - [Litecoin](https://litecoin.org/) + - [Monero](https://www.getmonero.org/) + - [Namecoin](https://www.namecoin.org/) + - [Particl](https://particl.io/) + - [Wownero](https://wownero.org/) - All private keys and seeds stay on device and are never shared. - Easy backup and restore feature to save all the information that's important to you. - Trading cryptocurrencies through our partners. @@ -16,108 +29,10 @@ Highlights include: - Favorite wallets with fast syncing - Custom Nodes. - Open source software. +- No ads. + +> You can find the roadmap [here](docs/roadmap.md). ## Building -### Prerequisites -- The only OS supported for building is Ubuntu 20.04 -- A machine with at least 100 GB of Storage -The following prerequisities can be installed with the setup script `scripts/setup.sh` or manually as described below: - -- Flutter 3.3.4 [(install manually or with git, do not install with snap)](https://docs.flutter.dev/get-started/install) -- Dart SDK Requirement (>=2.17.0, up until <3.0.0) -- Android setup ([Android Studio](https://developer.android.com/studio) and subsequent dependencies) - -### Scripted setup -[`scripts/setup.sh`](https://github.com/cypherstack/stack_wallet/blob/main/scripts/setup.sh) is provided as a tool to set up a stock Ubuntu 20.04 installation for building: download the script and run it anywhere. This script should skip the entire [Manual setup](#manual-setup) section below and prepare you for [running](#running). It will set up the stack_wallet repository in `~/projects/stack_wallet` and build it there. - -### Manual setup -After installing the prerequisites listed above, download the code and init the submodules -``` -git clone https://github.com/cypherstack/stack_wallet.git -cd stack_wallet -git submodule update --init --recursive -``` - -Install all dependencies listed in each of the plugins in the crypto_plugins folder (eg. [flutter_libmonero](https://github.com/cypherstack/flutter_libmonero/blob/main/howto-build-android.md), [flutter_libepiccash](https://github.com/cypherstack/flutter_libepiccash) ) as of Oct 3rd 2022 that is: -``` -sudo apt-get install unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake openjdk-8-jre-headless libgit2-dev clang libncurses5-dev libncursesw5-dev zlib1g-dev llvm sudo apt-get install debhelper libclang-dev cargo rustc opencl-headers libssl-dev ocl-icd-opencl-dev -``` - -Install [Rust](https://www.rust-lang.org/tools/install) -``` -cargo install cargo-ndk -rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android - -sudo apt install libc6-dev-i386 -sudo apt install build-essential cmake git libgit2-dev clang libncurses5-dev libncursesw5-dev zlib1g-dev pkg-config llvm -sudo apt install build-essential debhelper cmake libclang-dev libncurses5-dev clang libncursesw5-dev cargo rustc opencl-headers libssl-dev pkg-config ocl-icd-opencl-dev -sudo apt install unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake openjdk-8-jre-headless -``` - -Run prebuild script - -``` -cd scripts -./prebuild.sh -// when finished go back to the root directory -cd .. -``` - -Remove pre-installed system libraries for the following packages built by cryptography plugins in the crypto_plugins folder: `boost iconv libjson-dev libsecret openssl sodium unbound zmq`. You can use -``` -sudo apt list --installed | grep boost -``` -for example to find which pre-installed packages you may need to remove with `sudo apt remove`. Be careful, as some packages (especially boost) are linked to GNOME (GUI) packages: when in doubt, remove `-dev` packages first like with -``` -sudo apt-get remove '^libboost.*-dev.*' -``` -<!-- TODO: configure compiler to prefer built over system libraries --> - -Building plugins for Android -``` -cd scripts/android/ -./build_all.sh -// when finished go back to the root directory -cd ../.. -``` - -Building plugins for Linux - -``` -cd scripts/linux/ -./build_all.sh -// when finished go back to the root directory -cd ../.. -``` - -## Running -### Android -Plug in your android device or use the emulator available via Android Studio and then run the following commands: -``` -flutter pub get -flutter run android -``` - -Note on Emulators: Only x86_64 emulators are supported, x86 emulators will not work - -### Linux -Plug in your android device or use the emulator available via Android Studio and then run the following commands: -``` -flutter pub get Linux -flutter run linux -``` - -## Android Studio -Android Studio is the recommended IDE for development, not just for launching on Android devices and emulators but also for Linux desktop development. Install it and configure it as follows: -``` -# setup android studio -sudo apt install -y openjdk-11-jdk -sudo snap install android-studio --classic -``` - -Use Tools > SDK Manager to install the SDK Tools > Android SDK (API 30), SDK Tools > NDK, SDK Tools > Android SDK command line tools, and SDK Tools > CMake - -Then install the Flutter plugin and restart the IDE. In Android Studio's options for the Flutter language, enable auto format on save to match the project's code style. If you have problems with the Dart SDK, make sure to run `flutter` in a terminal to download it (use `source ~/.bashrc` to update your environment variables if you're still using the same terminal from which you ran `setup.sh`) - -Make a Pixel 4 (API 30) x86_64 emulator with 2GB of storage space for emulation +You can look at the [build instructions](docs/building.md) for more details. diff --git a/assets/default_themes/dark.zip b/assets/default_themes/dark.zip new file mode 100644 index 000000000..e2291ba2b Binary files /dev/null and b/assets/default_themes/dark.zip differ diff --git a/assets/default_themes/light.zip b/assets/default_themes/light.zip new file mode 100644 index 000000000..c9579152d Binary files /dev/null and b/assets/default_themes/light.zip differ diff --git a/assets/images/bitcoin.png b/assets/images/bitcoin.png deleted file mode 100644 index 63408e07e..000000000 Binary files a/assets/images/bitcoin.png and /dev/null differ diff --git a/assets/images/bitcoincash.png b/assets/images/bitcoincash.png deleted file mode 100644 index 18552e02e..000000000 Binary files a/assets/images/bitcoincash.png and /dev/null differ diff --git a/assets/images/doge.png b/assets/images/doge.png deleted file mode 100644 index b08e82a5b..000000000 Binary files a/assets/images/doge.png and /dev/null differ diff --git a/assets/images/epic-cash.png b/assets/images/epic-cash.png deleted file mode 100644 index ad26be290..000000000 Binary files a/assets/images/epic-cash.png and /dev/null differ diff --git a/assets/images/firo.png b/assets/images/firo.png deleted file mode 100644 index 4a679586d..000000000 Binary files a/assets/images/firo.png and /dev/null differ diff --git a/assets/images/litecoin.png b/assets/images/litecoin.png deleted file mode 100644 index 17994bd47..000000000 Binary files a/assets/images/litecoin.png and /dev/null differ diff --git a/assets/images/monero.png b/assets/images/monero.png deleted file mode 100644 index 679e647ea..000000000 Binary files a/assets/images/monero.png and /dev/null differ diff --git a/assets/images/namecoin.png b/assets/images/namecoin.png deleted file mode 100644 index 45cf8abb7..000000000 Binary files a/assets/images/namecoin.png and /dev/null differ diff --git a/assets/images/particl.png b/assets/images/particl.png deleted file mode 100644 index ef5939f47..000000000 Binary files a/assets/images/particl.png and /dev/null differ diff --git a/assets/images/stack.png b/assets/images/stack.png deleted file mode 100644 index b59af1608..000000000 Binary files a/assets/images/stack.png and /dev/null differ diff --git a/assets/images/wownero.png b/assets/images/wownero.png deleted file mode 100644 index 857ab2b4c..000000000 Binary files a/assets/images/wownero.png and /dev/null differ diff --git a/assets/lottie/arrow_rotate.json b/assets/lottie/arrow_rotate.json new file mode 100644 index 000000000..c729d2e7a --- /dev/null +++ b/assets/lottie/arrow_rotate.json @@ -0,0 +1 @@ +{"v":"5.10.2","fr":30,"ip":0,"op":60,"w":30,"h":30,"nm":"arrow-rotate","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"arrow-rotate","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":44,"s":[200]},{"t":60,"s":[360]}],"ix":10},"p":{"a":0,"k":[15,15,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-0.828],[0,0],[3.389,0],[1.441,-4.074],[-0.781,-0.277],[-0.276,0.778],[-3.22,0],[-1.369,-1.823],[0,0],[0,-0.83],[-0.83,0],[0,0],[-0.023,0],[0,0],[0,0.83],[0,0],[0.83,0]],"o":[[0,0],[-1.964,-2.437],[-4.533,0],[-0.276,0.741],[0.781,0.277],[1.031,-2.916],[2.494,0],[0,0],[-0.83,0],[0,0.83],[0,0],[0.023,0],[0,0],[0.83,0],[0,0],[0,-0.828],[-0.83,0]],"v":[[8.25,-8.25],[8.25,-6.497],[-0.042,-10.5],[-9.902,-3.502],[-8.988,-1.584],[-7.073,-2.498],[-0.042,-7.5],[6,-4.5],[4.5,-4.5],[3,-3],[4.5,-1.5],[8.452,-1.5],[8.522,-1.5],[9.75,-1.5],[11.25,-3],[11.25,-8.25],[9.75,-9.75]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[0,0],[-0.828,0],[0,0.83],[0,0],[-3.347,0],[-1.439,4.073],[0.783,0.277],[0.277,-0.778],[3.262,0],[1.411,1.823],[0,0],[0,0.83],[0.83,0],[0,0],[0,-0.83]],"o":[[0,0.83],[0.828,0],[0,0],[1.922,2.438],[4.575,0],[0.277,-0.783],[-0.778,-0.277],[-1.031,2.916],[-2.452,0],[0,0],[0.83,0],[0,-0.83],[0,0],[-0.828,0],[0,0]],"v":[[-11.25,8.25],[-9.75,9.75],[-8.25,8.25],[-8.25,6.497],[0,10.5],[9.9,3.502],[8.986,1.584],[7.073,2.498],[0,7.5],[-6.042,4.5],[-4.5,4.5],[-3,3],[-4.5,1.5],[-9.75,1.5],[-11.25,3]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.137254908681,0.137254908681,0.137254908681,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":4,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"ct":1,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/assets/lottie/icon_send.json b/assets/lottie/icon_send.json new file mode 100644 index 000000000..7dc1f7c2d --- /dev/null +++ b/assets/lottie/icon_send.json @@ -0,0 +1 @@ +{"v":"5.10.2","fr":30,"ip":0,"op":100,"w":24,"h":24,"nm":"icon-send","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"MASK","td":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0.125,0.125,0],"ix":1,"l":2},"s":{"a":0,"k":[87.368,87.368,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[23.75,23.75],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0.125,0.125],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":100,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Arrow","tt":1,"tp":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[-1.009,25.009,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":33,"s":[11.695,12.306,0],"to":[0,0,0],"ti":[0,0,0]},{"t":53,"s":[24.398,-0.397,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,-0.69],[-0.69,0],[0,0],[0,0],[-0.488,-0.488],[-0.488,0.488],[0,0],[0,0],[-0.69,0],[0,0.69],[0,0],[0.234,0.234],[0.332,0]],"o":[[-0.69,0],[0,0.69],[0,0],[0,0],[-0.488,0.488],[0.488,0.488],[0,0],[0,0],[0,0.69],[0.69,0],[0,0],[0,-0.332],[-0.234,-0.234],[0,0]],"v":[[-2,-5.25],[-3.25,-4],[-2,-2.75],[0.982,-2.75],[-4.884,3.116],[-4.884,4.884],[-3.116,4.884],[2.75,-0.982],[2.75,2],[4,3.25],[5.25,2],[5.25,-4],[4.884,-4.884],[4,-5.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.137254908681,0.137254908681,0.137254908681,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector (Stroke)","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":100,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Outline","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[12,0.063,0],"ix":1,"l":2},"s":{"a":0,"k":[90.104,90.104,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.137254901961,0.137254901961,0.137254901961,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.5,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[12,0.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":58,"s":[0]},{"t":97,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":58,"s":[100]},{"t":97,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":100,"st":0,"ct":1,"bm":0}],"markers":[{"tm":0,"cm":"{\r\n\"name\":\"SEGMENT 1\"\r\n}","dr":0},{"tm":53,"cm":"{\r\n\"name\":\"SEGMENT 2\"\r\n}","dr":0},{"tm":97,"cm":"{\r\n\"name\":\"SEGMENT 3\"\r\n}","dr":0}]} \ No newline at end of file diff --git a/assets/lottie/loader_and_checkmark.json b/assets/lottie/loader_and_checkmark.json new file mode 100644 index 000000000..406f52030 --- /dev/null +++ b/assets/lottie/loader_and_checkmark.json @@ -0,0 +1 @@ +{"v":"5.10.2","fr":30,"ip":0,"op":130,"w":24,"h":24,"nm":"Loader","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Arrow","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":115,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":117,"s":[80]},{"t":122,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":117,"s":[30,30,100]},{"t":122,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[4.75,-3],[-1.25,3],[-4.75,-0.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2.5,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":100,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":117,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":119,"s":[30]},{"t":122,"s":[0]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":130,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Fill","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":115,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":117,"s":[30]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":119,"s":[70]},{"t":122,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":117,"s":[241.821,241.821,100]},{"t":122,"s":[1511.821,1511.821,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[24,24],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"fl","c":{"a":0,"k":[0,0.647058823529,0.470588235294,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[6.243,6.243],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":130,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Сircle green","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":73,"s":[0]},{"t":74,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,-0.063,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.754]},"t":0,"s":[113.562,113.562,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":111,"s":[113.562,113.562,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":115,"s":[90,90,100]},{"t":122,"s":[113.562,113.562,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[19.125,19.125],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.647058823529,0.470588235294,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-0.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":74,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":87,"s":[0]},{"t":111,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":74,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":87,"s":[55]},{"t":111,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0,0.647058823529,0.470588235294,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":130,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Сircle black 2 turn","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":36,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":37,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":74,"s":[100]},{"t":75,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,-0.063,0],"ix":1,"l":2},"s":{"a":0,"k":[113.562,113.562,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[19.125,19.125],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.137254901961,0.137254901961,0.137254901961,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-0.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":37,"s":[0.2]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":50,"s":[0.2]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":57,"s":[27.5]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":72,"s":[99]},{"t":74,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":37,"s":[0.1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":50,"s":[55]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":57,"s":[73]},{"t":74,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.137254908681,0.137254908681,0.137254908681,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":130,"st":0,"ct":1,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Сircle black 1 turn","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":37,"s":[100]},{"t":38,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[12,12,0],"ix":2,"l":2},"a":{"a":0,"k":[0,-0.063,0],"ix":1,"l":2},"s":{"a":0,"k":[113.562,113.562,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[19.125,19.125],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.137254901961,0.137254901961,0.137254901961,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,-0.063],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":13,"s":[0.2]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[27.5]},{"t":37,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":13,"s":[55]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[73]},{"t":37,"s":[99]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.137254908681,0.137254908681,0.137254908681,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":0,"op":130,"st":0,"ct":1,"bm":0}],"markers":[{"tm":0,"cm":"{\r\n\"name\":\"marker 1\"\r\n}","dr":0},{"tm":74,"cm":"{\r\n\"name\":\"marker 2\"\r\n}","dr":0},{"tm":130,"cm":"{\r\n\"name\":\"marker 3\"\r\n}","dr":0}]} \ No newline at end of file diff --git a/assets/lottie/test.json b/assets/lottie/test.json deleted file mode 100644 index a8130f1da..000000000 --- a/assets/lottie/test.json +++ /dev/null @@ -1 +0,0 @@ -{"v":"4.8.0","meta":{"g":"LottieFiles AE ","a":"","k":"","d":"","tc":""},"fr":25,"ip":0,"op":75,"w":600,"h":600,"nm":"stack-test-1","ddd":0,"assets":[{"id":"image_0","w":171,"h":171,"u":"","p":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKsAAACrCAYAAAAZ6GwZAAAOSklEQVR4Xu2d309TWxbHd+ttEQaQqxgqcvlZDTRQwAASFCmQKgnWufwFlxcJmZdx/AeGm0xmHubF+zZvM/4F4w1PxgfBGKPEGIYXeZmouZFIUvUSGVFa25tF2juF29Jzzl7rnL3PWU2MD9177X2++9PDOmutvY9P8IcV0EQBnybz5GmyAoJhZQi0UYBh1WapeKIMKzOgjQIMqzZLxRNlWJkBbRRgWLVZKp4ow8oMaKMAw6rNUvFEGVZmQBsFGFZtloonyrAyA9oowLBqs1Q8UYaVGdBGAYZVm6XiiTKszIA2CjCs2iwVT5RhZQa0UYBh1WapeKIMKzOgjQIMqzZLxRNlWJkBbRRgWLVZKp4ow8oMaKMAw6rNUvFEGVZmQBsFGFZtloonyrAyA9oowLBqs1Q8UYaVkIHFxcWYEOJbIUSfEGKsYKhXPp9vVQixlEql7szMzLwknIZrTDOsBEu5uLgIgN4SQrQYNH87nU4vMLSHq8WwGqTJSLObN29eDYVCfwuHw92BQMBIl1/bZLPZLZ/PdyudTt+amZn52VRnjzRmWBEWen5+vlUI8deGhoYrAwMDxyVNvspmswvXrl37l6Qd13VnWCWWdHZ2ti4YDN4QQvzp1KlTaQRQC2ez7Pf7b0xPT4Nvyx8h+DBhqxRcv359Fv5sCyGO1dbWJsfGxuqt2irTD/zZG+waMKym+Zqfn49lMhmAtDffeXJyUlRVVZm2ZbRD3p9NJBILRvu4sR27AQZXFfzSTCYDfmRhCEqEQqHNwcHBBoNmZJu9EkLcSCQSd2QN6difYS2zauCXVlRULGSz2T8Wazo2NiZqa2vtXntP+rMM6yGYzc3NwZ9deIA6VqyZ3+/fmZ6errSb1ILxfsjFZz0R6mJYi5A2NzdnKKhfXV39enx8/LSDsArwZ/1+/8LVq1fBj3b1h2EtWN7cwxPcTff5paUIaGtre9Xd3W00S0UNEvizs4lEYol6IKfsM6xCiNzDE0D6nZmFUAzW/NSX0+n0rBtTt56GtSCo/2czkObbKgprfnqu82c9C2thUN8KqNCnqanpdX9/v6M+62FzB38WHhDdkrr1HKw5vxTipdK+pgoPWAZ/aP/JxWe19mc9A2upoL7BxS7a7OjRo5vxeNyuhIDMVPf6+ny+H1OpFKRutayfdT2sOb8UwjqmHp6MkpFIJIw2Vand9zqWIroa1nJBfQx6HMpgSU9dR3/WlbDmHp4gFCXtl5ajorOz8/2ZM2e+LtdO4e+XhRALOsRnXQWr2aA+BkB1dXU/jY6OfoNhy2Ebym+tcQWsVoP6WHBMTU0Js9tYsMbGtKP61hqtYZUN6mMtdCQS+dTR0XEUy54CdpTcWqMtrBhBfSwoAoHAh6mpqRosewrZUaoUUTtYMYP6mFAMDg5CITamSZVsKbG1RhtYKYL6mDQEg8HtK1euVGPaVMmWCltrlIeVOqiPCURvb+9Oc3Ozk8XYmJdTypZjW2uUhbXg4alkpb4dK2NmDL/f//ny5csVbogMGLhu2/1ZJWG1M6hvYFFMNTl58uTW8PBw0W0wpgzp09i2UkSlYHUiqE/BhAtDWYfKZNfWGiVghYenL1++3PL5fL+ngMcJmy6PDhzmz5JtrXEcVjuKTZyAFXa+jo6OVjqwTduJyz04JsnWGsdgVTVeirnSHgcWpET1Z22HVadQFBa4HglpFZULsxTRVljn5uYgDAWle156Wt5bxObm5mRvby/V4W1YvytKO9Jba2yBtdhhZpSqqGq7srIy2d/fX3/ixAlVp0g9r9uJRGLW6iCksLrxKd+q0IX96uvr9+6ylCcPYswT0QZKAoEEVvBLnz9//o/3799ffPPmTXpnZ6c6lUqdCAQCbysrK7dDodBXra2tp1tb4cBo737C4fBWOBw+5uKMF2pqFh3WqqqqP6TT6b/s7u6W3epRUVGxdf78+WBnZ6fb8+klf5GBQGArEokE3VRTQFXEjQkr3CZ/c36pkfsmQBuPx481NjYaae7KNjU1NZs9PT0NLvBnybbHYMEK73mCAxSknvJDodCb8fHxUE2NG+uYjf3G4HDivr6+Bg1dA/KNhxiwwh0VXtIgBWrhUkaj0c1z5841BINBYyvsslaQTGhvb9/t6upC05RKIsw4ark5YsAKoP56vn65AY1+Dwt28eJF4XV/tq+v75jCOxBsPSxDFlaImf3TKIBW2lVVVW1OTEw0eNmfhbfBDA4OKhPqcuoYIllY4cwk8oMkAPKmpqb/jo6OdnjZn21pafnQ1dVV46A/6+iBxTKwwlHm/7Zyt7Tax+/3/y8SiSQHBgZaPOzPfurp6cnaGeqyq161HBcysMJhZ0XfYFJuUNnvIbkwPDwsurq6PJu3tDF1i1o5JbP2MrBCqMrQ2fsyEzysb21t7at4PN7igtikZYkIU7ckNamWL1TydZjwOhslQivNzc0/TUxMfONh1+Bje3t7Cil1i5oilYHzYF+ZO2sWcyKytsCf7e7u3h4eHtbmcF/Zaz7YXyZ1q8K5AOX0cA2s+QsFfzYWi1W2tbXRvUy1nKoOfw+pW0iqmNhSo8SJK+Vkk4HVtrBVuYso9n1dXd3rycnJ0172Zw2kbslTpFbWrlQfGVgdf8AyIsTZs2dfj4yMnPawP7vT0dHh7+zsrCjQS8lTAsutp+thBQHAnx0YGNiNRCJfexVa8GfD4XAgHA7/Xcf3CcA6ysAKe6ksveys3C+I6nuv1s++fftW3L9/f/Pdu3fw8Al/+iFNrt0bW2RgtT2DhQXx8ePHN0dGRlxfb7C7uyuWlpY2X758WSxC8kNu86Y2b9SWgRVKA19gAeSEndbW1s1YLObKUsTV1dX3T58+PZrJZA7bhQFvIIS/kFq8UVsGVuCLpDzQTnD9fv/HaDSaGhoaUiLBIXvtGxsb4t69e1ufP382cz1726RzBfSyUyDrLwurY/UB2IqAP3vp0qWArvHZDx8+AKTJZDIpczbBjzlolfRnZWHV3hU4CD34s+Pj49rshQK/9NmzZ5tra2uYmbvvc66BUv6sLKyw1lrEW83eiXWIz66vr+88fPhQlPFLzV56vv3eG7Vzm0Ct2kDthwGrtlGBckrC1pqhoSFfNBpV6rVB4JcuLS0lt7e3Zf7kl7v8/PfK+LMYsLriQeuwlVNlqzj4pcvLy8mNjQ07ID0oieP+LBasMSHEfaM/VV3bQe1oPB6vt3trDfilq6urW2tra19lMpnfOagfuAbwUA3/bPdnsWAF/e4IIVxzcvVhQNi5VRz80idPnuyaDEVR8ww1rxCfhUNNbPtgwlqXS+GZie/ZdqHYAx05cuTThQsXslRbxcEvffToUT5Fij19LHt7VVt2xWcxYQUBXPuwVWp1q6urk7FYrB5rq3iZFCkWZNh2buegJY3PYsMKImhX4IKxco2NjcmxsTEpf3ZlZQX80kAmk9GxcJzcn6WAFdYefJnvMCDQyQaUIkaj0TScomKmFPHFixcfHzx4kFLML7UqPZk/SwWrZ4GFCzdaigihqLt376rul1qFFvxZSCpA/QjKhxJWmKBragesqF2qFBH80sePHyfX19ediJdauRSZPuDPArTSoS5qWOEiodAXoPVElKDYqhaWIq6trX1aWVnJEqVIZaCi7Jv3Z+F5xvLHDlhhcpYPGrZ8ZYp1hFLEQCDgFr/UqrqQuoWzfC197II1Pzm4y8Kvy5bD3Cwpwp2oFJDemWA3rCAEJA/Ah9Hm1exUq+cRu2h7vpyANb9G4BqAL+uJFK1HwCy8zL3jMTGzW07Cmr8wKIIBaNFPz/YgICpcMsrDVLELUQHWQn/W01EDFUiTnANamEp1WAv9Wa3OI5BcYDd0R08A6ABroT9r6Z1ablh5ja5h73jMXHko+bRVcgOKXSz4swAth7rIUTA1AHnRik531oNz9eyr300hZE9jW8oBdYY1789CQsGR9xjYw4HSozi+cVB1N6DY6kG6DqIGjr7PQGmscCenzJZsHWHNLwXsSgBo2Z/FhbPQmlKHXegMa15UcA04dYsLrOPbrnX3WQ9bDkjdArSe252Ay6hAT5Fizs8Nd9ZCPSDUBdCyP2uOEi2OvnQbrPkl4lJE47BKl+4ZH0qupVthzYe6wJfl1G1xRmxJkcrhub+3m2HNXymXIu5fc1tTpAyrNQW8XopIVrpnbTnM9/LCnfWgKl5M3ZKW7pnHzloPL8Ka92e9kLq19Swqawga7+VVWAv9WTeWIpKdimIcLfyWXoc1r6ibShGVSpFiIsuw7ldT59StkilShhVTgd/agq3iUCCjS+rW8dI92uX4v3W+s5ZWWvVSRGVK9xhWuxQoP46KqVttUqTl5TXegu+sxrRS5RQZtNNNjF22Wq0YVnPr4VQpotKle+YktN6aYbWmnV2pW+1TpNbkLd6LYZVTkzJ164oUqZy8+3szrPJqgj+LmbrVrnRPXkJjFhhWYzoZaSV7YLK2pXtGxMFow7BiqLjfhtnUrSOnm+BfNr1FhpVOYyOpW8dON6G7bDrLDCudtmC5VOrWMylSTHkZVkw1S9vKp27hf4gg2PqCXnsukX4UhpVeYx4BSQGGFUlINkOvAMNKrzGPgKQAw4okJJuhV4BhpdeYR0BSgGFFEpLN0CvAsNJrzCMgKcCwIgnJZugVYFjpNeYRkBRgWJGEZDP0CjCs9BrzCEgKMKxIQrIZegUYVnqNeQQkBRhWJCHZDL0CDCu9xjwCkgIMK5KQbIZeAYaVXmMeAUkBhhVJSDZDrwDDSq8xj4CkAMOKJCSboVeAYaXXmEdAUoBhRRKSzdArwLDSa8wjICnAsCIJyWboFWBY6TXmEZAUYFiRhGQz9AowrPQa8whICjCsSEKyGXoFGFZ6jXkEJAUYViQh2Qy9AgwrvcY8ApICvwBH7cTZ9uA+jwAAAABJRU5ErkJggg==","e":1}],"layers":[{"ddd":0,"ind":1,"ty":2,"nm":"stack-icon.ai","cl":"ai","refId":"image_0","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[306,304,0],"to":[0,4,0],"ti":[0,4.167,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":26,"s":[306,328,0],"to":[0,-4.167,0],"ti":[0,4,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":50,"s":[306,279,0],"to":[0,-4,0],"ti":[0,-4.167,0]},{"t":75,"s":[306,304,0]}],"ix":2,"x":"var $bm_rt;\n$bm_rt = transform.position;"},"a":{"a":0,"k":[85.5,85.5,0],"ix":1},"s":{"a":0,"k":[269.591,269.591,100],"ix":6}},"ao":0,"ip":0,"op":750,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/assets/svg/arrow-rotate.svg b/assets/svg/arrow-rotate.svg index dba41d408..2ee129b03 100644 --- a/assets/svg/arrow-rotate.svg +++ b/assets/svg/arrow-rotate.svg @@ -1,4 +1,3 @@ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path d="M23.2499 2.25V9C23.2499 9.82922 22.5782 10.5 21.7499 10.5H15.7499C14.9217 10.5 14.2499 9.82922 14.2499 9C14.2499 8.17078 14.9217 7.5 15.7499 7.5H17.8302C16.514 5.64844 14.3671 4.5 11.9999 4.5C8.87806 4.5 6.13588 6.49219 5.17963 9.45938C4.9265 10.2469 4.08181 10.6828 3.2901 10.425C2.50213 10.1716 2.069 9.32484 2.324 8.53687C3.68385 4.32844 7.57025 1.5 11.9999 1.5C15.3449 1.5 18.3796 3.11812 20.2499 5.72297V2.25C20.2499 1.42078 20.9216 0.75 21.7499 0.75C22.5782 0.75 23.2499 1.42078 23.2499 2.25Z" fill="white"/> -<path d="M21.675 15.4594C20.3156 19.6734 16.425 22.5 11.9578 22.5C8.65313 22.5 5.62031 20.8828 3.75 18.2766V21.75C3.75 22.5792 3.07828 23.25 2.25 23.25C1.42172 23.25 0.75 22.5792 0.75 21.75V15C0.75 14.1708 1.42172 13.5 2.25 13.5H8.25C9.07828 13.5 9.75 14.1708 9.75 15C9.75 15.8292 9.07828 16.5 8.25 16.5H6.16875C7.48594 18.3516 9.63281 19.5 12 19.5C15.1209 19.5 17.8641 17.5064 18.8203 14.5406C19.0745 13.7541 19.9163 13.3205 20.7094 13.5755C21.4969 13.7859 21.9328 14.6719 21.675 15.4594Z" fill="white"/> +<path d="M20.25 5.50313V3.75C20.25 2.92172 20.9203 2.25 21.75 2.25C22.5797 2.25 23.25 2.92172 23.25 3.75V9C23.25 9.82969 22.5797 10.5 21.75 10.5H20.5219C20.4984 10.5 20.475 10.5 20.4516 10.5H16.5C15.6703 10.5 15 9.82969 15 9C15 8.17031 15.6703 7.5 16.5 7.5H18C16.6313 5.67656 14.4516 4.5 11.9578 4.5C8.7375 4.5 5.95781 6.58594 4.92656 9.50156C4.65047 10.2797 3.79312 10.6922 3.01219 10.4156C2.23125 10.1391 1.82156 9.23906 2.09766 8.49844C3.53859 4.42406 7.425 1.5 11.9578 1.5C15.3469 1.5 18.2859 3.06656 20.25 5.50313ZM2.25 21.75C1.42172 21.75 0.75 21.0797 0.75 20.25V15C0.75 14.1703 1.42172 13.5 2.25 13.5H7.5C8.32969 13.5 9 14.1703 9 15C9 15.8297 8.32969 16.5 7.5 16.5H5.95781C7.36875 18.3234 9.54844 19.5 12 19.5C15.2625 19.5 18.0422 17.4141 19.0734 14.4984C19.35 13.7203 20.2078 13.3078 20.9859 13.5844C21.7688 13.8609 22.1766 14.7188 21.9 15.5016C20.4609 19.575 16.575 22.5 12 22.5C8.65312 22.5 5.67188 20.9344 3.75 18.4969V20.25C3.75 21.0797 3.07828 21.75 2.25 21.75Z" fill="#232323"/> </svg> diff --git a/assets/svg/buy/Simplex-Nuvei-Logo-light.svg b/assets/svg/buy/Simplex-Nuvei-Logo-light.svg new file mode 100644 index 000000000..367ce0de4 --- /dev/null +++ b/assets/svg/buy/Simplex-Nuvei-Logo-light.svg @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + id="svg2" + width="797.33331" + height="290.66666" + viewBox="0 0 797.33331 290.66666" + sodipodi:docname="Simplex-Nuvei-Logo light.svg" + inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"> + <metadata + id="metadata8"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs6" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="2433" + inkscape:window-height="1039" + id="namedview4" + showgrid="false" + inkscape:zoom="0.87040136" + inkscape:cx="469.71272" + inkscape:cy="275.98726" + inkscape:window-x="72" + inkscape:window-y="0" + inkscape:window-maximized="0" + inkscape:current-layer="svg2" /> + <path + style="fill:#e40046;stroke-width:1.33333325;fill-opacity:1" + d="m 785.0292,226.02914 c -1.799,-2.74566 -1.9298,-4.0247 -0.6895,-6.74669 1.3002,-2.85376 2.2808,-3.29658 6.5224,-2.94537 4.5785,0.37909 5.0273,0.77962 5.4165,4.83377 0.2333,2.43124 -0.2901,5.28124 -1.1633,6.33333 -2.5279,3.04599 -7.6109,2.30263 -10.0861,-1.47504 z M 124.35434,39.498519 c -11.90422,-8.089127 -12.24198,-26.472494 -0.65311,-35.5464665 5.69338,-4.45786203 16.37923,-5.045403 23.02932,-1.266223 7.19287,4.087632 10.18063,8.7102585 10.85048,16.7876965 1.1458,13.816817 -7.41334,23.276906 -21.06009,23.276906 -5.62675,0 -8.51836,-0.772876 -12.1666,-3.251913 z" + id="path12" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccccssssc" /> + <path + style="fill:#fbffff;stroke-width:1.33333325;fill-opacity:1" + d="m 552.36256,283.36248 c 0,-2.60792 0.68044,-3.33334 3.12668,-3.33334 3.75651,0 7.53999,-3.82936 7.53999,-7.63141 0,-1.51513 -3.3,-10.73496 -7.33334,-20.48852 -4.03333,-9.75356 -7.33333,-18.21667 -7.33333,-18.80691 0,-0.59024 1.99911,-1.07316 4.44245,-1.07316 4.40215,0 4.48835,0.1212 9.50144,13.35855 2.78244,7.34719 5.45658,13.60425 5.94252,13.90459 0.48595,0.30033 3.23898,-5.71102 6.11787,-13.35855 5.18256,-13.76708 5.27765,-13.90459 9.61503,-13.90459 2.40937,0 4.38069,0.48226 4.38069,1.07168 0,0.58943 -4.243,11.53943 -9.42888,24.33334 -10.47103,25.83273 -12.96841,29.26165 -21.31213,29.26165 -4.7207,0 -5.25899,-0.34119 -5.25899,-3.33333 z M 517.20904,269.2657 c -2.91224,-2.29077 -3.51315,-2.4048 -3.51315,-0.66666 0,1.44085 -1.25128,2.09677 -3.99999,2.09677 h -4 v -26.66667 -26.66665 h 4 3.99999 v 9.31373 9.31372 l 4.33334,-2.64208 c 5.30393,-3.23385 10.43481,-3.36171 16.6451,-0.41474 12.57463,5.96705 12.7231,30.70017 0.22311,37.16417 -5.45992,2.82344 -13.52375,2.44433 -17.6884,-0.83159 z m 15.18788,-8.90322 c 2.35075,-2.97778 2.77697,-4.90328 2.26861,-10.24858 -1.01185,-10.6394 -9.55112,-14.87302 -17.45649,-8.65465 -3.08255,2.42473 -3.51315,3.72285 -3.51315,10.59107 0,6.57734 0.47332,8.15914 2.96335,9.90322 4.91331,3.44142 12.35915,2.68866 15.73768,-1.59106 z M 664.848,270.46969 c -4.62026,-2.23717 -5.81878,-7.00959 -5.81878,-23.16995 v -15.2706 h 6 6 v 10.57987 c 0,5.81893 0.56563,12.06759 1.25696,13.88591 1.66876,4.38918 6.01392,5.65198 10.14166,2.9474 3.08796,-2.02334 3.26806,-2.83755 3.26806,-14.77724 v -12.63594 h 6 6 v 19.33334 19.33333 h -6 c -4.238,0 -6,-0.54513 -6,-1.85627 0,-1.46638 -1.0394,-1.32636 -4.9486,0.66667 -5.40013,2.75315 -11.44653,3.11955 -15.8993,0.96348 z m 85.1362,-1.09147 c -4.3604,-1.97904 -6.325,-3.94366 -8.3041,-8.30405 -3.335,-7.34811 -3.3109,-12.17291 0.096,-19.21059 4.7684,-9.85005 17.9432,-13.96921 27.9407,-8.73578 5.193,2.7184 10.6459,11.86154 10.6459,17.85053 v 3.71748 h -14 c -7.7,0 -14,0.50701 -14,1.12668 0,1.66849 4.2493,5.09759 7.5688,6.10773 1.5961,0.48574 5.0077,0.004 7.5812,-1.0719 4.5142,-1.88615 4.7563,-1.83742 6.8659,1.38225 2.9285,4.46948 1.9485,6.31099 -4.3652,8.20261 -7.4905,2.24422 -13.4344,1.92818 -20.0291,-1.06496 z m 17.1271,-25.1957 c -2.6669,-4.98314 -11.9101,-4.66606 -13.8834,0.47625 -0.6476,1.68777 0.5909,2.03704 7.2234,2.03704 7.4291,0 7.9083,-0.18084 6.66,-2.51329 z m -149.41541,7.17996 v -19.33334 h 6 c 4.44789,0 6,0.51804 6,2.00259 0,1.66224 0.84641,1.54895 4.98025,-0.66667 2.73915,-1.46809 6.66894,-2.66924 8.73288,-2.66924 4.65463,0 11.02084,3.18158 12.1253,6.05972 0.45553,1.18711 0.82824,9.3093 0.82824,18.04932 v 15.89095 h -6 -6 V 258.2358 c 0,-10.61235 -0.38127,-12.80507 -2.57112,-14.78686 -1.75924,-1.59209 -3.54859,-2.02684 -5.66667,-1.37678 -5.93675,1.82198 -6.42888,3.05601 -6.42888,16.12024 v 12.50341 h -6 -6 z m 95.44801,18.18554 c -2.0625,-3.33726 -12.7813,-32.61501 -12.7813,-34.91145 0,-2.17984 0.9319,-2.60743 5.6828,-2.60743 h 5.6829 l 3.902,11.25659 c 2.146,6.19113 4.319,10.99884 4.8287,10.6838 0.5098,-0.31504 2.6004,-5.38051 4.646,-11.2566 l 3.7191,-10.68379 h 6.4359 c 3.5397,0 6.4323,0.45 6.4279,1 0,0.55 -3.1839,9.1 -7.0654,19 l -7.0573,18 -6.8155,0.39888 c -3.7485,0.21939 -7.1711,-0.17661 -7.6058,-0.88 z m 69.8853,-18.18554 v -19.33334 h 6 6 v 19.33334 19.33333 h -6 -6 z M 372.36257,134.69582 V 52.02916 h 19.33333 19.33333 v 6.13812 6.13813 l 4.70373,-4.12994 c 10.5474,-9.26074 24.4038,-13.08556 38.97368,-10.75801 26.89726,4.29686 42.2916,24.38254 43.90008,57.27836 1.22411,25.03478 -4.63209,42.51699 -18.56269,55.41427 -6.23869,5.77592 -9.6518,7.77412 -17.18981,10.06372 -17.50572,5.3172 -34.61887,1.93937 -46.72588,-9.22285 l -5.76578,-5.31583 0.46191,29.86368 0.46192,29.86368 h -19.46192 -19.4619 z m 68.66666,5.18662 c 3.31185,-0.90984 7.43647,-3.64879 10.88208,-7.22624 13.41396,-13.92722 9.71121,-39.26103 -6.91468,-47.30949 -8.35727,-4.04568 -18.11716,-3.25662 -26.90811,2.17546 l -6.39262,3.95011 -0.37108,19.13174 c -0.40826,21.04839 -0.0876,22.06243 8.3593,26.43046 8.52487,4.40838 13.33094,5.04962 21.34511,2.84796 z M 34.362583,172.72269 c -4.4,-0.96863 -11.3,-3.0638 -15.33334,-4.65593 -7.22766,-2.8531 -19.99999,-10.31535 -19.99999,-11.68503 0,-0.64356 11.79414,-21.42021 14.41491,-25.39343 0.86027,-1.30421 2.99133,-0.58418 9.02236,3.04842 8.29972,4.99909 20.00456,9.1455 30.08306,10.65686 11.51899,1.72739 22.95943,-4.1186 19.75982,-10.09712 -1.57731,-2.94722 -6.00807,-4.33089 -25.28016,-7.89461 C 17.561163,121.25272 5.809853,112.145 3.561193,93.01243 1.649683,76.7484 10.897933,61.37578 26.919603,54.18568 c 15.17038,-6.80806 37.83079,-7.11508 56.92106,-0.7712 9.6907,3.22032 20.878177,10.01112 20.280737,12.31043 -0.23402,0.90067 -3.37934,7.11612 -6.989587,13.81211 l -6.5641,12.17454 -5.35583,-3.87975 c -15.64626,-11.33409 -44.8493,-11.91773 -44.8493,-0.89633 0,3.21688 2.95424,4.27485 23.62238,8.45968 25.10136,5.08245 34.472857,9.96708 41.223847,21.48679 5.16036,8.80548 5.94011,22.60858 1.81842,32.18969 -7.09168,16.48505 -26.102537,25.68948 -52.664647,25.49848 -6.6,-0.0475 -15.6,-0.8788 -20,-1.84743 z m 567.333307,1.13668 c -32.00756,-6.19831 -50.95752,-29.15792 -51.03224,-61.83021 -0.0338,-14.78787 3.06608,-25.58157 10.6118,-36.94968 16.21276,-24.42555 52.80966,-33.58632 80.42044,-20.13048 20.86176,10.16677 33.33333,32.12683 33.33333,58.69362 v 10.38654 h -41.06666 c -31.01931,0 -41.50024,-0.40777 -42.83881,-1.66667 -1.45932,-1.37247 -1.61346,-0.96059 -0.87316,2.33333 2.75757,12.2698 17.15777,20.4888 33.01802,18.84527 8.3956,-0.87001 20.136,-4.89837 23.71456,-8.13692 2.16152,-1.95615 2.88573,-1.24493 10.51705,10.32824 4.50762,6.83599 8.19567,12.76956 8.19567,13.18572 0,1.88301 -11.54904,8.09493 -20.97088,11.27967 -8.17071,2.76181 -13.46459,3.58773 -25.02912,3.90486 -8.06666,0.22122 -16.16666,0.11174 -18,-0.24329 z M 637.9533,95.36249 c -0.53742,-3.50176 -4.2082,-8.51106 -8.68749,-11.85533 -4.35057,-3.24818 -18.39814,-4.51157 -25.20081,-2.26648 -5.57947,1.84139 -12.53085,8.82141 -13.76245,13.81914 l -0.91114,3.69734 24.48558,-0.364 c 23.63233,-0.35132 24.4713,-0.45693 24.07631,-3.03067 z m -521.59073,16.66667 v -60 h 18.66667 18.66667 v 60 60 h -18.66667 -18.66667 z m 52,0 v -60 h 19.33333 19.33334 v 4.57987 c 0,2.51893 -0.63739,6.25631 -1.4164,8.30529 -1.3562,3.56705 -1.20287,3.51189 3.60646,-1.29745 19.41055,-19.41054 56.51202,-19.6531 67.76986,-0.44305 l 3.36413,5.74046 6.19689,-5.93768 c 18.98153,-18.1875 53.48313,-19.09932 64.89537,-1.71507 6.03128,9.18742 6.25035,11.37098 6.25035,62.29992 v 48.46771 h -18.66667 -18.66666 l -0.008,-37 c -0.004,-20.5529 -0.60015,-39.13373 -1.34082,-41.80012 -1.79337,-6.45608 -5.36997,-9.38297 -12.43953,-10.17981 -7.82781,-0.88231 -13.91113,1.29777 -19.92747,7.14142 l -4.95085,4.8093 v 38.51461 38.5146 h -18.66666 -18.66667 l -0.003,-38.33334 C 244.35857,99.86469 244.08369,94.81658 242.02624,90.71437 236.66909,80.03321 221.95053,79.9 210.69497,90.43083 l -3.66666,3.43057 v 39.08388 39.08388 H 187.6953 168.36197 v -60 z M 505.6959,88.65922 V 5.28928 l 18.99999,0.36994 19,0.36994 0.34343,83 0.34343,83 H 525.03932 505.6959 Z m 159.99999,82.68159 c 0,-0.3786 9,-14.16884 20.00001,-30.64499 11,-16.47614 20,-30.22794 20,-30.55955 0,-0.33161 -7.9917,-12.79137 -17.7593,-27.68835 -9.76755,-14.89698 -18.20295,-27.83543 -18.74531,-28.75209 -0.77215,-1.30504 3.58745,-1.66131 20.09261,-1.64196 l 21.0787,0.0247 10.6501,16.96654 c 7.5749,12.06762 10.7868,16.1991 11.1235,14.30862 0.2604,-1.46185 4.4597,-9.10791 9.3317,-16.99125 l 8.8582,-14.33333 h 21.0969 c 16.5223,0 20.88,0.3614 20.0964,1.66667 -0.5503,0.91666 -8.9857,13.85358 -18.7453,28.74869 -9.7597,14.89511 -17.7449,27.35487 -17.7449,27.68835 0,0.33348 9,14.08681 20,30.56295 11,16.47615 20,30.28163 20,30.67884 0,0.39722 -9.4115,0.55698 -20.9144,0.35502 l -20.9145,-0.36719 -11.2959,-17.78227 -11.296,-17.78226 -11.791,18.1156 -11.7911,18.1156 h -20.6652 c -11.36586,0 -20.66521,-0.30976 -20.66521,-0.68835 z" + id="path12-3" + inkscape:connector-curvature="0" + sodipodi:nodetypes="sssssssccssssssssccccccccssscscsccsscccsccscccccsssscccsscssccscccccscccsscscscccsscscccccsscccccscccccccccccccccccccccssssscccccccssscsccscsccccsssscscssscscsscssscscccscsccscccscsccccccccccccccsccscccsccccssccccccccscccccccccccccccccssccscccccsccsssscscccscc" /> +</svg> diff --git a/assets/svg/buy/Simplex-Nuvei-Logo.svg b/assets/svg/buy/Simplex-Nuvei-Logo.svg new file mode 100644 index 000000000..736f6fd45 --- /dev/null +++ b/assets/svg/buy/Simplex-Nuvei-Logo.svg @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + id="svg2" + width="797.33331" + height="290.66666" + viewBox="0 0 797.33331 290.66666" + sodipodi:docname="Simplex-Nuvei-Logo.svg" + inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"> + <metadata + id="metadata8"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs6" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="2433" + inkscape:window-height="1039" + id="namedview4" + showgrid="false" + inkscape:zoom="0.87040136" + inkscape:cx="469.71272" + inkscape:cy="275.98726" + inkscape:window-x="72" + inkscape:window-y="0" + inkscape:window-maximized="0" + inkscape:current-layer="svg2" /> + <path + style="fill:#e40046;stroke-width:1.33333325;fill-opacity:1" + d="m 785.0292,226.02914 c -1.799,-2.74566 -1.9298,-4.0247 -0.6895,-6.74669 1.3002,-2.85376 2.2808,-3.29658 6.5224,-2.94537 4.5785,0.37909 5.0273,0.77962 5.4165,4.83377 0.2333,2.43124 -0.2901,5.28124 -1.1633,6.33333 -2.5279,3.04599 -7.6109,2.30263 -10.0861,-1.47504 z M 124.35434,39.498519 c -11.90422,-8.089127 -12.24198,-26.472494 -0.65311,-35.5464665 5.69338,-4.45786203 16.37923,-5.045403 23.02932,-1.266223 7.19287,4.087632 10.18063,8.7102585 10.85048,16.7876965 1.1458,13.816817 -7.41334,23.276906 -21.06009,23.276906 -5.62675,0 -8.51836,-0.772876 -12.1666,-3.251913 z" + id="path12" + inkscape:connector-curvature="0" + sodipodi:nodetypes="cccccccssssc" /> + <path + style="fill:#000000;stroke-width:1.33333325" + d="m 552.36256,283.36248 c 0,-2.60792 0.68044,-3.33334 3.12668,-3.33334 3.75651,0 7.53999,-3.82936 7.53999,-7.63141 0,-1.51513 -3.3,-10.73496 -7.33334,-20.48852 -4.03333,-9.75356 -7.33333,-18.21667 -7.33333,-18.80691 0,-0.59024 1.99911,-1.07316 4.44245,-1.07316 4.40215,0 4.48835,0.1212 9.50144,13.35855 2.78244,7.34719 5.45658,13.60425 5.94252,13.90459 0.48595,0.30033 3.23898,-5.71102 6.11787,-13.35855 5.18256,-13.76708 5.27765,-13.90459 9.61503,-13.90459 2.40937,0 4.38069,0.48226 4.38069,1.07168 0,0.58943 -4.243,11.53943 -9.42888,24.33334 -10.47103,25.83273 -12.96841,29.26165 -21.31213,29.26165 -4.7207,0 -5.25899,-0.34119 -5.25899,-3.33333 z M 517.20904,269.2657 c -2.91224,-2.29077 -3.51315,-2.4048 -3.51315,-0.66666 0,1.44085 -1.25128,2.09677 -3.99999,2.09677 h -4 v -26.66667 -26.66665 h 4 3.99999 v 9.31373 9.31372 l 4.33334,-2.64208 c 5.30393,-3.23385 10.43481,-3.36171 16.6451,-0.41474 12.57463,5.96705 12.7231,30.70017 0.22311,37.16417 -5.45992,2.82344 -13.52375,2.44433 -17.6884,-0.83159 z m 15.18788,-8.90322 c 2.35075,-2.97778 2.77697,-4.90328 2.26861,-10.24858 -1.01185,-10.6394 -9.55112,-14.87302 -17.45649,-8.65465 -3.08255,2.42473 -3.51315,3.72285 -3.51315,10.59107 0,6.57734 0.47332,8.15914 2.96335,9.90322 4.91331,3.44142 12.35915,2.68866 15.73768,-1.59106 z M 664.848,270.46969 c -4.62026,-2.23717 -5.81878,-7.00959 -5.81878,-23.16995 v -15.2706 h 6 6 v 10.57987 c 0,5.81893 0.56563,12.06759 1.25696,13.88591 1.66876,4.38918 6.01392,5.65198 10.14166,2.9474 3.08796,-2.02334 3.26806,-2.83755 3.26806,-14.77724 v -12.63594 h 6 6 v 19.33334 19.33333 h -6 c -4.238,0 -6,-0.54513 -6,-1.85627 0,-1.46638 -1.0394,-1.32636 -4.9486,0.66667 -5.40013,2.75315 -11.44653,3.11955 -15.8993,0.96348 z m 85.1362,-1.09147 c -4.3604,-1.97904 -6.325,-3.94366 -8.3041,-8.30405 -3.335,-7.34811 -3.3109,-12.17291 0.096,-19.21059 4.7684,-9.85005 17.9432,-13.96921 27.9407,-8.73578 5.193,2.7184 10.6459,11.86154 10.6459,17.85053 v 3.71748 h -14 c -7.7,0 -14,0.50701 -14,1.12668 0,1.66849 4.2493,5.09759 7.5688,6.10773 1.5961,0.48574 5.0077,0.004 7.5812,-1.0719 4.5142,-1.88615 4.7563,-1.83742 6.8659,1.38225 2.9285,4.46948 1.9485,6.31099 -4.3652,8.20261 -7.4905,2.24422 -13.4344,1.92818 -20.0291,-1.06496 z m 17.1271,-25.1957 c -2.6669,-4.98314 -11.9101,-4.66606 -13.8834,0.47625 -0.6476,1.68777 0.5909,2.03704 7.2234,2.03704 7.4291,0 7.9083,-0.18084 6.66,-2.51329 z m -149.41541,7.17996 v -19.33334 h 6 c 4.44789,0 6,0.51804 6,2.00259 0,1.66224 0.84641,1.54895 4.98025,-0.66667 2.73915,-1.46809 6.66894,-2.66924 8.73288,-2.66924 4.65463,0 11.02084,3.18158 12.1253,6.05972 0.45553,1.18711 0.82824,9.3093 0.82824,18.04932 v 15.89095 h -6 -6 V 258.2358 c 0,-10.61235 -0.38127,-12.80507 -2.57112,-14.78686 -1.75924,-1.59209 -3.54859,-2.02684 -5.66667,-1.37678 -5.93675,1.82198 -6.42888,3.05601 -6.42888,16.12024 v 12.50341 h -6 -6 z m 95.44801,18.18554 c -2.0625,-3.33726 -12.7813,-32.61501 -12.7813,-34.91145 0,-2.17984 0.9319,-2.60743 5.6828,-2.60743 h 5.6829 l 3.902,11.25659 c 2.146,6.19113 4.319,10.99884 4.8287,10.6838 0.5098,-0.31504 2.6004,-5.38051 4.646,-11.2566 l 3.7191,-10.68379 h 6.4359 c 3.5397,0 6.4323,0.45 6.4279,1 0,0.55 -3.1839,9.1 -7.0654,19 l -7.0573,18 -6.8155,0.39888 c -3.7485,0.21939 -7.1711,-0.17661 -7.6058,-0.88 z m 69.8853,-18.18554 v -19.33334 h 6 6 v 19.33334 19.33333 h -6 -6 z M 372.36257,134.69582 V 52.02916 h 19.33333 19.33333 v 6.13812 6.13813 l 4.70373,-4.12994 c 10.5474,-9.26074 24.4038,-13.08556 38.97368,-10.75801 26.89726,4.29686 42.2916,24.38254 43.90008,57.27836 1.22411,25.03478 -4.63209,42.51699 -18.56269,55.41427 -6.23869,5.77592 -9.6518,7.77412 -17.18981,10.06372 -17.50572,5.3172 -34.61887,1.93937 -46.72588,-9.22285 l -5.76578,-5.31583 0.46191,29.86368 0.46192,29.86368 h -19.46192 -19.4619 z m 68.66666,5.18662 c 3.31185,-0.90984 7.43647,-3.64879 10.88208,-7.22624 13.41396,-13.92722 9.71121,-39.26103 -6.91468,-47.30949 -8.35727,-4.04568 -18.11716,-3.25662 -26.90811,2.17546 l -6.39262,3.95011 -0.37108,19.13174 c -0.40826,21.04839 -0.0876,22.06243 8.3593,26.43046 8.52487,4.40838 13.33094,5.04962 21.34511,2.84796 z M 34.362583,172.72269 c -4.4,-0.96863 -11.3,-3.0638 -15.33334,-4.65593 -7.22766,-2.8531 -19.99999,-10.31535 -19.99999,-11.68503 0,-0.64356 11.79414,-21.42021 14.41491,-25.39343 0.86027,-1.30421 2.99133,-0.58418 9.02236,3.04842 8.29972,4.99909 20.00456,9.1455 30.08306,10.65686 11.51899,1.72739 22.95943,-4.1186 19.75982,-10.09712 -1.57731,-2.94722 -6.00807,-4.33089 -25.28016,-7.89461 C 17.561163,121.25272 5.809853,112.145 3.561193,93.01243 1.649683,76.7484 10.897933,61.37578 26.919603,54.18568 c 15.17038,-6.80806 37.83079,-7.11508 56.92106,-0.7712 9.6907,3.22032 20.878177,10.01112 20.280737,12.31043 -0.23402,0.90067 -3.37934,7.11612 -6.989587,13.81211 l -6.5641,12.17454 -5.35583,-3.87975 c -15.64626,-11.33409 -44.8493,-11.91773 -44.8493,-0.89633 0,3.21688 2.95424,4.27485 23.62238,8.45968 25.10136,5.08245 34.472857,9.96708 41.223847,21.48679 5.16036,8.80548 5.94011,22.60858 1.81842,32.18969 -7.09168,16.48505 -26.102537,25.68948 -52.664647,25.49848 -6.6,-0.0475 -15.6,-0.8788 -20,-1.84743 z m 567.333307,1.13668 c -32.00756,-6.19831 -50.95752,-29.15792 -51.03224,-61.83021 -0.0338,-14.78787 3.06608,-25.58157 10.6118,-36.94968 16.21276,-24.42555 52.80966,-33.58632 80.42044,-20.13048 20.86176,10.16677 33.33333,32.12683 33.33333,58.69362 v 10.38654 h -41.06666 c -31.01931,0 -41.50024,-0.40777 -42.83881,-1.66667 -1.45932,-1.37247 -1.61346,-0.96059 -0.87316,2.33333 2.75757,12.2698 17.15777,20.4888 33.01802,18.84527 8.3956,-0.87001 20.136,-4.89837 23.71456,-8.13692 2.16152,-1.95615 2.88573,-1.24493 10.51705,10.32824 4.50762,6.83599 8.19567,12.76956 8.19567,13.18572 0,1.88301 -11.54904,8.09493 -20.97088,11.27967 -8.17071,2.76181 -13.46459,3.58773 -25.02912,3.90486 -8.06666,0.22122 -16.16666,0.11174 -18,-0.24329 z M 637.9533,95.36249 c -0.53742,-3.50176 -4.2082,-8.51106 -8.68749,-11.85533 -4.35057,-3.24818 -18.39814,-4.51157 -25.20081,-2.26648 -5.57947,1.84139 -12.53085,8.82141 -13.76245,13.81914 l -0.91114,3.69734 24.48558,-0.364 c 23.63233,-0.35132 24.4713,-0.45693 24.07631,-3.03067 z m -521.59073,16.66667 v -60 h 18.66667 18.66667 v 60 60 h -18.66667 -18.66667 z m 52,0 v -60 h 19.33333 19.33334 v 4.57987 c 0,2.51893 -0.63739,6.25631 -1.4164,8.30529 -1.3562,3.56705 -1.20287,3.51189 3.60646,-1.29745 19.41055,-19.41054 56.51202,-19.6531 67.76986,-0.44305 l 3.36413,5.74046 6.19689,-5.93768 c 18.98153,-18.1875 53.48313,-19.09932 64.89537,-1.71507 6.03128,9.18742 6.25035,11.37098 6.25035,62.29992 v 48.46771 h -18.66667 -18.66666 l -0.008,-37 c -0.004,-20.5529 -0.60015,-39.13373 -1.34082,-41.80012 -1.79337,-6.45608 -5.36997,-9.38297 -12.43953,-10.17981 -7.82781,-0.88231 -13.91113,1.29777 -19.92747,7.14142 l -4.95085,4.8093 v 38.51461 38.5146 h -18.66666 -18.66667 l -0.003,-38.33334 C 244.35857,99.86469 244.08369,94.81658 242.02624,90.71437 236.66909,80.03321 221.95053,79.9 210.69497,90.43083 l -3.66666,3.43057 v 39.08388 39.08388 H 187.6953 168.36197 v -60 z M 505.6959,88.65922 V 5.28928 l 18.99999,0.36994 19,0.36994 0.34343,83 0.34343,83 H 525.03932 505.6959 Z m 159.99999,82.68159 c 0,-0.3786 9,-14.16884 20.00001,-30.64499 11,-16.47614 20,-30.22794 20,-30.55955 0,-0.33161 -7.9917,-12.79137 -17.7593,-27.68835 -9.76755,-14.89698 -18.20295,-27.83543 -18.74531,-28.75209 -0.77215,-1.30504 3.58745,-1.66131 20.09261,-1.64196 l 21.0787,0.0247 10.6501,16.96654 c 7.5749,12.06762 10.7868,16.1991 11.1235,14.30862 0.2604,-1.46185 4.4597,-9.10791 9.3317,-16.99125 l 8.8582,-14.33333 h 21.0969 c 16.5223,0 20.88,0.3614 20.0964,1.66667 -0.5503,0.91666 -8.9857,13.85358 -18.7453,28.74869 -9.7597,14.89511 -17.7449,27.35487 -17.7449,27.68835 0,0.33348 9,14.08681 20,30.56295 11,16.47615 20,30.28163 20,30.67884 0,0.39722 -9.4115,0.55698 -20.9144,0.35502 l -20.9145,-0.36719 -11.2959,-17.78227 -11.296,-17.78226 -11.791,18.1156 -11.7911,18.1156 h -20.6652 c -11.36586,0 -20.66521,-0.30976 -20.66521,-0.68835 z" + id="path12-3" + inkscape:connector-curvature="0" + sodipodi:nodetypes="sssssssccssssssssccccccccssscscsccsscccsccscccccsssscccsscssccscccccscccsscscscccsscscccccsscccccscccccccccccccccccccccssssscccccccssscsccscsccccsssscscssscscsscssscscccscsccscccscsccccccccccccccsccscccsccccssccccccccscccccccccccccccccssccscccccsccsssscscccscc" /> +</svg> diff --git a/assets/svg/cc.svg b/assets/svg/cc.svg new file mode 100644 index 000000000..646ae64ce --- /dev/null +++ b/assets/svg/cc.svg @@ -0,0 +1,11 @@ +<svg width="25" height="24" viewBox="0 0 25 24" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g clip-path="url(#clip0_7503_4777)"> +<path d="M24.5 10.5H0.5V6H24.5V10.5Z" fill="#424A97"/> +<path opacity="0.4" d="M21.8333 1.5C23.3042 1.5 24.5 2.84297 24.5 4.5V6H0.5V4.5C0.5 2.84297 1.69375 1.5 3.16667 1.5H21.8333ZM24.5 19.5C24.5 21.1547 23.3042 22.5 21.8333 22.5H3.16667C1.69375 22.5 0.5 21.1547 0.5 19.5V10.5H24.5V19.5ZM5.16667 16.5C4.8 16.5 4.5 16.8375 4.5 17.25C4.5 17.6625 4.8 18 5.16667 18H7.83333C8.2 18 8.5 17.6625 8.5 17.25C8.5 16.8375 8.2 16.5 7.83333 16.5H5.16667ZM10.5 18H15.8333C16.2 18 16.5 17.6625 16.5 17.25C16.5 16.8375 16.2 16.5 15.8333 16.5H10.5C10.1333 16.5 9.83333 16.8375 9.83333 17.25C9.83333 17.6625 10.1333 18 10.5 18Z" fill="#424A97"/> +</g> +<defs> +<clipPath id="clip0_7503_4777"> +<rect width="24" height="24" fill="white" transform="translate(0.5)"/> +</clipPath> +</defs> +</svg> diff --git a/assets/svg/circle-plus-filled.svg b/assets/svg/circle-plus-filled.svg new file mode 100644 index 000000000..3e3244adb --- /dev/null +++ b/assets/svg/circle-plus-filled.svg @@ -0,0 +1,3 @@ +<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M0 10C0 4.47656 4.47656 0 10 0C15.5234 0 20 4.47656 20 10C20 15.5234 15.5234 20 10 20C4.47656 20 0 15.5234 0 10ZM10 14.375C10.5195 14.375 10.9375 13.957 10.9375 13.4375V10.9375H13.4375C13.957 10.9375 14.375 10.5195 14.375 10C14.375 9.48047 13.957 9.0625 13.4375 9.0625H10.9375V6.5625C10.9375 6.04297 10.5195 5.625 10 5.625C9.48047 5.625 9.0625 6.04297 9.0625 6.5625V9.0625H6.5625C6.04297 9.0625 5.625 9.48047 5.625 10C5.625 10.5195 6.04297 10.9375 6.5625 10.9375H9.0625V13.4375C9.0625 13.957 9.48047 14.375 10 14.375Z" fill="#232323"/> +</svg> diff --git a/assets/svg/circle-plus.svg b/assets/svg/circle-plus.svg new file mode 100644 index 000000000..a09b12711 --- /dev/null +++ b/assets/svg/circle-plus.svg @@ -0,0 +1,10 @@ +<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> +<g clip-path="url(#clip0_7035_24064)"> +<path d="M0 10C0 4.47656 4.47656 0 10 0C15.5234 0 20 4.47656 20 10C20 15.5234 15.5234 20 10 20C4.47656 20 0 15.5234 0 10ZM10 14.375C10.5195 14.375 10.9375 13.957 10.9375 13.4375V10.9375H13.4375C13.957 10.9375 14.375 10.5195 14.375 10C14.375 9.48047 13.957 9.0625 13.4375 9.0625H10.9375V6.5625C10.9375 6.04297 10.5195 5.625 10 5.625C9.48047 5.625 9.0625 6.04297 9.0625 6.5625V9.0625H6.5625C6.04297 9.0625 5.625 9.48047 5.625 10C5.625 10.5195 6.04297 10.9375 6.5625 10.9375H9.0625V13.4375C9.0625 13.957 9.48047 14.375 10 14.375Z" fill="#232323"/> +</g> +<defs> +<clipPath id="clip0_7035_24064"> +<rect width="20" height="20" fill="white"/> +</clipPath> +</defs> +</svg> diff --git a/assets/svg/coin_control/frozen.svg b/assets/svg/coin_control/frozen.svg new file mode 100644 index 000000000..3fe52d57e --- /dev/null +++ b/assets/svg/coin_control/frozen.svg @@ -0,0 +1,3 @@ +<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M17.8628 14.5871C17.6515 14.9523 17.2648 15.1564 16.8709 15.1564C16.6744 15.1564 16.4756 15.1061 16.2934 14.9998L15.395 14.4755L15.5876 15.2021C15.7091 15.6608 15.4352 16.132 14.976 16.2527C14.9021 16.2728 14.8281 16.2817 14.7553 16.2817C14.3746 16.2817 14.0262 16.0277 13.9242 15.6429L13.289 13.2467L11.1454 11.9946L11.1454 14.519L12.8777 16.2693C13.2121 16.6073 13.2087 17.1509 12.871 17.4846C12.703 17.6522 12.4846 17.7345 12.2661 17.7345C12.0444 17.7345 11.8225 17.6495 11.6545 17.4794L11.1454 16.9649V18.021C11.1454 18.6544 10.6319 19.1668 9.99845 19.1668C9.36502 19.1668 8.85155 18.6544 8.85155 18.021L8.85154 16.9647L8.34236 17.4792C8.00856 17.8172 7.46401 17.8194 7.12599 17.4859C6.78825 17.1525 6.78492 16.6087 7.11927 16.2706L8.85163 14.5204L8.82147 11.9946L6.67768 13.2454L6.04246 15.6416C5.94048 16.0265 5.59201 16.2804 5.21138 16.2804C5.13851 16.2804 5.06457 16.2715 4.99066 16.2513C4.53161 16.1305 4.25768 15.6594 4.37907 15.2007L4.57168 14.4742L3.67327 14.9984C3.52235 15.1063 3.32362 15.1564 3.12703 15.1564C2.7328 15.1564 2.34858 14.9527 2.13553 14.5878C1.8157 14.0417 2 13.3403 2.54731 13.0212L3.47687 12.4787L2.74891 12.2815C2.29022 12.1593 2.01987 11.6867 2.14412 11.2284C2.26837 10.77 2.73888 10.5015 3.19936 10.6232L5.55942 11.2642L7.72576 10.0002L5.55942 8.73616L3.19936 9.37712C3.12378 9.39726 3.04808 9.40733 2.9736 9.40733C2.59512 9.40733 2.24779 9.15557 2.1443 8.77282C2.01987 8.31364 2.29022 7.84456 2.74891 7.71924L3.47687 7.52201L2.54731 6.97803C2.00018 6.65913 1.81577 5.95752 2.13553 5.41146C2.45368 4.86432 3.15604 4.67956 3.70425 4.99967L4.60265 5.52389L4.41001 4.79736C4.28862 4.33939 4.56398 3.86816 4.99009 3.74749C5.48423 3.62324 5.92108 3.89967 6.07505 4.35729L6.71027 6.75352L8.82147 8.0057L8.82146 5.48128L7.12062 3.72959C6.78761 3.39157 6.78761 2.84766 7.12778 2.51429C7.46437 2.17949 8.00864 2.18415 8.34523 2.52109L8.85441 3.03564L8.82147 1.97933C8.82147 1.3459 9.33494 0.833496 9.96838 0.833496C10.6018 0.833496 11.1153 1.3459 11.1153 1.97933L11.1153 3.03564L11.6245 2.52109C11.9577 2.18429 12.5021 2.17981 12.8408 2.51438C13.1786 2.84782 13.1819 3.39166 12.8476 3.72968L11.1152 5.47994L11.1454 8.0057L13.2891 6.75495L13.9244 4.35872C14.0459 3.90111 14.5173 3.62467 14.9764 3.74893C15.4354 3.86978 15.7094 4.34082 15.588 4.79951L15.3954 5.52604L16.2938 5.00182C16.8409 4.68292 17.5438 4.86755 17.8625 5.41361C18.1823 5.95967 17.998 6.66113 17.4507 6.98018L16.5211 7.52266L17.2491 7.71988C17.7078 7.8441 17.9785 8.31643 17.8539 8.77405C17.7502 9.15683 17.4031 9.40855 17.0246 9.40855C16.9501 9.40855 16.8745 9.39848 16.7988 9.37834L14.4074 8.73616L12.2733 10.0002L14.4382 11.2634L16.7997 10.6236C17.2612 10.5015 17.7303 10.77 17.8556 11.2284C17.98 11.686 17.7095 12.1583 17.2509 12.2825L16.5229 12.4797L17.4525 13.0222C17.9989 13.341 18.1815 14.0428 17.8628 14.5871Z" fill="#96B0D6"/> +</svg> diff --git a/assets/svg/coin_control/gamepad.svg b/assets/svg/coin_control/gamepad.svg new file mode 100644 index 000000000..6fe0b6c30 --- /dev/null +++ b/assets/svg/coin_control/gamepad.svg @@ -0,0 +1,3 @@ +<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M20.0002 14.3397C20.0002 14.1755 19.9859 14.0085 19.9562 13.8404L18.9131 7.22787C18.4814 4.76975 16.3127 3.3335 9.97206 3.3335C3.71894 3.3335 1.52237 4.75412 1.08706 7.22725L0.0439375 13.8397C0.0142906 14.008 0 14.1747 0 14.3391C0 15.9607 1.39313 17.3332 3.17188 17.3332C4.72469 17.3332 6.1025 16.3938 6.59375 15.0007L6.82812 14.3332H13.1719L13.4062 15.0007C13.8975 16.3938 15.2753 17.3332 16.8281 17.3332C18.6064 17.3054 20.0002 15.9616 20.0002 14.3397ZM7.72206 10.0835L6.72331 10.0832L6.72206 11.0835C6.72206 11.496 6.38519 11.8335 5.97269 11.8335C5.56019 11.8335 5.22206 11.496 5.22206 11.0835L5.22321 10.0832L4.22206 10.0835C3.80956 10.0835 3.47269 9.746 3.47269 9.3335C3.47269 8.921 3.80925 8.5835 4.22206 8.5835L5.22331 8.58315L5.22206 7.5835C5.22206 7.171 5.56019 6.8335 5.97269 6.8335C6.38519 6.8335 6.72206 7.171 6.72206 7.5835L6.72321 8.58318L7.72206 8.5835C8.13456 8.5835 8.47269 8.921 8.47269 9.3335C8.47206 9.746 8.16269 10.0835 7.72206 10.0835ZM13.5002 12.0554C12.8099 12.0554 12.2502 11.4954 12.2502 10.8054C12.2502 10.1154 12.8099 9.55537 13.5002 9.55537C14.1905 9.55537 14.7502 10.1154 14.7502 10.8054C14.7502 11.5241 14.1908 12.0554 13.5002 12.0554ZM15.5002 9.05537C14.8099 9.05537 14.2502 8.49537 14.2502 7.80537C14.2502 7.11537 14.8099 6.55537 15.5002 6.55537C16.1905 6.55537 16.7502 7.11537 16.7502 7.80537C16.7502 8.52412 16.1908 9.05537 15.5002 9.05537Z" fill="#8E9192"/> +</svg> diff --git a/assets/svg/coin_control/selected.svg b/assets/svg/coin_control/selected.svg new file mode 100644 index 000000000..454ad8af6 --- /dev/null +++ b/assets/svg/coin_control/selected.svg @@ -0,0 +1,3 @@ +<svg width="16" height="18" viewBox="0 0 16 18" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path d="M15.7179 3.5972C16.0821 3.92551 16.0821 4.45639 15.7179 4.75327L6.28929 13.9739C5.98571 14.3301 5.44286 14.3301 5.10714 13.9739L0.251036 9.22385C-0.0836786 8.92698 -0.0836786 8.3961 0.251036 8.06779C0.585714 7.74297 1.12857 7.74297 1.46321 8.06779L5.71429 12.2275L14.5357 3.5972C14.8714 3.27099 15.4143 3.27099 15.7179 3.5972Z" fill="white"/> +</svg> diff --git a/assets/svg/coin_control/unfrozen.svg b/assets/svg/coin_control/unfrozen.svg new file mode 100644 index 000000000..d3d4da221 --- /dev/null +++ b/assets/svg/coin_control/unfrozen.svg @@ -0,0 +1,3 @@ +<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M4.27148 8.01188C4.27148 9.27588 6.83529 10.3035 10.0007 10.3035C13.166 10.3035 15.7298 9.27588 15.7298 8.01188C15.7298 6.74788 13.166 5.72021 10.0007 5.72021C6.83529 5.72021 4.27148 6.74788 4.27148 8.01188ZM19.1673 8.29834C19.1673 5.45166 15.0638 3.14209 10.0007 3.14209C4.9375 3.14209 0.833984 5.45166 0.833984 8.29834C0.833984 11.145 4.9375 13.4546 10.0007 13.4546C15.0638 13.4546 19.1673 11.145 19.1673 8.29834ZM3.93454 9.73779C3.50163 9.32601 3.09342 8.74593 3.09342 8.01188C3.09342 7.27783 3.50163 6.69775 3.93454 6.28597C4.36458 5.88135 4.92676 5.56624 5.49251 5.32633C6.72786 4.84652 8.31055 4.57438 9.96842 4.57438C11.6908 4.57438 13.2734 4.84652 14.4766 5.32633C15.0745 5.56624 15.6367 5.88135 16.0664 6.28597C16.4997 6.69775 16.8757 7.27783 16.8757 8.01188C16.8757 8.74593 16.4997 9.32601 16.0664 9.73779C15.6367 10.1424 15.0745 10.4575 14.4766 10.6652C13.2734 11.1772 11.6908 11.4494 10.0007 11.4494C8.31055 11.4494 6.72786 11.1772 5.49251 10.6652C4.92676 10.4575 4.36458 10.1424 3.93454 9.73779ZM19.1673 11.2386C18.6947 11.8008 18.1038 12.2878 17.4486 12.6709V14.9805C18.5299 14.2285 19.1673 13.3047 19.1673 12.277V11.2386ZM16.3027 15.6071V13.3226C15.2858 13.7917 14.1221 14.1462 12.8652 14.361V16.6634C14.1615 16.4521 15.3324 16.0977 16.3027 15.6071ZM9.90337 14.5687C9.3533 14.5689 8.81281 14.5691 8.2819 14.515V16.8138C8.8022 16.8608 9.34137 16.8606 9.89058 16.8604C9.92723 16.8604 9.96392 16.8604 10.0007 16.8604C10.0374 16.8604 10.0741 16.8604 10.1107 16.8604C10.6599 16.8606 11.1991 16.8608 11.7194 16.8138V14.515C11.1885 14.5691 10.648 14.5689 10.0979 14.5687C10.0655 14.5687 10.0331 14.5687 10.0007 14.5687C9.96819 14.5687 9.93576 14.5687 9.90337 14.5687ZM7.13607 16.6634V14.361C5.87923 14.1462 4.71549 13.7917 3.69857 13.3226V15.6071C4.66895 16.0977 5.83984 16.4521 7.13607 16.6634ZM0.833984 12.277C0.833984 13.3047 1.471 14.2285 2.55273 14.9805V12.6709C1.89818 12.2878 1.307 11.8008 0.833984 11.2386V12.277Z" fill="#F7931A"/> +</svg> diff --git a/assets/svg/coin_icons/Bitcoin.svg b/assets/svg/coin_icons/Bitcoin.svg deleted file mode 100644 index f1fa5a204..000000000 --- a/assets/svg/coin_icons/Bitcoin.svg +++ /dev/null @@ -1,4 +0,0 @@ -<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> -<path d="M23.6408 14.9029C22.0379 21.3315 15.5261 25.2438 9.09602 23.6407C2.66858 22.038 -1.24421 15.5266 0.359461 9.09838C1.96163 2.66907 8.47345 -1.24364 14.9016 0.359095C21.3313 1.96183 25.2437 8.47401 23.6408 14.9029Z" fill="#F7931A"/> -<path d="M17.2896 10.2905C17.5285 8.69373 16.3126 7.83536 14.6501 7.26274L15.1894 5.09976L13.8726 4.77164L13.3476 6.87762C13.0014 6.79137 12.6459 6.71 12.2926 6.62937L12.8214 4.50952L11.5054 4.1814L10.9657 6.34363C10.6791 6.27838 10.3979 6.21388 10.1248 6.146L10.1263 6.13925L8.3104 5.68588L7.96011 7.09212C7.96011 7.09212 8.93709 7.31599 8.91646 7.32986C9.44977 7.46299 9.54615 7.81586 9.53003 8.09561L8.91571 10.5597C8.95246 10.5691 9.0001 10.5826 9.0526 10.6036C9.00872 10.5927 8.96184 10.5807 8.91346 10.5691L8.05237 14.0209C7.98711 14.1829 7.82172 14.4259 7.44893 14.3337C7.46206 14.3528 6.49183 14.0948 6.49183 14.0948L5.83813 15.6019L7.55169 16.029C7.87048 16.1089 8.18288 16.1925 8.49041 16.2713L7.94548 18.459L9.26075 18.7871L9.80043 16.6226C10.1597 16.7201 10.5085 16.8101 10.8498 16.8949L10.312 19.0492L11.6287 19.3774L12.1737 17.1938C14.419 17.6186 16.1075 17.4473 16.8182 15.4167C17.3909 13.7817 16.7897 12.8386 15.6083 12.2236C16.4686 12.0252 17.1167 11.4593 17.2896 10.2905V10.2905ZM14.281 14.5088C13.8741 16.1438 11.1209 15.2599 10.2284 15.0383L10.9514 12.1399C11.844 12.3627 14.7063 12.8037 14.281 14.5088V14.5088ZM14.6883 10.2668C14.317 11.7541 12.0255 10.9985 11.2822 10.8132L11.9378 8.18448C12.6811 8.36973 15.075 8.71548 14.6883 10.2668V10.2668Z" fill="white"/> -</svg> diff --git a/assets/svg/coin_icons/Bitcoincash.svg b/assets/svg/coin_icons/Bitcoincash.svg deleted file mode 100644 index 4e700f9e0..000000000 --- a/assets/svg/coin_icons/Bitcoincash.svg +++ /dev/null @@ -1 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 511.76 511.76"><title>bitcoin-cash-bch \ No newline at end of file diff --git a/assets/svg/coin_icons/Dogecoin.svg b/assets/svg/coin_icons/Dogecoin.svg deleted file mode 100644 index c435731dc..000000000 --- a/assets/svg/coin_icons/Dogecoin.svg +++ /dev/null @@ -1 +0,0 @@ -Dogecoin (DOGE) \ No newline at end of file diff --git a/assets/svg/coin_icons/EpicCash.svg b/assets/svg/coin_icons/EpicCash.svg deleted file mode 100644 index adf888ede..000000000 --- a/assets/svg/coin_icons/EpicCash.svg +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/svg/coin_icons/Firo.svg b/assets/svg/coin_icons/Firo.svg deleted file mode 100644 index 5eea49eff..000000000 --- a/assets/svg/coin_icons/Firo.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/assets/svg/coin_icons/Litecoin.svg b/assets/svg/coin_icons/Litecoin.svg deleted file mode 100644 index 2b89ca50b..000000000 --- a/assets/svg/coin_icons/Litecoin.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/assets/svg/coin_icons/Monero.svg b/assets/svg/coin_icons/Monero.svg deleted file mode 100644 index d1b70188e..000000000 --- a/assets/svg/coin_icons/Monero.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/assets/svg/coin_icons/Namecoin.svg b/assets/svg/coin_icons/Namecoin.svg deleted file mode 100644 index 2cda6aaf0..000000000 --- a/assets/svg/coin_icons/Namecoin.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/svg/coin_icons/Particl.svg b/assets/svg/coin_icons/Particl.svg deleted file mode 100644 index 3f8a920ab..000000000 --- a/assets/svg/coin_icons/Particl.svg +++ /dev/null @@ -1 +0,0 @@ -particl-part-logo \ No newline at end of file diff --git a/assets/svg/coin_icons/Wownero.svg b/assets/svg/coin_icons/Wownero.svg deleted file mode 100644 index f7a90e94c..000000000 --- a/assets/svg/coin_icons/Wownero.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/assets/svg/dark/bell-new.svg b/assets/svg/dark/bell-new.svg deleted file mode 100644 index f976e0986..000000000 --- a/assets/svg/dark/bell-new.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/assets/svg/dark/buy-coins-icon.svg b/assets/svg/dark/buy-coins-icon.svg deleted file mode 100644 index 9170c4190..000000000 --- a/assets/svg/dark/buy-coins-icon.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/assets/svg/dark/exchange-2.svg b/assets/svg/dark/exchange-2.svg deleted file mode 100644 index ee04dcebe..000000000 --- a/assets/svg/dark/exchange-2.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/assets/svg/dark/stack-icon1.svg b/assets/svg/dark/stack-icon1.svg deleted file mode 100644 index 4fb16176a..000000000 --- a/assets/svg/dark/stack-icon1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/assets/svg/dark/tx-exchange-icon-failed.svg b/assets/svg/dark/tx-exchange-icon-failed.svg deleted file mode 100644 index 64acda4e9..000000000 --- a/assets/svg/dark/tx-exchange-icon-failed.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/assets/svg/dark/tx-exchange-icon-pending.svg b/assets/svg/dark/tx-exchange-icon-pending.svg deleted file mode 100644 index f9cdeb7c2..000000000 --- a/assets/svg/dark/tx-exchange-icon-pending.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/assets/svg/dark/tx-exchange-icon.svg b/assets/svg/dark/tx-exchange-icon.svg deleted file mode 100644 index 36b2cf7cc..000000000 --- a/assets/svg/dark/tx-exchange-icon.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/assets/svg/dark/tx-icon-receive-failed.svg b/assets/svg/dark/tx-icon-receive-failed.svg deleted file mode 100644 index cb1d500b1..000000000 --- a/assets/svg/dark/tx-icon-receive-failed.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/assets/svg/dark/tx-icon-receive-pending.svg b/assets/svg/dark/tx-icon-receive-pending.svg deleted file mode 100644 index efb8350b3..000000000 --- a/assets/svg/dark/tx-icon-receive-pending.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/assets/svg/dark/tx-icon-receive.svg b/assets/svg/dark/tx-icon-receive.svg deleted file mode 100644 index 15be19d52..000000000 --- a/assets/svg/dark/tx-icon-receive.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/assets/svg/dark/tx-icon-send-failed.svg b/assets/svg/dark/tx-icon-send-failed.svg deleted file mode 100644 index 2be637ef3..000000000 --- a/assets/svg/dark/tx-icon-send-failed.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/assets/svg/dark/tx-icon-send-pending.svg b/assets/svg/dark/tx-icon-send-pending.svg deleted file mode 100644 index 50cca5a9e..000000000 --- a/assets/svg/dark/tx-icon-send-pending.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/assets/svg/dark/tx-icon-send.svg b/assets/svg/dark/tx-icon-send.svg deleted file mode 100644 index 0e64ee37e..000000000 --- a/assets/svg/dark/tx-icon-send.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/assets/svg/exchange_icons/mb_blue.svg b/assets/svg/exchange_icons/mb_blue.svg new file mode 100644 index 000000000..d9fea3781 --- /dev/null +++ b/assets/svg/exchange_icons/mb_blue.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/svg/exchange_icons/mb_green.svg b/assets/svg/exchange_icons/mb_green.svg new file mode 100644 index 000000000..d5d10ca7e --- /dev/null +++ b/assets/svg/exchange_icons/mb_green.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/svg/exchange_icons/trocador.svg b/assets/svg/exchange_icons/trocador.svg new file mode 100644 index 000000000..b3d9171ff --- /dev/null +++ b/assets/svg/exchange_icons/trocador.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/faceid.svg b/assets/svg/faceid.svg new file mode 100644 index 000000000..52ea3712d --- /dev/null +++ b/assets/svg/faceid.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/assets/svg/file-upload.svg b/assets/svg/file-upload.svg new file mode 100644 index 000000000..0e3576cbe --- /dev/null +++ b/assets/svg/file-upload.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/file.svg b/assets/svg/file.svg new file mode 100644 index 000000000..afe1d662e --- /dev/null +++ b/assets/svg/file.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/fingerprint.svg b/assets/svg/fingerprint.svg new file mode 100644 index 000000000..5919bdd1a --- /dev/null +++ b/assets/svg/fingerprint.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/svg/light/bell-new.svg b/assets/svg/light/bell-new.svg deleted file mode 100644 index ffb3d630b..000000000 --- a/assets/svg/light/bell-new.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/assets/svg/light/buy-coins-icon.svg b/assets/svg/light/buy-coins-icon.svg deleted file mode 100644 index 5788e2bf0..000000000 --- a/assets/svg/light/buy-coins-icon.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/assets/svg/light/exchange-2.svg b/assets/svg/light/exchange-2.svg deleted file mode 100644 index 11e246a3b..000000000 --- a/assets/svg/light/exchange-2.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/assets/svg/light/stack-icon1.svg b/assets/svg/light/stack-icon1.svg deleted file mode 100644 index f316012d7..000000000 --- a/assets/svg/light/stack-icon1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/assets/svg/light/tx-exchange-icon-failed.svg b/assets/svg/light/tx-exchange-icon-failed.svg deleted file mode 100644 index a54836bba..000000000 --- a/assets/svg/light/tx-exchange-icon-failed.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/assets/svg/light/tx-exchange-icon-pending.svg b/assets/svg/light/tx-exchange-icon-pending.svg deleted file mode 100644 index 5f9aa4256..000000000 --- a/assets/svg/light/tx-exchange-icon-pending.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/assets/svg/light/tx-exchange-icon.svg b/assets/svg/light/tx-exchange-icon.svg deleted file mode 100644 index fcd3ef9dc..000000000 --- a/assets/svg/light/tx-exchange-icon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/assets/svg/light/tx-icon-receive-failed.svg b/assets/svg/light/tx-icon-receive-failed.svg deleted file mode 100644 index 189bd15c9..000000000 --- a/assets/svg/light/tx-icon-receive-failed.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/assets/svg/light/tx-icon-receive-pending.svg b/assets/svg/light/tx-icon-receive-pending.svg deleted file mode 100644 index 64ea8da3d..000000000 --- a/assets/svg/light/tx-icon-receive-pending.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/assets/svg/light/tx-icon-receive.svg b/assets/svg/light/tx-icon-receive.svg deleted file mode 100644 index 1076d8d57..000000000 --- a/assets/svg/light/tx-icon-receive.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/assets/svg/light/tx-icon-send-failed.svg b/assets/svg/light/tx-icon-send-failed.svg deleted file mode 100644 index 9751b61e8..000000000 --- a/assets/svg/light/tx-icon-send-failed.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/assets/svg/light/tx-icon-send-pending.svg b/assets/svg/light/tx-icon-send-pending.svg deleted file mode 100644 index e4ec777e3..000000000 --- a/assets/svg/light/tx-icon-send-pending.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/assets/svg/light/tx-icon-send.svg b/assets/svg/light/tx-icon-send.svg deleted file mode 100644 index ee32aa6b4..000000000 --- a/assets/svg/light/tx-icon-send.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/assets/svg/list-ul.svg b/assets/svg/list-ul.svg new file mode 100644 index 000000000..f5cef4eae --- /dev/null +++ b/assets/svg/list-ul.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/ocean-breeze-theme.svg b/assets/svg/ocean-breeze-theme.svg deleted file mode 100644 index 0deb96ec8..000000000 --- a/assets/svg/ocean-breeze-theme.svg +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/svg/oceanBreeze/bell-new.svg b/assets/svg/oceanBreeze/bell-new.svg deleted file mode 100644 index 8cef32715..000000000 --- a/assets/svg/oceanBreeze/bell-new.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/assets/svg/oceanBreeze/bg.svg b/assets/svg/oceanBreeze/bg.svg deleted file mode 100644 index 35fbda281..000000000 --- a/assets/svg/oceanBreeze/bg.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/assets/svg/oceanBreeze/buy-coins-icon.svg b/assets/svg/oceanBreeze/buy-coins-icon.svg deleted file mode 100644 index d9613bccb..000000000 --- a/assets/svg/oceanBreeze/buy-coins-icon.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/assets/svg/oceanBreeze/exchange-2.svg b/assets/svg/oceanBreeze/exchange-2.svg deleted file mode 100644 index 7baeaf87f..000000000 --- a/assets/svg/oceanBreeze/exchange-2.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/assets/svg/oceanBreeze/stack-icon1.svg b/assets/svg/oceanBreeze/stack-icon1.svg deleted file mode 100644 index f316012d7..000000000 --- a/assets/svg/oceanBreeze/stack-icon1.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/assets/svg/oceanBreeze/tx-exchange-icon-failed.svg b/assets/svg/oceanBreeze/tx-exchange-icon-failed.svg deleted file mode 100644 index a54836bba..000000000 --- a/assets/svg/oceanBreeze/tx-exchange-icon-failed.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/assets/svg/oceanBreeze/tx-exchange-icon-pending.svg b/assets/svg/oceanBreeze/tx-exchange-icon-pending.svg deleted file mode 100644 index 5f9aa4256..000000000 --- a/assets/svg/oceanBreeze/tx-exchange-icon-pending.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/assets/svg/oceanBreeze/tx-exchange-icon.svg b/assets/svg/oceanBreeze/tx-exchange-icon.svg deleted file mode 100644 index fcd3ef9dc..000000000 --- a/assets/svg/oceanBreeze/tx-exchange-icon.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/assets/svg/oceanBreeze/tx-icon-receive-failed.svg b/assets/svg/oceanBreeze/tx-icon-receive-failed.svg deleted file mode 100644 index 189bd15c9..000000000 --- a/assets/svg/oceanBreeze/tx-icon-receive-failed.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/assets/svg/oceanBreeze/tx-icon-receive-pending.svg b/assets/svg/oceanBreeze/tx-icon-receive-pending.svg deleted file mode 100644 index 64ea8da3d..000000000 --- a/assets/svg/oceanBreeze/tx-icon-receive-pending.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/assets/svg/oceanBreeze/tx-icon-receive.svg b/assets/svg/oceanBreeze/tx-icon-receive.svg deleted file mode 100644 index 1076d8d57..000000000 --- a/assets/svg/oceanBreeze/tx-icon-receive.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/assets/svg/oceanBreeze/tx-icon-send-failed.svg b/assets/svg/oceanBreeze/tx-icon-send-failed.svg deleted file mode 100644 index 9751b61e8..000000000 --- a/assets/svg/oceanBreeze/tx-icon-send-failed.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/assets/svg/oceanBreeze/tx-icon-send-pending.svg b/assets/svg/oceanBreeze/tx-icon-send-pending.svg deleted file mode 100644 index e4ec777e3..000000000 --- a/assets/svg/oceanBreeze/tx-icon-send-pending.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/assets/svg/oceanBreeze/tx-icon-send.svg b/assets/svg/oceanBreeze/tx-icon-send.svg deleted file mode 100644 index ee32aa6b4..000000000 --- a/assets/svg/oceanBreeze/tx-icon-send.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/assets/svg/persona-easy-1.svg b/assets/svg/persona-easy-1.svg deleted file mode 100644 index 1e15b8dab..000000000 --- a/assets/svg/persona-easy-1.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/svg/persona-incognito-1.svg b/assets/svg/persona-incognito-1.svg deleted file mode 100644 index c31c285b1..000000000 --- a/assets/svg/persona-incognito-1.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/assets/svg/robot-head.svg b/assets/svg/robot-head.svg new file mode 100644 index 000000000..b6810c227 --- /dev/null +++ b/assets/svg/robot-head.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/rotate-exclamation.svg b/assets/svg/rotate-exclamation.svg deleted file mode 100644 index 6b6064125..000000000 --- a/assets/svg/rotate-exclamation.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/assets/svg/tokens.svg b/assets/svg/tokens.svg new file mode 100644 index 000000000..be52b614c --- /dev/null +++ b/assets/svg/tokens.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/assets/svg/trocador_rating_a.svg b/assets/svg/trocador_rating_a.svg new file mode 100644 index 000000000..1e75af73b --- /dev/null +++ b/assets/svg/trocador_rating_a.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/svg/trocador_rating_b.svg b/assets/svg/trocador_rating_b.svg new file mode 100644 index 000000000..5d678305a --- /dev/null +++ b/assets/svg/trocador_rating_b.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/svg/trocador_rating_c.svg b/assets/svg/trocador_rating_c.svg new file mode 100644 index 000000000..87ecf6b24 --- /dev/null +++ b/assets/svg/trocador_rating_c.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/svg/trocador_rating_d.svg b/assets/svg/trocador_rating_d.svg new file mode 100644 index 000000000..8973c7e65 --- /dev/null +++ b/assets/svg/trocador_rating_d.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/svg/unclaimed.svg b/assets/svg/unclaimed.svg new file mode 100644 index 000000000..a6ff2213d --- /dev/null +++ b/assets/svg/unclaimed.svg @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/svg/user-minus.svg b/assets/svg/user-minus.svg new file mode 100644 index 000000000..a454f5d00 --- /dev/null +++ b/assets/svg/user-minus.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/user-plus.svg b/assets/svg/user-plus.svg new file mode 100644 index 000000000..10795f73c --- /dev/null +++ b/assets/svg/user-plus.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/svg/whirlpool.svg b/assets/svg/whirlpool.svg new file mode 100644 index 000000000..cf075308f --- /dev/null +++ b/assets/svg/whirlpool.svg @@ -0,0 +1,3 @@ + + + diff --git a/crypto_plugins/flutter_libepiccash b/crypto_plugins/flutter_libepiccash index 0309140a9..594ab89bc 160000 --- a/crypto_plugins/flutter_libepiccash +++ b/crypto_plugins/flutter_libepiccash @@ -1 +1 @@ -Subproject commit 0309140a95a51388df0effcc39ff0a25b2752b29 +Subproject commit 594ab89bc665a15a810ba7476ed2ad255fa8b5ac diff --git a/crypto_plugins/flutter_liblelantus b/crypto_plugins/flutter_liblelantus index 6864d7c0d..b3fac32f5 160000 --- a/crypto_plugins/flutter_liblelantus +++ b/crypto_plugins/flutter_liblelantus @@ -1 +1 @@ -Subproject commit 6864d7c0d4fa68c371e3f0c067afd50b0d59cc9b +Subproject commit b3fac32f57d7ef97da8641463d1a852f41660f9b diff --git a/crypto_plugins/flutter_libmonero b/crypto_plugins/flutter_libmonero index 061eda1d9..81659ce57 160000 --- a/crypto_plugins/flutter_libmonero +++ b/crypto_plugins/flutter_libmonero @@ -1 +1 @@ -Subproject commit 061eda1d9e93f8fc2fcdc3c6b970815350ec00dc +Subproject commit 81659ce57952c5ab54ffe6bacfbf43da159fff3e diff --git a/dockerfile.linux b/dockerfile.linux index 0853741d9..4a3867008 100644 --- a/dockerfile.linux +++ b/dockerfile.linux @@ -2,10 +2,10 @@ FROM ubuntu:20.04 as base COPY . /stack_wallet WORKDIR /stack_wallet/scripts/linux RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y git=1:2.25.1-1ubuntu3.6 make=4.2.1-1.2 curl=7.68.0-1ubuntu2.14 cargo=0.62.0ubuntu0libgit2-0ubuntu0.20.04.1 \ - file=1:5.38-4 ca-certificates=20211016~20.04.1 cmake=3.16.3-1ubuntu1.20.04.1 cmake-data=3.16.3-1ubuntu1.20.04.1 g++=4:9.3.0-1ubuntu2 libgmp-dev=2:6.2.0+dfsg-4ubuntu0.1 libssl-dev=1.1.1f-1ubuntu2.16 libclang-dev=1:10.0-50~exp1 \ - unzip=6.0-25ubuntu1.1 python3=3.8.2-0ubuntu2 pkg-config=0.29.1-0ubuntu4 libglib2.0-dev=2.64.6-1~ubuntu20.04.4 libgcrypt20-dev=1.8.5-5ubuntu1.1 gettext-base=0.19.8.1-10build1 libgirepository1.0-dev=1.64.1-1~ubuntu20.04.1 \ - valac=0.48.6-0ubuntu1 xsltproc=1.1.34-4ubuntu0.20.04.1 docbook-xsl=1.79.1+dfsg-2 python3-pip=20.0.2-5ubuntu1.6 ninja-build=1.10.0-1build1 clang=1:10.0-50~exp1 libgtk-3-dev=3.24.20-0ubuntu1.1 \ - libunbound-dev=1.9.4-2ubuntu1.4 libzmq3-dev=4.3.2-2ubuntu1 libtool=2.4.6-14 autoconf=2.69-11.1 automake=1:1.16.1-4ubuntu6 bison=2:3.5.1+dfsg-1 \ + file=1:5.38-4 ca-certificates=20211016ubuntu0.20.04.1 cmake=3.16.3-1ubuntu1.20.04.1 cmake-data=3.16.3-1ubuntu1.20.04.1 g++=4:9.3.0-1ubuntu2 libgmp-dev=2:6.2.0+dfsg-4ubuntu0.1 libssl-dev=1.1.1f-1ubuntu2.16 \ + libclang-dev=1:10.0-50~exp1 unzip=6.0-25ubuntu1.1 python3=3.8.2-0ubuntu2 pkg-config=0.29.1-0ubuntu4 libglib2.0-dev=2.64.6-1~ubuntu20.04.4 libgcrypt20-dev=1.8.5-5ubuntu1.1 gettext-base=0.19.8.1-10build1 \ + libgirepository1.0-dev=1.64.1-1~ubuntu20.04.1 valac=0.48.6-0ubuntu1 xsltproc=1.1.34-4ubuntu0.20.04.1 docbook-xsl=1.79.1+dfsg-2 python3-pip=20.0.2-5ubuntu1.6 ninja-build=1.10.0-1build1 clang=1:10.0-50~exp1 \ + libgtk-3-dev=3.24.20-0ubuntu1.1 libunbound-dev=1.9.4-2ubuntu1.4 libzmq3-dev=4.3.2-2ubuntu1 libtool=2.4.6-14 autoconf=2.69-11.1 automake=1:1.16.1-4ubuntu6 bison=2:3.5.1+dfsg-1 \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* \ && pip3 install --upgrade meson==0.64.1 markdown==3.4.1 markupsafe==2.1.1 jinja2==3.1.2 pygments==2.13.0 toml==0.10.2 typogrify==2.0.7 tomli==2.0.1 && cd .. && ./prebuild.sh && cd linux && ./build_all.sh diff --git a/docs/building.md b/docs/building.md new file mode 100644 index 000000000..3f5f4415a --- /dev/null +++ b/docs/building.md @@ -0,0 +1,137 @@ +# Building + +Here you will find instructions on how to install the necessary tools for building and running the app. + +## Prerequisites + +- The only OS supported for building is Ubuntu 20.04. Advanced users may also be able to build on other Debian-based distributions like Linux Mint. +- Android setup ([Android Studio](https://developer.android.com/studio) and subsequent dependencies) +- 100 GB of storage + +Install Android Studio following the instructions below before proceeding, then the following prerequisites can be installed with the setup script [`scripts/setup.sh`](./../scripts/setup.sh) or manually as described below: + +- Flutter 3.7.12 [(install manually or with git, do not install with snap)](https://docs.flutter.dev/get-started/install) +- Dart SDK Requirement (>=2.19.0, up until <3.0.0) (normally included with a flutter install) + +### Android Studio +Android Studio is the recommended IDE for development, not just for launching on Android devices and emulators but also for Linux desktop development. + +Follow instructions here [https://developer.android.com/studio/install#linux](https://developer.android.com/studio/install#linux) or install via snap: +``` +# setup android studio +sudo apt install -y openjdk-11-jdk +sudo snap install android-studio --classic +``` + +Use Tools > SDK Manager to install: + - SDK Tools > Android SDK (API 30) + - SDK Tools > NDK + - SDK Tools > Android SDK command line tools + - SDK Tools > CMake + +Then in File > Settings > Plugins, install the Flutter plugin and restart the IDE. In File > Settings > Languages & Frameworks > Flutter > Editor, enable auto format on save to match the project's code style. If you have problems with the Dart SDK, make sure to run `flutter` in a terminal to download it (use `source ~/.bashrc` to update your environment variables if you're still using the same terminal from which you ran `setup.sh`) + +Make a Pixel 4 (API 30) x86_64 emulator with 2GB of storage space for emulation + +### Scripted setup + +[`scripts/setup.sh`](./../scripts/setup.sh) is provided as a tool to set up installation for building: download the script and run it anywhere. This script should skip the entire [Manual setup](#manual-setup) section below and prepare you for [running](#running). It will set up the stack_wallet repository in `~/projects/stack_wallet` and build it there. + +### Manual setup + +> If you used the `setup.sh` script, skip to [running](#running) + +Install basic dependencies +``` +sudo apt-get install libssl-dev curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake libgit2-dev clang libncurses5-dev libncursesw5-dev zlib1g-dev llvm python3-distutils +``` + +The following *may* be needed for Android studio: +``` +sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1 libbz2-1.0:i386 +``` + +Install [Rust](https://www.rust-lang.org/tools/install) with command: +``` +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +source ~/.bashrc +rustup install 1.67.1 +rustup default 1.67.1 +``` + +Install the additional components for Rust: +``` +cargo install cargo-ndk --version 2.12.7 +``` +Android specific dependencies: +``` +sudo apt-get install libc6-dev-i386 +rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android +``` +Linux desktop specific dependencies: +``` +sudo apt-get install clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev meson python3-pip libgirepository1.0-dev valac xsltproc docbook-xsl +pip3 install --upgrade meson==0.64.1 markdown==3.4.1 markupsafe==2.1.1 jinja2==3.1.2 pygments==2.13.0 toml==0.10.2 typogrify==2.0.7 tomli==2.0.1 +``` + +After installing the prerequisites listed above, download the code and init the submodules +``` +git clone https://github.com/cypherstack/stack_wallet.git +cd stack_wallet +git submodule update --init --recursive + +``` + +Run prebuild script + +``` +cd scripts +./prebuild.sh +// when finished go back to the root directory +cd .. +``` + +Remove pre-installed system libraries for the following packages built by cryptography plugins in the crypto_plugins folder: `boost iconv libjson-dev libsecret openssl sodium unbound zmq`. You can use +``` +sudo apt list --installed | grep boost +``` +for example to find which pre-installed packages you may need to remove with `sudo apt remove`. Be careful, as some packages (especially boost) are linked to GNOME (GUI) packages: when in doubt, remove `-dev` packages first like with +``` +sudo apt-get remove '^libboost.*-dev.*' +``` + + +Building plugins for Android +> Warning: This will take a long time, please be patient +``` +cd scripts/android/ +./build_all.sh +// when finished go back to the root directory +cd ../.. +``` + +Building plugins for Linux + +``` +cd scripts/linux/ +./build_all.sh +// when finished go back to the root directory +cd ../.. +``` + +## Running +### Android +Plug in your android device or use the emulator available via Android Studio and then run the following commands: +``` +flutter pub get +flutter run android +``` + +Note on Emulators: Only x86_64 emulators are supported, x86 emulators will not work + +### Linux +Plug in your android device or use the emulator available via Android Studio and then run the following commands: +``` +flutter pub get Linux +flutter run linux +``` diff --git a/docs/roadmap.md b/docs/roadmap.md new file mode 100644 index 000000000..384f6f360 --- /dev/null +++ b/docs/roadmap.md @@ -0,0 +1,5 @@ +# Roadmap + +This document describes the roadmap for the project. It is a living document that will be updated as the project evolves. + +- [ ] Fill in the roadmap \ No newline at end of file diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 3158eba31..7b1095736 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 10.0 + 15.0 diff --git a/ios/Podfile b/ios/Podfile index 9411102b1..f17bddc91 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -platform :ios, '10.0' +platform :ios, '15.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/ios/Podfile.lock b/ios/Podfile.lock index bb92ea24c..76dc00433 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -47,6 +47,7 @@ PODS: - cw_wownero/Boost (= 0.0.2) - cw_wownero/OpenSSL (= 0.0.2) - cw_wownero/Sodium (= 0.0.2) + - cw_wownero/Unbound (= 0.0.2) - cw_wownero/Wownero (= 0.0.2) - Flutter - cw_wownero/Boost (0.0.2): @@ -58,9 +59,14 @@ PODS: - cw_wownero/Sodium (0.0.2): - cw_shared_external - Flutter + - cw_wownero/Unbound (0.0.2): + - cw_shared_external + - Flutter - cw_wownero/Wownero (0.0.2): - cw_shared_external - Flutter + - device_info_plus (0.0.1): + - Flutter - devicelocale (0.0.1): - Flutter - DKImagePickerController/Core (4.3.4): @@ -119,8 +125,9 @@ PODS: - MTBBarcodeScanner (5.0.11) - package_info_plus (0.4.5): - Flutter - - path_provider_ios (0.0.1): + - path_provider_foundation (0.0.1): - Flutter + - FlutterMacOS - permission_handler_apple (9.0.4): - Flutter - ReachabilitySwift (5.0.0) @@ -129,8 +136,9 @@ PODS: - SDWebImage/Core (5.13.2) - share_plus (0.0.1): - Flutter - - shared_preferences_ios (0.0.1): + - shared_preferences_foundation (0.0.1): - Flutter + - FlutterMacOS - stack_wallet_backup (0.0.1): - Flutter - SwiftProtobuf (1.19.0) @@ -147,6 +155,7 @@ DEPENDENCIES: - cw_monero (from `.symlinks/plugins/cw_monero/ios`) - cw_shared_external (from `.symlinks/plugins/cw_shared_external/ios`) - cw_wownero (from `.symlinks/plugins/cw_wownero/ios`) + - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - devicelocale (from `.symlinks/plugins/devicelocale/ios`) - file_picker (from `.symlinks/plugins/file_picker/ios`) - Flutter (from `Flutter`) @@ -160,10 +169,10 @@ DEPENDENCIES: - lelantus (from `.symlinks/plugins/lelantus/ios`) - local_auth (from `.symlinks/plugins/local_auth/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/ios`) - stack_wallet_backup (from `.symlinks/plugins/stack_wallet_backup/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - wakelock (from `.symlinks/plugins/wakelock/ios`) @@ -191,6 +200,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/cw_shared_external/ios" cw_wownero: :path: ".symlinks/plugins/cw_wownero/ios" + device_info_plus: + :path: ".symlinks/plugins/device_info_plus/ios" devicelocale: :path: ".symlinks/plugins/devicelocale/ios" file_picker: @@ -217,14 +228,14 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/local_auth/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" - path_provider_ios: - :path: ".symlinks/plugins/path_provider_ios/ios" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/ios" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" share_plus: :path: ".symlinks/plugins/share_plus/ios" - shared_preferences_ios: - :path: ".symlinks/plugins/shared_preferences_ios/ios" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/ios" stack_wallet_backup: :path: ".symlinks/plugins/stack_wallet_backup/ios" url_launcher_ios: @@ -238,11 +249,12 @@ SPEC CHECKSUMS: connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e cw_monero: 9816991daff0e3ad0a8be140e31933b5526babd4 cw_shared_external: 2972d872b8917603478117c9957dfca611845a92 - cw_wownero: 08e5713fe311a3be95efd7f3c1bf9d47d9cfafde + cw_wownero: ac53899fa5c6ff46b3fb490aa3b7ca36301fa832 + device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed devicelocale: b22617f40038496deffba44747101255cee005b0 DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 - file_picker: 817ab1d8cd2da9d2da412a417162deee3500fc95 + file_picker: ce3938a0df3cc1ef404671531facef740d03f920 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 flutter_libepiccash: 36241aa7d3126f6521529985ccb3dc5eaf7bb317 flutter_libmonero: da68a616b73dd0374a8419c684fa6b6df2c44ffe @@ -250,23 +262,23 @@ SPEC CHECKSUMS: flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5 - isar_flutter_libs: bfb66f35a1fa9db9ec96b93539a03329ce147738 - lelantus: 97ab4ecc648423278f807e499b3a717dea9268f8 + isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073 + lelantus: 417f0221260013dfc052cae9cf4b741b6479edba local_auth: 1740f55d7af0a2e2a8684ce225fe79d8931e808c MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e - path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 + path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852 permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866 share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68 - shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad + shared_preferences_foundation: 297b3ebca31b34ec92be11acd7fb0ba932c822ca stack_wallet_backup: 5b8563aba5d8ffbf2ce1944331ff7294a0ec7c03 SwiftProtobuf: 6ef3f0e422ef90d6605ca20b21a94f6c1324d6b3 SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780 - url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de + url_launcher_ios: fb12c43172927bb5cf75aeebd073f883801f1993 wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f -PODFILE CHECKSUM: fe0e1ee7f3d1f7d00b11b474b62dd62134535aea +PODFILE CHECKSUM: 57c8aed26fba39d3ec9424816221f294a07c58eb COCOAPODS: 1.11.3 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 2e5d4bac0..4f2f20dc9 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -17,7 +17,7 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - C64D72F92051288D5CB5033D /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 08E84808BC00DEE3447AF47E /* Pods_Runner.framework */; }; + B49D91439948369648AB0603 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51604430FD0FD1FA5C4767A0 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -35,11 +35,12 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 08E84808BC00DEE3447AF47E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 15168938F13F6113519C963B /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 1B82E12F9C5D326CBB2ADF7E /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 689E9A74C0452C94E3479BEA /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 51604430FD0FD1FA5C4767A0 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; @@ -53,7 +54,6 @@ 7E8A4F15288D645200F18717 /* flutter_libepiccash.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = flutter_libepiccash.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7E8A4F19288D721300F18717 /* flutter_libepiccash.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = flutter_libepiccash.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7E8A4F1D288D72D100F18717 /* flutter_libepiccash.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = flutter_libepiccash.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 7F0C23A93667326FB8E95604 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -61,7 +61,7 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - F69E2FD9CB433963DAA9B09E /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + E6F536731AC506735EB76340 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -72,7 +72,7 @@ 7E1603B5288D73EA002F7A6F /* libepic_cash_wallet.a in Frameworks */, 7E729AE82893C1B1009BBD65 /* flutter_libepiccash.framework in Frameworks */, 7E569F992798D47200056D51 /* mobileliblelantus.framework in Frameworks */, - C64D72F92051288D5CB5033D /* Pods_Runner.framework in Frameworks */, + B49D91439948369648AB0603 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -82,9 +82,9 @@ 4D9BC5822E8E05B80CC958A0 /* Pods */ = { isa = PBXGroup; children = ( - 7F0C23A93667326FB8E95604 /* Pods-Runner.debug.xcconfig */, - F69E2FD9CB433963DAA9B09E /* Pods-Runner.release.xcconfig */, - 689E9A74C0452C94E3479BEA /* Pods-Runner.profile.xcconfig */, + 1B82E12F9C5D326CBB2ADF7E /* Pods-Runner.debug.xcconfig */, + E6F536731AC506735EB76340 /* Pods-Runner.release.xcconfig */, + 15168938F13F6113519C963B /* Pods-Runner.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -102,7 +102,7 @@ 7E8A4F06288D5A9300F18717 /* libepic_cash_wallet.a */, 7E8A4F02288D57DE00F18717 /* flutter_libepiccash.framework */, 7E569F982798D47200056D51 /* mobileliblelantus.framework */, - 08E84808BC00DEE3447AF47E /* Pods_Runner.framework */, + 51604430FD0FD1FA5C4767A0 /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; @@ -167,14 +167,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - D7C3965259DE109272702285 /* [CP] Check Pods Manifest.lock */, + B108E043921CDEDDCB9E1E86 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - 30277110A528C730AF372175 /* [CP] Embed Pods Frameworks */, + FD1CA371131604E6658D4146 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -235,7 +235,59 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 30277110A528C730AF372175 /* [CP] Embed Pods Frameworks */ = { + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; + }; + B108E043921CDEDDCB9E1E86 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + FD1CA371131604E6658D4146 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -267,9 +319,9 @@ "${BUILT_PRODUCTS_DIR}/lelantus/lelantus.framework", "${BUILT_PRODUCTS_DIR}/local_auth/local_auth.framework", "${BUILT_PRODUCTS_DIR}/package_info_plus/package_info_plus.framework", - "${BUILT_PRODUCTS_DIR}/path_provider_ios/path_provider_ios.framework", + "${BUILT_PRODUCTS_DIR}/path_provider_foundation/path_provider_foundation.framework", "${BUILT_PRODUCTS_DIR}/share_plus/share_plus.framework", - "${BUILT_PRODUCTS_DIR}/shared_preferences_ios/shared_preferences_ios.framework", + "${BUILT_PRODUCTS_DIR}/shared_preferences_foundation/shared_preferences_foundation.framework", "${BUILT_PRODUCTS_DIR}/stack_wallet_backup/stack_wallet_backup.framework", "${BUILT_PRODUCTS_DIR}/url_launcher_ios/url_launcher_ios.framework", "${BUILT_PRODUCTS_DIR}/wakelock/wakelock.framework", @@ -301,9 +353,9 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/lelantus.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/local_auth.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info_plus.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_ios.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_foundation.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share_plus.framework", - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_ios.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_foundation.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/stack_wallet_backup.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_ios.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/wakelock.framework", @@ -313,56 +365,6 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Thin Binary"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n"; - }; - 9740EEB61CF901F6004384FC /* Run Script */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "Run Script"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; - }; - D7C3965259DE109272702285 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -439,7 +441,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -456,7 +458,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 83; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 4DQKUWSG6C; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -510,7 +512,7 @@ "$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**", "$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs", ); - MARKETING_VERSION = 1.5.11; + MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)"; ONLY_ACTIVE_ARCH = NO; PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -574,7 +576,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -624,7 +626,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -643,7 +645,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 83; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 4DQKUWSG6C; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -697,7 +699,7 @@ "$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**", "$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs", ); - MARKETING_VERSION = 1.5.11; + MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)"; ONLY_ACTIVE_ARCH = NO; PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -722,7 +724,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 83; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; DEVELOPMENT_TEAM = 4DQKUWSG6C; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -776,7 +778,7 @@ "$(PROJECT_DIR)/../crypto_plugins/flutter_libmonero/cw_shared_external/ios/External/ios/**", "$(PROJECT_DIR)/../crypto_plugins/flutter_libepiccash/ios/libs", ); - MARKETING_VERSION = 1.5.11; + MARKETING_VERSION = "$(FLUTTER_BUILD_NAME)"; ONLY_ACTIVE_ARCH = NO; PRODUCT_BUNDLE_IDENTIFIER = com.cypherstack.stackwallet; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 4ef19051a..6b8be59a8 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,6 +2,8 @@ + ITSAppUsesNonExemptEncryption + CADisableMinimumFrameDurationOnPhone CFBundleDevelopmentRegion @@ -29,7 +31,7 @@ LSSupportsOpeningDocumentsInPlace NSCameraUsageDescription - App requires access to the Camera to scan QR codes from other people's installed app in order to coordinate sending Firo. + App requires access to the Camera to scan QR codes from other people's installed app in order to coordinate sending. NSFaceIDUsageDescription This app requires Face ID permissions so that the user can securely lock their wallet if their device uses Face ID. It will be useful feature for all users, and especially those prone to forget their login pin on iPhones where Touch ID is not available. NSPhotoLibraryUsageDescription @@ -57,5 +59,7 @@ UIViewControllerBasedStatusBarAppearance + UIApplicationSupportsIndirectInputEvents + diff --git a/lib/hive/db.dart b/lib/db/hive/db.dart similarity index 95% rename from lib/hive/db.dart rename to lib/db/hive/db.dart index 557f4ef30..5700f985d 100644 --- a/lib/hive/db.dart +++ b/lib/db/hive/db.dart @@ -31,8 +31,9 @@ class DB { static const String boxNameWalletsToDeleteOnStart = "walletsToDeleteOnStart"; static const String boxNamePriceCache = "priceAPIPrice24hCache"; static const String boxNameDBInfo = "dbInfo"; - static const String boxNameTheme = "theme"; + // static const String boxNameTheme = "theme"; static const String boxNameDesktopData = "desktopData"; + static const String boxNameBuys = "buysBox"; String boxNameTxCache({required Coin coin}) => "${coin.name}_txCache"; String boxNameSetCache({required Coin coin}) => @@ -40,7 +41,6 @@ class DB { String boxNameUsedSerialsCache({required Coin coin}) => "${coin.name}_usedSerialsCache"; - Box? _boxAddressBook; Box? _boxDebugInfo; Box? _boxNodeModels; Box? _boxPrimaryNodes; @@ -99,7 +99,6 @@ class DB { _boxPrefs = await Hive.openBox(boxNamePrefs); } - _boxAddressBook = await Hive.openBox(boxNameAddressBook); _boxDebugInfo = await Hive.openBox(boxNameDebugInfo); if (Hive.isBoxOpen(boxNameNodeModels)) { @@ -245,3 +244,12 @@ class DB { Future deleteBoxFromDisk({required String boxName}) async => await mutex.protect(() async => await Hive.deleteBoxFromDisk(boxName)); } + +abstract class DBKeys { + static const String cachedBalance = "cachedBalance"; + static const String cachedBalanceSecondary = "cachedBalanceSecondary"; + static const String isFavorite = "isFavorite"; + static const String id = "id"; + static const String storedChainHeight = "storedChainHeight"; + static const String ethTokenContracts = "ethTokenContracts"; +} diff --git a/lib/db/isar/main_db.dart b/lib/db/isar/main_db.dart new file mode 100644 index 000000000..b9841de02 --- /dev/null +++ b/lib/db/isar/main_db.dart @@ -0,0 +1,485 @@ +import 'package:decimal/decimal.dart'; +import 'package:flutter_native_splash/cli_commands.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/exceptions/main_db/main_db_exception.dart'; +import 'package:stackwallet/models/isar/models/block_explorer.dart'; +import 'package:stackwallet/models/isar/models/contact_entry.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/stack_file_system.dart'; +import 'package:tuple/tuple.dart'; + +part '../queries/queries.dart'; + +class MainDB { + MainDB._(); + static MainDB? _instance; + static MainDB get instance => _instance ??= MainDB._(); + + Isar? _isar; + + Isar get isar => _isar!; + + Future initMainDB({Isar? mock}) async { + if (mock != null) { + _isar = mock; + return true; + } + if (_isar != null && isar.isOpen) return false; + _isar = await Isar.open( + [ + TransactionSchema, + TransactionNoteSchema, + UTXOSchema, + AddressSchema, + AddressLabelSchema, + EthContractSchema, + TransactionBlockExplorerSchema, + StackThemeSchema, + ContactEntrySchema, + ], + directory: (await StackFileSystem.applicationIsarDirectory()).path, + // inspector: kDebugMode, + inspector: false, + name: "wallet_data", + maxSizeMiB: 512, + ); + return true; + } + + // contact entries + List getContactEntries(){ + return isar.contactEntrys.where().findAllSync(); + } + + Future deleteContactEntry({required String id}) { + try { + return isar.writeTxn(() async { + await isar.contactEntrys.deleteByCustomId(id); + return true; + }); + } catch (e) { + throw MainDBException("failed deleteContactEntry: $id", e); + } + } + + Future isContactEntryExists({required String id}) async { + return isar.contactEntrys + .where() + .customIdEqualTo(id) + .count() + .then((value) => value > 0); + } + + ContactEntry? getContactEntry({required String id}) { + return isar.contactEntrys.where().customIdEqualTo(id).findFirstSync(); + } + + Future putContactEntry({required ContactEntry contactEntry}) async { + try { + return await isar.writeTxn(() async { + await isar.contactEntrys.put(contactEntry); + return true; + }); + } catch (e) { + throw MainDBException("failed putContactEntry: $contactEntry", e); + } + } + + // tx block explorers + TransactionBlockExplorer? getTransactionBlockExplorer({required Coin coin}) { + return isar.transactionBlockExplorers + .where() + .tickerEqualTo(coin.ticker) + .findFirstSync(); + } + + Future putTransactionBlockExplorer( + TransactionBlockExplorer explorer) async { + try { + return await isar.writeTxn(() async { + return await isar.transactionBlockExplorers.put(explorer); + }); + } catch (e) { + throw MainDBException("failed putTransactionBlockExplorer: $explorer", e); + } + } + + // addresses + QueryBuilder getAddresses( + String walletId) => + isar.addresses.where().walletIdEqualTo(walletId); + + Future putAddress(Address address) async { + try { + return await isar.writeTxn(() async { + return await isar.addresses.put(address); + }); + } catch (e) { + throw MainDBException("failed putAddress: $address", e); + } + } + + Future> putAddresses(List
addresses) async { + try { + return await isar.writeTxn(() async { + return await isar.addresses.putAll(addresses); + }); + } catch (e) { + throw MainDBException("failed putAddresses: $addresses", e); + } + } + + Future> updateOrPutAddresses(List
addresses) async { + try { + List ids = []; + await isar.writeTxn(() async { + for (final address in addresses) { + final storedAddress = await isar.addresses + .getByValueWalletId(address.value, address.walletId); + + int id; + if (storedAddress == null) { + id = await isar.addresses.put(address); + } else { + address.id = storedAddress.id; + await storedAddress.transactions.load(); + final txns = storedAddress.transactions.toList(); + await isar.addresses.delete(storedAddress.id); + id = await isar.addresses.put(address); + address.transactions.addAll(txns); + await address.transactions.save(); + } + ids.add(id); + } + }); + return ids; + } catch (e) { + throw MainDBException("failed updateOrPutAddresses: $addresses", e); + } + } + + Future getAddress(String walletId, String address) async { + return isar.addresses.getByValueWalletId(address, walletId); + } + + Future updateAddress(Address oldAddress, Address newAddress) async { + try { + return await isar.writeTxn(() async { + newAddress.id = oldAddress.id; + await oldAddress.transactions.load(); + final txns = oldAddress.transactions.toList(); + await isar.addresses.delete(oldAddress.id); + final id = await isar.addresses.put(newAddress); + newAddress.transactions.addAll(txns); + await newAddress.transactions.save(); + return id; + }); + } catch (e) { + throw MainDBException( + "failed updateAddress: from=$oldAddress to=$newAddress", e); + } + } + + // transactions + QueryBuilder getTransactions( + String walletId) => + isar.transactions.where().walletIdEqualTo(walletId); + + Future putTransaction(Transaction transaction) async { + try { + return await isar.writeTxn(() async { + return await isar.transactions.put(transaction); + }); + } catch (e) { + throw MainDBException("failed putTransaction: $transaction", e); + } + } + + Future> putTransactions(List transactions) async { + try { + return await isar.writeTxn(() async { + return await isar.transactions.putAll(transactions); + }); + } catch (e) { + throw MainDBException("failed putTransactions: $transactions", e); + } + } + + Future getTransaction(String walletId, String txid) async { + return isar.transactions.getByTxidWalletId(txid, walletId); + } + + Stream watchTransaction({ + required Id id, + bool fireImmediately = false, + }) { + return isar.transactions.watchObject(id, fireImmediately: fireImmediately); + } + + // utxos + QueryBuilder getUTXOs(String walletId) => + isar.utxos.where().walletIdEqualTo(walletId); + + Future putUTXO(UTXO utxo) => isar.writeTxn(() async { + await isar.utxos.put(utxo); + }); + + Future putUTXOs(List utxos) => isar.writeTxn(() async { + await isar.utxos.putAll(utxos); + }); + + Future updateUTXOs(String walletId, List utxos) async { + await isar.writeTxn(() async { + final set = utxos.toSet(); + for (final utxo in utxos) { + // check if utxo exists in db and update accordingly + final storedUtxo = await isar.utxos + .where() + .txidWalletIdVoutEqualTo(utxo.txid, utxo.walletId, utxo.vout) + .findFirst(); + + if (storedUtxo != null) { + // update + set.remove(utxo); + set.add( + storedUtxo.copyWith( + value: utxo.value, + address: utxo.address, + blockTime: utxo.blockTime, + blockHeight: utxo.blockHeight, + blockHash: utxo.blockHash, + ), + ); + } + } + + await isar.utxos.where().walletIdEqualTo(walletId).deleteAll(); + await isar.utxos.putAll(set.toList()); + }); + } + + Stream watchUTXO({ + required Id id, + bool fireImmediately = false, + }) { + return isar.utxos.watchObject(id, fireImmediately: fireImmediately); + } + + // transaction notes + QueryBuilder + getTransactionNotes(String walletId) => + isar.transactionNotes.where().walletIdEqualTo(walletId); + + Future putTransactionNote(TransactionNote transactionNote) => + isar.writeTxn(() async { + await isar.transactionNotes.put(transactionNote); + }); + + Future putTransactionNotes(List transactionNotes) => + isar.writeTxn(() async { + await isar.transactionNotes.putAll(transactionNotes); + }); + + Future getTransactionNote( + String walletId, String txid) async { + return isar.transactionNotes.getByTxidWalletId( + txid, + walletId, + ); + } + + Stream watchTransactionNote({ + required Id id, + bool fireImmediately = false, + }) { + return isar.transactionNotes + .watchObject(id, fireImmediately: fireImmediately); + } + + // address labels + QueryBuilder getAddressLabels( + String walletId) => + isar.addressLabels.where().walletIdEqualTo(walletId); + + Future putAddressLabel(AddressLabel addressLabel) => + isar.writeTxn(() async { + return await isar.addressLabels.put(addressLabel); + }); + + int putAddressLabelSync(AddressLabel addressLabel) => isar.writeTxnSync(() { + return isar.addressLabels.putSync(addressLabel); + }); + + Future putAddressLabels(List addressLabels) => + isar.writeTxn(() async { + await isar.addressLabels.putAll(addressLabels); + }); + + Future getAddressLabel( + String walletId, String addressString) async { + return isar.addressLabels.getByAddressStringWalletId( + addressString, + walletId, + ); + } + + AddressLabel? getAddressLabelSync(String walletId, String addressString) { + return isar.addressLabels.getByAddressStringWalletIdSync( + addressString, + walletId, + ); + } + + Stream watchAddressLabel({ + required Id id, + bool fireImmediately = false, + }) { + return isar.addressLabels.watchObject(id, fireImmediately: fireImmediately); + } + + Future updateAddressLabel(AddressLabel addressLabel) async { + try { + return await isar.writeTxn(() async { + return await isar.addressLabels.put(addressLabel); + }); + } catch (e) { + throw MainDBException("failed updateAddressLabel", e); + } + } + + // + Future deleteWalletBlockchainData(String walletId) async { + final transactionCount = await getTransactions(walletId).count(); + final addressCount = await getAddresses(walletId).count(); + final utxoCount = await getUTXOs(walletId).count(); + + await isar.writeTxn(() async { + const paginateLimit = 50; + + // transactions + for (int i = 0; i < transactionCount; i += paginateLimit) { + final txnIds = await getTransactions(walletId) + .offset(i) + .limit(paginateLimit) + .idProperty() + .findAll(); + await isar.transactions.deleteAll(txnIds); + } + + // addresses + for (int i = 0; i < addressCount; i += paginateLimit) { + final addressIds = await getAddresses(walletId) + .offset(i) + .limit(paginateLimit) + .idProperty() + .findAll(); + await isar.addresses.deleteAll(addressIds); + } + + // utxos + for (int i = 0; i < utxoCount; i += paginateLimit) { + final utxoIds = await getUTXOs(walletId) + .offset(i) + .limit(paginateLimit) + .idProperty() + .findAll(); + await isar.utxos.deleteAll(utxoIds); + } + }); + } + + Future deleteAddressLabels(String walletId) async { + final addressLabelCount = await getAddressLabels(walletId).count(); + await isar.writeTxn(() async { + const paginateLimit = 50; + for (int i = 0; i < addressLabelCount; i += paginateLimit) { + final labelIds = await getAddressLabels(walletId) + .offset(i) + .limit(paginateLimit) + .idProperty() + .findAll(); + await isar.addressLabels.deleteAll(labelIds); + } + }); + } + + Future deleteTransactionNotes(String walletId) async { + final noteCount = await getTransactionNotes(walletId).count(); + await isar.writeTxn(() async { + const paginateLimit = 50; + for (int i = 0; i < noteCount; i += paginateLimit) { + final labelIds = await getTransactionNotes(walletId) + .offset(i) + .limit(paginateLimit) + .idProperty() + .findAll(); + await isar.transactionNotes.deleteAll(labelIds); + } + }); + } + + Future addNewTransactionData( + List> transactionsData, + String walletId, + ) async { + try { + await isar.writeTxn(() async { + for (final data in transactionsData) { + final tx = data.item1; + + final potentiallyUnconfirmedTx = await getTransaction( + walletId, + tx.txid, + ); + if (potentiallyUnconfirmedTx != null) { + // update use id to replace tx + tx.id = potentiallyUnconfirmedTx.id; + await isar.transactions.delete(potentiallyUnconfirmedTx.id); + } + // save transaction + await isar.transactions.put(tx); + + if (data.item2 != null) { + final address = await getAddress(walletId, data.item2!.value); + + // check if address exists in db and add if it does not + if (address == null) { + await isar.addresses.put(data.item2!); + } + + // link and save address + tx.address.value = address ?? data.item2!; + await tx.address.save(); + } + } + }); + } catch (e) { + throw MainDBException("failed addNewTransactionData", e); + } + } + + // ========== Ethereum ======================================================= + + // eth contracts + + QueryBuilder getEthContracts() => + isar.ethContracts.where(); + + Future getEthContract(String contractAddress) => + isar.ethContracts.where().addressEqualTo(contractAddress).findFirst(); + + EthContract? getEthContractSync(String contractAddress) => + isar.ethContracts.where().addressEqualTo(contractAddress).findFirstSync(); + + Future putEthContract(EthContract contract) => isar.writeTxn(() async { + return await isar.ethContracts.put(contract); + }); + + Future putEthContracts(List contracts) => + isar.writeTxn(() async { + await isar.ethContracts.putAll(contracts); + }); +} diff --git a/lib/db/queries/queries.dart b/lib/db/queries/queries.dart new file mode 100644 index 000000000..65fc6d26e --- /dev/null +++ b/lib/db/queries/queries.dart @@ -0,0 +1,182 @@ +part of 'package:stackwallet/db/isar/main_db.dart'; + +enum CCFilter { + all, + available, + frozen; + + @override + String toString() { + if (this == all) { + return "Show $name outputs"; + } + + return "${name.capitalize()} outputs"; + } +} + +enum CCSortDescriptor { + age, + address, + value; + + @override + String toString() { + return name.capitalize(); + } +} + +extension MainDBQueries on MainDB { + List queryUTXOsSync({ + required String walletId, + required CCFilter filter, + required CCSortDescriptor sort, + required String searchTerm, + required Coin coin, + }) { + var preSort = getUTXOs(walletId).filter().group((q) { + final qq = q.group( + (q) => q.usedIsNull().or().usedEqualTo(false), + ); + switch (filter) { + case CCFilter.frozen: + return qq.and().isBlockedEqualTo(true); + case CCFilter.available: + return qq.and().isBlockedEqualTo(false); + case CCFilter.all: + return qq; + } + }); + + if (searchTerm.isNotEmpty) { + preSort = preSort.and().group( + (q) { + var qq = q.addressContains(searchTerm, caseSensitive: false); + + qq = qq.or().nameContains(searchTerm, caseSensitive: false); + qq = qq.or().group( + (q) => q + .isBlockedEqualTo(true) + .and() + .blockedReasonContains(searchTerm, caseSensitive: false), + ); + + qq = qq.or().txidContains(searchTerm, caseSensitive: false); + qq = qq.or().blockHashContains(searchTerm, caseSensitive: false); + + final maybeDecimal = Decimal.tryParse(searchTerm); + if (maybeDecimal != null) { + qq = qq.or().valueEqualTo( + Amount.fromDecimal( + maybeDecimal, + fractionDigits: coin.decimals, + ).raw.toInt(), + ); + } + + final maybeInt = int.tryParse(searchTerm); + if (maybeInt != null) { + qq = qq.or().valueEqualTo(maybeInt); + } + + return qq; + }, + ); + } + + final List ids; + switch (sort) { + case CCSortDescriptor.age: + ids = preSort.sortByBlockHeight().idProperty().findAllSync(); + break; + case CCSortDescriptor.address: + ids = preSort.sortByAddress().idProperty().findAllSync(); + break; + case CCSortDescriptor.value: + ids = preSort.sortByValueDesc().idProperty().findAllSync(); + break; + } + return ids; + } + + Map> queryUTXOsGroupedByAddressSync({ + required String walletId, + required CCFilter filter, + required CCSortDescriptor sort, + required String searchTerm, + required Coin coin, + }) { + var preSort = getUTXOs(walletId).filter().group((q) { + final qq = q.group( + (q) => q.usedIsNull().or().usedEqualTo(false), + ); + switch (filter) { + case CCFilter.frozen: + return qq.and().isBlockedEqualTo(true); + case CCFilter.available: + return qq.and().isBlockedEqualTo(false); + case CCFilter.all: + return qq; + } + }); + + if (searchTerm.isNotEmpty) { + preSort = preSort.and().group( + (q) { + var qq = q.addressContains(searchTerm, caseSensitive: false); + + qq = qq.or().nameContains(searchTerm, caseSensitive: false); + qq = qq.or().group( + (q) => q + .isBlockedEqualTo(true) + .and() + .blockedReasonContains(searchTerm, caseSensitive: false), + ); + + qq = qq.or().txidContains(searchTerm, caseSensitive: false); + qq = qq.or().blockHashContains(searchTerm, caseSensitive: false); + + final maybeDecimal = Decimal.tryParse(searchTerm); + if (maybeDecimal != null) { + qq = qq.or().valueEqualTo( + Amount.fromDecimal( + maybeDecimal, + fractionDigits: coin.decimals, + ).raw.toInt(), + ); + } + + final maybeInt = int.tryParse(searchTerm); + if (maybeInt != null) { + qq = qq.or().valueEqualTo(maybeInt); + } + + return qq; + }, + ); + } + + final List utxos; + switch (sort) { + case CCSortDescriptor.age: + utxos = preSort.sortByBlockHeight().findAllSync(); + break; + case CCSortDescriptor.address: + utxos = preSort.sortByAddress().findAllSync(); + break; + case CCSortDescriptor.value: + utxos = preSort.sortByValueDesc().findAllSync(); + break; + } + + final Map> results = {}; + for (final utxo in utxos) { + if (results[utxo.address!] == null) { + results[utxo.address!] = []; + } + results[utxo.address!]!.add(utxo.id); + } + + return results; + } +} diff --git a/lib/dto/ethereum/eth_token_tx_dto.dart b/lib/dto/ethereum/eth_token_tx_dto.dart new file mode 100644 index 000000000..e7329ef79 --- /dev/null +++ b/lib/dto/ethereum/eth_token_tx_dto.dart @@ -0,0 +1,169 @@ +/// address : "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984" +/// blockNumber : 16484149 +/// logIndex : 61 +/// topics : ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef","0x0000000000000000000000003a5cc8689d1b0cef2c317bc5c0ad6ce88b27d597","0x000000000000000000000000c5e81fc2401b8104966637d5334cbce92f01dbf7"] +/// data : "0x0000000000000000000000000000000000000000000000002dac1c4be587d800" +/// articulatedLog : {"name":"Transfer","inputs":{"_amount":"3291036540000000000","_from":"0x3a5cc8689d1b0cef2c317bc5c0ad6ce88b27d597","_to":"0xc5e81fc2401b8104966637d5334cbce92f01dbf7"}} +/// compressedLog : "{name:Transfer|inputs:{_amount:3291036540000000000|_from:0x3a5cc8689d1b0cef2c317bc5c0ad6ce88b27d597|_to:0xc5e81fc2401b8104966637d5334cbce92f01dbf7}}" +/// transactionHash : "0x5b59559a77fa5f1c70528d41f4fa2e5fa5a00b21fc2f3bc26b208b3062e46333" +/// transactionIndex : 25 + +class EthTokenTxDto { + EthTokenTxDto({ + required this.address, + required this.blockNumber, + required this.logIndex, + required this.topics, + required this.data, + required this.articulatedLog, + required this.compressedLog, + required this.transactionHash, + required this.transactionIndex, + }); + + EthTokenTxDto.fromMap(Map map) + : address = map['address'] as String, + blockNumber = map['blockNumber'] as int, + logIndex = map['logIndex'] as int, + topics = List.from(map['topics'] as List), + data = map['data'] as String, + articulatedLog = map['articulatedLog'] == null + ? null + : ArticulatedLog.fromMap( + Map.from( + map['articulatedLog'] as Map, + ), + ), + compressedLog = map['compressedLog'] as String, + transactionHash = map['transactionHash'] as String, + transactionIndex = map['transactionIndex'] as int; + + final String address; + final int blockNumber; + final int logIndex; + final List topics; + final String data; + final ArticulatedLog? articulatedLog; + final String compressedLog; + final String transactionHash; + final int transactionIndex; + + EthTokenTxDto copyWith({ + String? address, + int? blockNumber, + int? logIndex, + List? topics, + String? data, + ArticulatedLog? articulatedLog, + String? compressedLog, + String? transactionHash, + int? transactionIndex, + }) => + EthTokenTxDto( + address: address ?? this.address, + blockNumber: blockNumber ?? this.blockNumber, + logIndex: logIndex ?? this.logIndex, + topics: topics ?? this.topics, + data: data ?? this.data, + articulatedLog: articulatedLog ?? this.articulatedLog, + compressedLog: compressedLog ?? this.compressedLog, + transactionHash: transactionHash ?? this.transactionHash, + transactionIndex: transactionIndex ?? this.transactionIndex, + ); + + Map toMap() { + final map = {}; + map['address'] = address; + map['blockNumber'] = blockNumber; + map['logIndex'] = logIndex; + map['topics'] = topics; + map['data'] = data; + map['articulatedLog'] = articulatedLog?.toMap(); + map['compressedLog'] = compressedLog; + map['transactionHash'] = transactionHash; + map['transactionIndex'] = transactionIndex; + return map; + } + + @override + String toString() { + return toMap().toString(); + } +} + +/// name : "Transfer" +/// inputs : {"_amount":"3291036540000000000","_from":"0x3a5cc8689d1b0cef2c317bc5c0ad6ce88b27d597","_to":"0xc5e81fc2401b8104966637d5334cbce92f01dbf7"} + +class ArticulatedLog { + ArticulatedLog({ + required this.name, + required this.inputs, + }); + + ArticulatedLog.fromMap(Map map) + : name = map['name'] as String, + inputs = Inputs.fromMap( + Map.from( + map['inputs'] as Map, + ), + ); + + final String name; + final Inputs inputs; + + ArticulatedLog copyWith({ + String? name, + Inputs? inputs, + }) => + ArticulatedLog( + name: name ?? this.name, + inputs: inputs ?? this.inputs, + ); + + Map toMap() { + final map = {}; + map['name'] = name; + map['inputs'] = inputs.toMap(); + return map; + } +} + +/// _amount : "3291036540000000000" +/// _from : "0x3a5cc8689d1b0cef2c317bc5c0ad6ce88b27d597" +/// _to : "0xc5e81fc2401b8104966637d5334cbce92f01dbf7" +/// +class Inputs { + Inputs({ + required this.amount, + required this.from, + required this.to, + }); + + Inputs.fromMap(Map map) + : amount = map['_amount'] as String, + from = map['_from'] as String, + to = map['_to'] as String; + + final String amount; + final String from; + final String to; + + Inputs copyWith({ + String? amount, + String? from, + String? to, + }) => + Inputs( + amount: amount ?? this.amount, + from: from ?? this.from, + to: to ?? this.to, + ); + + Map toMap() { + final map = {}; + map['_amount'] = amount; + map['_from'] = from; + map['_to'] = to; + return map; + } +} diff --git a/lib/dto/ethereum/eth_token_tx_extra_dto.dart b/lib/dto/ethereum/eth_token_tx_extra_dto.dart new file mode 100644 index 000000000..bbc457b0c --- /dev/null +++ b/lib/dto/ethereum/eth_token_tx_extra_dto.dart @@ -0,0 +1,121 @@ +import 'dart:convert'; + +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; + +class EthTokenTxExtraDTO { + EthTokenTxExtraDTO({ + required this.blockHash, + required this.blockNumber, + required this.from, + required this.gas, + required this.gasCost, + required this.gasPrice, + required this.gasUsed, + required this.hash, + required this.input, + required this.nonce, + required this.timestamp, + required this.to, + required this.transactionIndex, + required this.value, + }); + + factory EthTokenTxExtraDTO.fromMap(Map map) => + EthTokenTxExtraDTO( + hash: map['hash'] as String, + blockHash: map['blockHash'] as String, + blockNumber: map['blockNumber'] as int, + transactionIndex: map['transactionIndex'] as int, + timestamp: map['timestamp'] as int, + from: map['from'] as String, + to: map['to'] as String, + value: Amount( + rawValue: BigInt.parse(map['value'] as String), + fractionDigits: Coin.ethereum.decimals, + ), + gas: _amountFromJsonNum(map['gas']), + gasPrice: _amountFromJsonNum(map['gasPrice']), + nonce: map['nonce'] as int, + input: map['input'] as String, + gasCost: _amountFromJsonNum(map['gasCost']), + gasUsed: _amountFromJsonNum(map['gasUsed']), + ); + + final String hash; + final String blockHash; + final int blockNumber; + final int transactionIndex; + final int timestamp; + final String from; + final String to; + final Amount value; + final Amount gas; + final Amount gasPrice; + final String input; + final int nonce; + final Amount gasCost; + final Amount gasUsed; + + static Amount _amountFromJsonNum(dynamic json) { + return Amount( + rawValue: BigInt.from(json as num), + fractionDigits: Coin.ethereum.decimals, + ); + } + + EthTokenTxExtraDTO copyWith({ + String? hash, + String? blockHash, + int? blockNumber, + int? transactionIndex, + int? timestamp, + String? from, + String? to, + Amount? value, + Amount? gas, + Amount? gasPrice, + int? nonce, + String? input, + Amount? gasCost, + Amount? gasUsed, + }) => + EthTokenTxExtraDTO( + hash: hash ?? this.hash, + blockHash: blockHash ?? this.blockHash, + blockNumber: blockNumber ?? this.blockNumber, + transactionIndex: transactionIndex ?? this.transactionIndex, + timestamp: timestamp ?? this.timestamp, + from: from ?? this.from, + to: to ?? this.to, + value: value ?? this.value, + gas: gas ?? this.gas, + gasPrice: gasPrice ?? this.gasPrice, + nonce: nonce ?? this.nonce, + input: input ?? this.input, + gasCost: gasCost ?? this.gasCost, + gasUsed: gasUsed ?? this.gasUsed, + ); + + Map toMap() { + final map = {}; + map['hash'] = hash; + map['blockHash'] = blockHash; + map['blockNumber'] = blockNumber; + map['transactionIndex'] = transactionIndex; + map['timestamp'] = timestamp; + map['from'] = from; + map['to'] = to; + map['value'] = value; + map['gas'] = gas; + map['gasPrice'] = gasPrice; + map['input'] = input; + map['nonce'] = nonce; + map['gasCost'] = gasCost; + map['gasUsed'] = gasUsed; + return map; + } + + @override + String toString() => jsonEncode(toMap()); +} diff --git a/lib/dto/ethereum/eth_tx_dto.dart b/lib/dto/ethereum/eth_tx_dto.dart new file mode 100644 index 000000000..a4345dec5 --- /dev/null +++ b/lib/dto/ethereum/eth_tx_dto.dart @@ -0,0 +1,135 @@ +import 'dart:convert'; + +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; + +class EthTxDTO { + EthTxDTO({ + required this.hash, + required this.blockHash, + required this.blockNumber, + required this.transactionIndex, + required this.timestamp, + required this.from, + required this.to, + required this.value, + required this.gas, + required this.gasPrice, + required this.maxFeePerGas, + required this.maxPriorityFeePerGas, + required this.isError, + required this.hasToken, + required this.compressedTx, + required this.gasCost, + required this.gasUsed, + }); + + factory EthTxDTO.fromMap(Map map) => EthTxDTO( + hash: map['hash'] as String, + blockHash: map['blockHash'] as String, + blockNumber: map['blockNumber'] as int, + transactionIndex: map['transactionIndex'] as int, + timestamp: map['timestamp'] as int, + from: map['from'] as String, + to: map['to'] as String, + value: _amountFromJsonNum(map['value']), + gas: _amountFromJsonNum(map['gas']), + gasPrice: _amountFromJsonNum(map['gasPrice']), + maxFeePerGas: _amountFromJsonNum(map['maxFeePerGas']), + maxPriorityFeePerGas: _amountFromJsonNum(map['maxPriorityFeePerGas']), + isError: map['isError'] as int, + hasToken: map['hasToken'] as int, + compressedTx: map['compressedTx'] as String, + gasCost: _amountFromJsonNum(map['gasCost']), + gasUsed: _amountFromJsonNum(map['gasUsed']), + ); + + final String hash; + final String blockHash; + final int blockNumber; + final int transactionIndex; + final int timestamp; + final String from; + final String to; + final Amount value; + final Amount gas; + final Amount gasPrice; + final Amount maxFeePerGas; + final Amount maxPriorityFeePerGas; + final int isError; + final int hasToken; + final String compressedTx; + final Amount gasCost; + final Amount gasUsed; + + static Amount _amountFromJsonNum(dynamic json) { + return Amount( + rawValue: BigInt.from(json as num), + fractionDigits: Coin.ethereum.decimals, + ); + } + + EthTxDTO copyWith({ + String? hash, + String? blockHash, + int? blockNumber, + int? transactionIndex, + int? timestamp, + String? from, + String? to, + Amount? value, + Amount? gas, + Amount? gasPrice, + Amount? maxFeePerGas, + Amount? maxPriorityFeePerGas, + int? isError, + int? hasToken, + String? compressedTx, + Amount? gasCost, + Amount? gasUsed, + }) => + EthTxDTO( + hash: hash ?? this.hash, + blockHash: blockHash ?? this.blockHash, + blockNumber: blockNumber ?? this.blockNumber, + transactionIndex: transactionIndex ?? this.transactionIndex, + timestamp: timestamp ?? this.timestamp, + from: from ?? this.from, + to: to ?? this.to, + value: value ?? this.value, + gas: gas ?? this.gas, + gasPrice: gasPrice ?? this.gasPrice, + maxFeePerGas: maxFeePerGas ?? this.maxFeePerGas, + maxPriorityFeePerGas: maxPriorityFeePerGas ?? this.maxPriorityFeePerGas, + isError: isError ?? this.isError, + hasToken: hasToken ?? this.hasToken, + compressedTx: compressedTx ?? this.compressedTx, + gasCost: gasCost ?? this.gasCost, + gasUsed: gasUsed ?? this.gasUsed, + ); + + Map toMap() { + final map = {}; + map['hash'] = hash; + map['blockHash'] = blockHash; + map['blockNumber'] = blockNumber; + map['transactionIndex'] = transactionIndex; + map['timestamp'] = timestamp; + map['from'] = from; + map['to'] = to; + map['value'] = value; + map['gas'] = gas; + map['gasPrice'] = gasPrice; + map['maxFeePerGas'] = maxFeePerGas; + map['maxPriorityFeePerGas'] = maxPriorityFeePerGas; + map['isError'] = isError; + map['hasToken'] = hasToken; + map['compressedTx'] = compressedTx; + map['gasCost'] = gasCost; + map['gasUsed'] = gasUsed; + return map; + } + + @override + String toString() => jsonEncode(toMap()); +} diff --git a/lib/dto/ethereum/pending_eth_tx_dto.dart b/lib/dto/ethereum/pending_eth_tx_dto.dart new file mode 100644 index 000000000..0c1fa03b6 --- /dev/null +++ b/lib/dto/ethereum/pending_eth_tx_dto.dart @@ -0,0 +1,150 @@ +/// blockHash : null +/// blockNumber : null +/// from : "0x..." +/// gas : "0x7e562" +/// maxPriorityFeePerGas : "0x444380" +/// maxFeePerGas : "0x342570c00" +/// hash : "0x...da64e4" +/// input : "....." +/// nonce : "0x70" +/// to : "0x00....." +/// transactionIndex : null +/// value : "0x0" +/// type : "0x2" +/// accessList : [] +/// chainId : "0x1" +/// v : "0x0" +/// r : "0xd..." +/// s : "0x17d...6e6" + +class PendingEthTxDto { + PendingEthTxDto({ + required this.blockHash, + required this.blockNumber, + required this.from, + required this.gas, + required this.maxPriorityFeePerGas, + required this.maxFeePerGas, + required this.hash, + required this.input, + required this.nonce, + required this.to, + required this.transactionIndex, + required this.value, + required this.type, + required this.accessList, + required this.chainId, + required this.v, + required this.r, + required this.s, + }); + + factory PendingEthTxDto.fromMap(Map map) => PendingEthTxDto( + blockHash: map['blockHash'] as String?, + blockNumber: map['blockNumber'] as int?, + from: map['from'] as String, + gas: map['gas'] as String, + maxPriorityFeePerGas: map['maxPriorityFeePerGas'] as String, + maxFeePerGas: map['maxFeePerGas'] as String, + hash: map['hash'] as String, + input: map['input'] as String, + nonce: map['nonce'] as String, + to: map['to'] as String, + transactionIndex: map['transactionIndex'] as int?, + value: map['value'] as String, + type: map['type'] as String, + accessList: map['accessList'] as List? ?? [], + chainId: map['chainId'] as String, + v: map['v'] as String, + r: map['r'] as String, + s: map['s'] as String, + ); + + final String? blockHash; + final int? blockNumber; + final String from; + final String gas; + final String maxPriorityFeePerGas; + final String maxFeePerGas; + final String hash; + final String input; + final String nonce; + final String to; + final int? transactionIndex; + final String value; + final String type; + final List accessList; + final String chainId; + final String v; + final String r; + final String s; + + PendingEthTxDto copyWith({ + String? blockHash, + int? blockNumber, + String? from, + String? gas, + String? maxPriorityFeePerGas, + String? maxFeePerGas, + String? hash, + String? input, + String? nonce, + String? to, + int? transactionIndex, + String? value, + String? type, + List? accessList, + String? chainId, + String? v, + String? r, + String? s, + }) => + PendingEthTxDto( + blockHash: blockHash ?? this.blockHash, + blockNumber: blockNumber ?? this.blockNumber, + from: from ?? this.from, + gas: gas ?? this.gas, + maxPriorityFeePerGas: maxPriorityFeePerGas ?? this.maxPriorityFeePerGas, + maxFeePerGas: maxFeePerGas ?? this.maxFeePerGas, + hash: hash ?? this.hash, + input: input ?? this.input, + nonce: nonce ?? this.nonce, + to: to ?? this.to, + transactionIndex: transactionIndex ?? this.transactionIndex, + value: value ?? this.value, + type: type ?? this.type, + accessList: accessList ?? this.accessList, + chainId: chainId ?? this.chainId, + v: v ?? this.v, + r: r ?? this.r, + s: s ?? this.s, + ); + + Map toMap() { + final map = {}; + map['blockHash'] = blockHash; + map['blockNumber'] = blockNumber; + map['from'] = from; + map['gas'] = gas; + map['maxPriorityFeePerGas'] = maxPriorityFeePerGas; + map['maxFeePerGas'] = maxFeePerGas; + map['hash'] = hash; + map['input'] = input; + map['nonce'] = nonce; + map['to'] = to; + map['transactionIndex'] = transactionIndex; + map['value'] = value; + map['type'] = type; + map['accessList'] = accessList; + map['chainId'] = chainId; + map['v'] = v; + map['r'] = r; + map['s'] = s; + return map; + } + + @override + String toString() { + return toMap().toString(); + } +} diff --git a/lib/electrumx_rpc/cached_electrumx.dart b/lib/electrumx_rpc/cached_electrumx.dart index e7d815eab..2aab533f8 100644 --- a/lib/electrumx_rpc/cached_electrumx.dart +++ b/lib/electrumx_rpc/cached_electrumx.dart @@ -1,7 +1,7 @@ import 'dart:convert'; +import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; -import 'package:stackwallet/hive/db.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; @@ -115,8 +115,9 @@ class CachedElectrumX { key: groupId, value: set); Logging.instance.log( - "Updated currently anonymity set for ${coin.name} with group ID $groupId", - level: LogLevel.Info); + "Updated current anonymity set for ${coin.name} with group ID $groupId", + level: LogLevel.Info, + ); } return set; @@ -187,16 +188,17 @@ class CachedElectrumX { } } - Future> getUsedCoinSerials({ + Future> getUsedCoinSerials({ required Coin coin, int startNumber = 0, }) async { try { - List? cachedSerials = DB.instance.get( + final _list = DB.instance.get( boxName: DB.instance.boxNameUsedSerialsCache(coin: coin), key: "serials") as List?; - cachedSerials ??= []; + List cachedSerials = + _list == null ? [] : List.from(_list); final startNumber = cachedSerials.length; @@ -210,8 +212,9 @@ class CachedElectrumX { ); final serials = await client.getUsedCoinSerials(startNumber: startNumber); - List newSerials = []; - for (var element in (serials["serials"] as List)) { + List newSerials = []; + + for (final element in (serials["serials"] as List)) { if (!isHexadecimal(element as String)) { newSerials.add(base64ToHex(element)); } else { @@ -221,9 +224,10 @@ class CachedElectrumX { cachedSerials.addAll(newSerials); await DB.instance.put( - boxName: DB.instance.boxNameUsedSerialsCache(coin: coin), - key: "serials", - value: cachedSerials); + boxName: DB.instance.boxNameUsedSerialsCache(coin: coin), + key: "serials", + value: cachedSerials, + ); return cachedSerials; } catch (e, s) { diff --git a/lib/electrumx_rpc/electrumx.dart b/lib/electrumx_rpc/electrumx.dart index c34b760e4..6667c3de5 100644 --- a/lib/electrumx_rpc/electrumx.dart +++ b/lib/electrumx_rpc/electrumx.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:decimal/decimal.dart'; import 'package:stackwallet/electrumx_rpc/rpc.dart'; +import 'package:stackwallet/exceptions/electrumx/no_such_transaction.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; import 'package:uuid/uuid.dart'; @@ -132,7 +133,17 @@ class ElectrumX { final response = await _rpcClient!.request(jsonRequestString); if (response["error"] != null) { - throw Exception("JSONRPC response error: $response"); + if (response["error"] + .toString() + .contains("No such mempool or blockchain transaction")) { + throw NoSuchTransactionException( + "No such mempool or blockchain transaction", + args.first.toString(), + ); + } + + throw Exception( + "JSONRPC response \ncommand: $command \nargs: $args \nerror: $response"); } currentFailoverIndex = -1; @@ -544,6 +555,10 @@ class ElectrumX { verbose, ], ); + if (!verbose) { + return {"rawtx": response["result"] as String}; + } + return Map.from(response["result"] as Map); } catch (e) { rethrow; diff --git a/lib/exceptions/address/address_exception.dart b/lib/exceptions/address/address_exception.dart new file mode 100644 index 000000000..3331bef99 --- /dev/null +++ b/lib/exceptions/address/address_exception.dart @@ -0,0 +1,5 @@ +import 'package:stackwallet/exceptions/sw_exception.dart'; + +class AddressException extends SWException { + AddressException(super.message); +} diff --git a/lib/exceptions/electrumx/no_such_transaction.dart b/lib/exceptions/electrumx/no_such_transaction.dart new file mode 100644 index 000000000..0c7bedeae --- /dev/null +++ b/lib/exceptions/electrumx/no_such_transaction.dart @@ -0,0 +1,7 @@ +import 'package:stackwallet/exceptions/sw_exception.dart'; + +class NoSuchTransactionException extends SWException { + final String txid; + + NoSuchTransactionException(super.message, this.txid); +} diff --git a/lib/exceptions/exchange/exchange_exception.dart b/lib/exceptions/exchange/exchange_exception.dart new file mode 100644 index 000000000..c68599321 --- /dev/null +++ b/lib/exceptions/exchange/exchange_exception.dart @@ -0,0 +1,13 @@ +import 'package:stackwallet/exceptions/sw_exception.dart'; + +enum ExchangeExceptionType { generic, serializeResponseError, orderNotFound } + +class ExchangeException extends SWException { + ExchangeExceptionType type; + ExchangeException(super.message, this.type); + + @override + String toString() { + return message; + } +} diff --git a/lib/exceptions/exchange/majestic_bank/mb_exception.dart b/lib/exceptions/exchange/majestic_bank/mb_exception.dart new file mode 100644 index 000000000..d3130d874 --- /dev/null +++ b/lib/exceptions/exchange/majestic_bank/mb_exception.dart @@ -0,0 +1,5 @@ +import 'package:stackwallet/exceptions/exchange/exchange_exception.dart'; + +class MBException extends ExchangeException { + MBException(super.message, super.type); +} diff --git a/lib/exceptions/exchange/pair_unavailable_exception.dart b/lib/exceptions/exchange/pair_unavailable_exception.dart new file mode 100644 index 000000000..9c9ee5a5c --- /dev/null +++ b/lib/exceptions/exchange/pair_unavailable_exception.dart @@ -0,0 +1,5 @@ +import 'package:stackwallet/exceptions/exchange/exchange_exception.dart'; + +class PairUnavailableException extends ExchangeException { + PairUnavailableException(super.message, super.type); +} diff --git a/lib/exceptions/exchange/unsupported_currency_exception.dart b/lib/exceptions/exchange/unsupported_currency_exception.dart new file mode 100644 index 000000000..9e4430de8 --- /dev/null +++ b/lib/exceptions/exchange/unsupported_currency_exception.dart @@ -0,0 +1,7 @@ +import 'package:stackwallet/exceptions/exchange/exchange_exception.dart'; + +class UnsupportedCurrencyException extends ExchangeException { + UnsupportedCurrencyException(super.message, super.type, this.currency); + + final String currency; +} diff --git a/lib/exceptions/main_db/main_db_exception.dart b/lib/exceptions/main_db/main_db_exception.dart new file mode 100644 index 000000000..4285060ba --- /dev/null +++ b/lib/exceptions/main_db/main_db_exception.dart @@ -0,0 +1,12 @@ +import 'package:stackwallet/exceptions/sw_exception.dart'; + +class MainDBException extends SWException { + MainDBException(super.message, this.originalError); + + final Object originalError; + + @override + String toString() { + return "$message: originalError=$originalError"; + } +} diff --git a/lib/exceptions/sw_exception.dart b/lib/exceptions/sw_exception.dart new file mode 100644 index 000000000..34ab664f2 --- /dev/null +++ b/lib/exceptions/sw_exception.dart @@ -0,0 +1,11 @@ +// generic stack wallet exception which all other custom exceptions should +// extend from + +class SWException with Exception { + SWException(this.message); + + final String message; + + @override + toString() => message; +} diff --git a/lib/exceptions/wallet/insufficient_balance_exception.dart b/lib/exceptions/wallet/insufficient_balance_exception.dart new file mode 100644 index 000000000..219ef6a76 --- /dev/null +++ b/lib/exceptions/wallet/insufficient_balance_exception.dart @@ -0,0 +1,5 @@ +import 'package:stackwallet/exceptions/sw_exception.dart'; + +class InsufficientBalanceException extends SWException { + InsufficientBalanceException(super.message); +} diff --git a/lib/exceptions/wallet/paynym_send_exception.dart b/lib/exceptions/wallet/paynym_send_exception.dart new file mode 100644 index 000000000..9980e6790 --- /dev/null +++ b/lib/exceptions/wallet/paynym_send_exception.dart @@ -0,0 +1,5 @@ +import 'package:stackwallet/exceptions/sw_exception.dart'; + +class PaynymSendException extends SWException { + PaynymSendException(super.message); +} diff --git a/lib/main.dart b/lib/main.dart index 0acca2a3c..06167255a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,6 +6,7 @@ import 'package:cw_core/node.dart'; import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_libmonero/monero/monero.dart'; @@ -17,11 +18,12 @@ import 'package:hive_flutter/hive_flutter.dart'; import 'package:isar/isar.dart'; import 'package:keyboard_dismisser/keyboard_dismisser.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/db/hive/db.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; -import 'package:stackwallet/models/isar/models/log.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; import 'package:stackwallet/models/models.dart'; import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/models/notification_model.dart'; @@ -39,28 +41,26 @@ import 'package:stackwallet/providers/global/base_currencies_provider.dart'; // import 'package:stackwallet/providers/global/has_authenticated_start_state_provider.dart'; import 'package:stackwallet/providers/global/trades_service_provider.dart'; import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/providers/ui/color_theme_provider.dart'; import 'package:stackwallet/route_generator.dart'; +// import 'package:stackwallet/services/buy/buy_data_loading_service.dart'; import 'package:stackwallet/services/debug_service.dart'; -import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart'; import 'package:stackwallet/services/locale_service.dart'; import 'package:stackwallet/services/node_service.dart'; import 'package:stackwallet/services/notifications_api.dart'; import 'package:stackwallet/services/notifications_service.dart'; import 'package:stackwallet/services/trade_service.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; +import 'package:stackwallet/themes/theme_service.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/db_version_migration.dart'; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/stack_file_system.dart'; -import 'package:stackwallet/utilities/theme/color_theme.dart'; -import 'package:stackwallet/utilities/theme/dark_colors.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/ocean_breeze_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/crypto_notifications.dart'; import 'package:window_size/window_size.dart'; final openedFromSWBFileStringStateProvider = @@ -75,7 +75,6 @@ void main() async { if (Platform.isIOS) { Util.libraryPath = await getLibraryDirectory(); } - Screen? screen; if (Platform.isLinux || (Util.isDesktop && !Platform.isIOS)) { screen = await getCurrentScreen(); @@ -88,7 +87,7 @@ void main() async { setWindowMaxSize(Size.infinite); final screenHeight = screen?.frame.height; - if (screenHeight != null) { + if (screenHeight != null && !kDebugMode) { // starting to height be 3/4 screen height or 900, whichever is smaller final height = min(screenHeight * 0.75, 900); setWindowFrame( @@ -103,12 +102,13 @@ void main() async { [LogSchema], directory: (await StackFileSystem.applicationIsarDirectory()).path, inspector: false, + maxSizeMiB: 512, ); await Logging.instance.init(isar); await DebugService.instance.init(isar); // clear out all info logs on startup. No need to await and block - unawaited(DebugService.instance.purgeInfoLogs()); + unawaited(DebugService.instance.deleteLogsOlderThan()); } // Registering Transaction Model Adapters @@ -154,13 +154,15 @@ void main() async { (await StackFileSystem.applicationHiveDirectory()).path); await Hive.openBox(DB.boxNameDBInfo); + await Hive.openBox(DB.boxNamePrefs); + await Prefs.instance.init(); - // todo: db migrate stuff for desktop needs to be handled eventually + // Desktop migrate handled elsewhere (currently desktop_login_view.dart) if (!Util.isDesktop) { int dbVersion = DB.instance.get( boxName: DB.boxNameDBInfo, key: "hive_data_version") as int? ?? 0; - if (dbVersion < Constants.currentHiveDbVersion) { + if (dbVersion < Constants.currentDataVersion) { try { await DbVersionMigrator().migrate( dbVersion, @@ -170,7 +172,7 @@ void main() async { ), ); } catch (e, s) { - Logging.instance.log("Cannot migrate database\n$e $s", + Logging.instance.log("Cannot migrate mobile database\n$e $s", level: LogLevel.Error, printFullLength: true); } } @@ -179,12 +181,16 @@ void main() async { wownero.onStartup(); monero.onStartup(); - await Hive.openBox(DB.boxNameTheme); - // SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, // overlays: [SystemUiOverlay.bottom]); await NotificationApi.init(); + await MainDB.instance.initMainDB(); + ThemeService.instance.init(MainDB.instance); + + // check and update or install default themes + await ThemeService.instance.checkDefaultThemesOnStartup(); + runApp(const ProviderScope(child: MyApp())); } @@ -250,6 +256,13 @@ class _MaterialAppWithThemeState extends ConsumerState _desktopHasPassword = await ref.read(storageCryptoHandlerProvider).hasPassword(); } + + ref + .read(priceAnd24hChangeNotifierProvider) + .tokenContractAddressesToCheck + .addAll( + await MainDB.instance.getEthContracts().addressProperty().findAll(), + ); } Future load() async { @@ -263,6 +276,9 @@ class _MaterialAppWithThemeState extends ConsumerState await loadShared(); } + ref.read(applicationThemesDirectoryPathProvider.notifier).state = + (await StackFileSystem.applicationThemesDirectory()).path; + _notificationsService = ref.read(notificationsProvider); _nodeService = ref.read(nodeServiceChangeNotifierProvider); _tradesService = ref.read(tradesServiceProvider); @@ -286,11 +302,20 @@ class _MaterialAppWithThemeState extends ConsumerState // TODO: this should probably run unawaited. Keep commented out for now as proper community nodes ui hasn't been implemented yet // unawaited(_nodeService.updateCommunityNodes()); + await ExchangeDataLoadingService.instance.initDB(); // run without awaiting - if (Constants.enableExchange && - ref.read(prefsChangeNotifierProvider).externalCalls && + if (ref.read(prefsChangeNotifierProvider).externalCalls && await ref.read(prefsChangeNotifierProvider).isExternalCallsSet()) { - unawaited(ExchangeDataLoadingService().loadAll(ref)); + if (Constants.enableExchange) { + await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty( + ref.read(efCurrencyPairProvider), + ref.read(efRateTypeProvider), + ); + unawaited(ExchangeDataLoadingService.instance.loadAll()); + } + // if (Constants.enableBuy) { + // unawaited(BuyDataLoadingService().loadAll(ref)); + // } } if (ref.read(prefsChangeNotifierProvider).isAutoBackupEnabled) { @@ -307,6 +332,11 @@ class _MaterialAppWithThemeState extends ConsumerState break; } } + + // ref + // .read(prefsChangeNotifierProvider) + // .userID; // Just reading the ref should set it if it's not already set + // We shouldn't need to do this, instead only generating an ID when (or if) the userID is looked up when creating a quote } catch (e, s) { Logger.print("$e $s", normalLength: false); } @@ -314,22 +344,24 @@ class _MaterialAppWithThemeState extends ConsumerState @override void initState() { - ref.read(exchangeFormStateProvider).exchange = ChangeNowExchange(); - final colorScheme = DB.instance - .get(boxName: DB.boxNameTheme, key: "colorScheme") as String?; - - StackColorTheme colorTheme; - switch (colorScheme) { - case "dark": - colorTheme = DarkColors(); - break; - case "oceanBreeze": - colorTheme = OceanBreezeColors(); - break; - case "light": - default: - colorTheme = LightColors(); + String themeId; + if (ref.read(prefsChangeNotifierProvider).enableSystemBrightness) { + final brightness = WidgetsBinding.instance.window.platformBrightness; + switch (brightness) { + case Brightness.dark: + themeId = + ref.read(prefsChangeNotifierProvider).systemBrightnessDarkThemeId; + break; + case Brightness.light: + themeId = ref + .read(prefsChangeNotifierProvider) + .systemBrightnessLightThemeId; + break; + } + } else { + themeId = ref.read(prefsChangeNotifierProvider).themeId; } + loadingCompleter = Completer(); WidgetsBinding.instance.addObserver(this); // load locale and prefs @@ -338,8 +370,13 @@ class _MaterialAppWithThemeState extends ConsumerState .loadLocale(notify: false); WidgetsBinding.instance.addPostFrameCallback((_) async { - ref.read(colorThemeProvider.state).state = - StackColors.fromStackColorTheme(colorTheme); + //Add themes path to provider + ref.read(applicationThemesDirectoryPathProvider.notifier).state = + (await StackFileSystem.applicationThemesDirectory()).path; + + ref.read(themeProvider.state).state = ref.read(pThemeService).getTheme( + themeId: themeId, + )!; if (Platform.isAndroid) { // fetch open file if it exists @@ -358,6 +395,30 @@ class _MaterialAppWithThemeState extends ConsumerState } }); + WidgetsBinding.instance.window.onPlatformBrightnessChanged = () { + String themeId; + switch (WidgetsBinding.instance.window.platformBrightness) { + case Brightness.dark: + themeId = + ref.read(prefsChangeNotifierProvider).systemBrightnessDarkThemeId; + break; + case Brightness.light: + themeId = ref + .read(prefsChangeNotifierProvider) + .systemBrightnessLightThemeId; + break; + } + + WidgetsBinding.instance.addPostFrameCallback((_) { + if (ref.read(prefsChangeNotifierProvider).enableSystemBrightness) { + ref.read(themeProvider.state).state = + ref.read(pThemeService).getTheme( + themeId: themeId, + )!; + } + }); + }; + super.initState(); } @@ -488,7 +549,7 @@ class _MaterialAppWithThemeState extends ConsumerState // addToDebugMessagesDB: false); // }); - final colorScheme = ref.watch(colorThemeProvider.state).state; + final colorScheme = ref.watch(colorProvider.state).state; return MaterialApp( key: GlobalKey(), @@ -498,7 +559,7 @@ class _MaterialAppWithThemeState extends ConsumerState theme: ThemeData( extensions: [colorScheme], highlightColor: colorScheme.highlight, - brightness: Brightness.light, + brightness: colorScheme.brightness, fontFamily: GoogleFonts.inter().fontFamily, unselectedWidgetColor: colorScheme.radioButtonBorderDisabled, // textTheme: GoogleFonts.interTextTheme().copyWith( @@ -586,70 +647,74 @@ class _MaterialAppWithThemeState extends ConsumerState _buildOutlineInputBorder(colorScheme.textFieldDefaultBG), ), ), - home: Util.isDesktop - ? FutureBuilder( - future: loadShared(), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - if (_desktopHasPassword) { - String? startupWalletId; - if (ref - .read(prefsChangeNotifierProvider) - .gotoWalletOnStartup) { - startupWalletId = - ref.read(prefsChangeNotifierProvider).startupWalletId; + home: CryptoNotifications( + child: Util.isDesktop + ? FutureBuilder( + future: loadShared(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (_desktopHasPassword) { + String? startupWalletId; + if (ref + .read(prefsChangeNotifierProvider) + .gotoWalletOnStartup) { + startupWalletId = ref + .read(prefsChangeNotifierProvider) + .startupWalletId; + } + + return DesktopLoginView( + startupWalletId: startupWalletId, + load: load, + ); + } else { + return const IntroView(); } - - return DesktopLoginView( - startupWalletId: startupWalletId, - load: load, - ); } else { - return const IntroView(); + return const LoadingView(); } - } else { - return const LoadingView(); - } - }, - ) - : FutureBuilder( - future: load(), - builder: (BuildContext context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - // FlutterNativeSplash.remove(); - if (ref.read(walletsChangeNotifierProvider).hasWallets || - ref.read(prefsChangeNotifierProvider).hasPin) { - // return HomeView(); + }, + ) + : FutureBuilder( + future: load(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + // FlutterNativeSplash.remove(); + if (ref.read(walletsChangeNotifierProvider).hasWallets || + ref.read(prefsChangeNotifierProvider).hasPin) { + // return HomeView(); - String? startupWalletId; - if (ref - .read(prefsChangeNotifierProvider) - .gotoWalletOnStartup) { - startupWalletId = - ref.read(prefsChangeNotifierProvider).startupWalletId; + String? startupWalletId; + if (ref + .read(prefsChangeNotifierProvider) + .gotoWalletOnStartup) { + startupWalletId = ref + .read(prefsChangeNotifierProvider) + .startupWalletId; + } + + return LockscreenView( + isInitialAppLogin: true, + routeOnSuccess: HomeView.routeName, + routeOnSuccessArguments: startupWalletId, + biometricsAuthenticationTitle: "Unlock Stack", + biometricsLocalizedReason: + "Unlock your stack wallet using biometrics", + biometricsCancelButtonString: "Cancel", + ); + } else { + return const IntroView(); } - - return LockscreenView( - isInitialAppLogin: true, - routeOnSuccess: HomeView.routeName, - routeOnSuccessArguments: startupWalletId, - biometricsAuthenticationTitle: "Unlock Stack", - biometricsLocalizedReason: - "Unlock your stack wallet using biometrics", - biometricsCancelButtonString: "Cancel", - ); } else { - return const IntroView(); + // CURRENTLY DISABLED as cannot be animated + // technically not needed as FlutterNativeSplash will overlay + // anything returned here until the future completes but + // FutureBuilder requires you to return something + return const LoadingView(); } - } else { - // CURRENTLY DISABLED as cannot be animated - // technically not needed as FlutterNativeSplash will overlay - // anything returned here until the future completes but - // FutureBuilder requires you to return something - return const LoadingView(); - } - }, - ), + }, + ), + ), ); } } diff --git a/lib/models/add_wallet_list_entity/add_wallet_list_entity.dart b/lib/models/add_wallet_list_entity/add_wallet_list_entity.dart new file mode 100644 index 000000000..3dd24d7b1 --- /dev/null +++ b/lib/models/add_wallet_list_entity/add_wallet_list_entity.dart @@ -0,0 +1,8 @@ +import 'package:equatable/equatable.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; + +abstract class AddWalletListEntity extends Equatable { + Coin get coin; + String get name; + String get ticker; +} diff --git a/lib/models/add_wallet_list_entity/sub_classes/coin_entity.dart b/lib/models/add_wallet_list_entity/sub_classes/coin_entity.dart new file mode 100644 index 000000000..770a9d1cf --- /dev/null +++ b/lib/models/add_wallet_list_entity/sub_classes/coin_entity.dart @@ -0,0 +1,20 @@ +import 'package:stackwallet/models/add_wallet_list_entity/add_wallet_list_entity.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; + +class CoinEntity extends AddWalletListEntity { + CoinEntity(this._coin); + + final Coin _coin; + + @override + Coin get coin => _coin; + + @override + String get name => coin.prettyName; + + @override + String get ticker => coin.ticker; + + @override + List get props => [coin, name, ticker]; +} diff --git a/lib/models/add_wallet_list_entity/sub_classes/eth_token_entity.dart b/lib/models/add_wallet_list_entity/sub_classes/eth_token_entity.dart new file mode 100644 index 000000000..ccc0da239 --- /dev/null +++ b/lib/models/add_wallet_list_entity/sub_classes/eth_token_entity.dart @@ -0,0 +1,21 @@ +import 'package:stackwallet/models/add_wallet_list_entity/add_wallet_list_entity.dart'; +import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; + +class EthTokenEntity extends AddWalletListEntity { + EthTokenEntity(this.token); + + final EthContract token; + + @override + Coin get coin => Coin.ethereum; + + @override + String get name => token.name; + + @override + String get ticker => token.symbol; + + @override + List get props => [coin, name, ticker, token.address]; +} diff --git a/lib/models/balance.dart b/lib/models/balance.dart new file mode 100644 index 000000000..63fbe9ab7 --- /dev/null +++ b/lib/models/balance.dart @@ -0,0 +1,69 @@ +import 'dart:convert'; + +import 'package:stackwallet/utilities/amount/amount.dart'; + +class Balance { + final Amount total; + final Amount spendable; + final Amount blockedTotal; + final Amount pendingSpendable; + + Balance({ + required this.total, + required this.spendable, + required this.blockedTotal, + required this.pendingSpendable, + }); + + String toJsonIgnoreCoin() => jsonEncode({ + "total": total.toJsonString(), + "spendable": spendable.toJsonString(), + "blockedTotal": blockedTotal.toJsonString(), + "pendingSpendable": pendingSpendable.toJsonString(), + }); + + // need to fall back to parsing from int due to cached balances being previously + // stored as int values instead of Amounts + factory Balance.fromJson(String json, int deprecatedValue) { + final decoded = jsonDecode(json); + return Balance( + total: decoded["total"] is String + ? Amount.fromSerializedJsonString(decoded["total"] as String) + : Amount( + rawValue: BigInt.from(decoded["total"] as int), + fractionDigits: deprecatedValue, + ), + spendable: decoded["spendable"] is String + ? Amount.fromSerializedJsonString(decoded["spendable"] as String) + : Amount( + rawValue: BigInt.from(decoded["spendable"] as int), + fractionDigits: deprecatedValue, + ), + blockedTotal: decoded["blockedTotal"] is String + ? Amount.fromSerializedJsonString(decoded["blockedTotal"] as String) + : Amount( + rawValue: BigInt.from(decoded["blockedTotal"] as int), + fractionDigits: deprecatedValue, + ), + pendingSpendable: decoded["pendingSpendable"] is String + ? Amount.fromSerializedJsonString( + decoded["pendingSpendable"] as String) + : Amount( + rawValue: BigInt.from(decoded["pendingSpendable"] as int), + fractionDigits: deprecatedValue, + ), + ); + } + + Map toMap() => { + "total": total, + "spendable": spendable, + "blockedTotal": blockedTotal, + "pendingSpendable": pendingSpendable, + }; + + @override + String toString() { + return toMap().toString(); + } +} diff --git a/lib/models/buy/buy_form_state.dart b/lib/models/buy/buy_form_state.dart new file mode 100644 index 000000000..843262eb3 --- /dev/null +++ b/lib/models/buy/buy_form_state.dart @@ -0,0 +1,13 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:stackwallet/services/buy/buy.dart'; + +class BuyFormState extends ChangeNotifier { + Buy? _buy; + Buy? get buy => _buy; + set buy(Buy? value) { + _buy = value; + } + + bool reversed = false; +} diff --git a/lib/models/buy/response_objects/crypto.dart b/lib/models/buy/response_objects/crypto.dart new file mode 100644 index 000000000..d3f3aded0 --- /dev/null +++ b/lib/models/buy/response_objects/crypto.dart @@ -0,0 +1,44 @@ +class Crypto { + /// Crypto ticker + final String ticker; + + /// Crypto name + final String name; + + /// Crypto network + final String? network; + + /// Crypto contract address + final String? contractAddress; + + Crypto({ + required this.ticker, + required this.name, + required this.network, + required this.contractAddress, + }); + + factory Crypto.fromJson(Map json) { + try { + return Crypto( + ticker: "${json['ticker']}", + name: "${json['name']}", + network: "${json['network']}", + contractAddress: "${json['contractAddress']}", + ); + } catch (e) { + rethrow; + } + } + + Map toJson() { + final map = { + "ticker": ticker, + "name": name, + "network": network, + "contractAddress": contractAddress, + }; + + return map; + } +} diff --git a/lib/models/buy/response_objects/fiat.dart b/lib/models/buy/response_objects/fiat.dart new file mode 100644 index 000000000..00cb0f945 --- /dev/null +++ b/lib/models/buy/response_objects/fiat.dart @@ -0,0 +1,45 @@ +import 'package:decimal/decimal.dart'; + +class Fiat { + /// Fiat ticker + final String ticker; + + /// Fiat name + final String name; + + /// Fiat name + final Decimal minAmount; + + /// Fiat name + final Decimal maxAmount; + + Fiat( + {required this.ticker, + required this.name, + required this.minAmount, + required this.maxAmount}); + + factory Fiat.fromJson(Map json) { + try { + return Fiat( + ticker: "${json['ticker']}", + name: "${json['name']}", // TODO nameFromTicker + minAmount: Decimal.parse("${json['minAmount'] ?? 0}"), + maxAmount: Decimal.parse("${json['maxAmount'] ?? 0}"), + ); + } catch (e) { + rethrow; + } + } + + Map toJson() { + final map = { + "ticker": ticker, + "name": name, + "min_amount": minAmount, + "max_amount": maxAmount, + }; + + return map; + } +} diff --git a/lib/models/buy/response_objects/order.dart b/lib/models/buy/response_objects/order.dart new file mode 100644 index 000000000..3802dd1cc --- /dev/null +++ b/lib/models/buy/response_objects/order.dart @@ -0,0 +1,17 @@ +import 'package:stackwallet/models/buy/response_objects/quote.dart'; + +class SimplexOrder { + final SimplexQuote quote; + + late final String paymentId; + late final String orderId; + late final String userId; + // TODO remove after userIds are sourced from isar/storage + + SimplexOrder({ + required this.quote, + required this.paymentId, + required this.orderId, + required this.userId, + }); +} diff --git a/lib/models/buy/response_objects/quote.dart b/lib/models/buy/response_objects/quote.dart new file mode 100644 index 000000000..e91ca4d67 --- /dev/null +++ b/lib/models/buy/response_objects/quote.dart @@ -0,0 +1,26 @@ +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/models/buy/response_objects/crypto.dart'; +import 'package:stackwallet/models/buy/response_objects/fiat.dart'; + +class SimplexQuote { + final Crypto crypto; + final Fiat fiat; + + late final Decimal youPayFiatPrice; + late final Decimal youReceiveCryptoAmount; + + late final String id; + late final String receivingAddress; + + late final bool buyWithFiat; + + SimplexQuote({ + required this.crypto, + required this.fiat, + required this.youPayFiatPrice, + required this.youReceiveCryptoAmount, + required this.id, + required this.receivingAddress, + required this.buyWithFiat, + }); +} diff --git a/lib/models/buy/simplex/simplex.dart b/lib/models/buy/simplex/simplex.dart new file mode 100644 index 000000000..bb75036ab --- /dev/null +++ b/lib/models/buy/simplex/simplex.dart @@ -0,0 +1,51 @@ +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/models/buy/response_objects/crypto.dart'; +import 'package:stackwallet/models/buy/response_objects/fiat.dart'; +import 'package:stackwallet/models/buy/response_objects/order.dart'; +import 'package:stackwallet/models/buy/response_objects/quote.dart'; + +class Simplex { + List supportedCryptos = []; + List supportedFiats = []; + SimplexQuote quote = SimplexQuote( + crypto: Crypto.fromJson({'ticker': 'BTC', 'name': 'Bitcoin', 'image': ''}), + fiat: Fiat.fromJson( + {'ticker': 'USD', 'name': 'United States Dollar', 'image': ''}), + youPayFiatPrice: Decimal.parse("100"), + youReceiveCryptoAmount: Decimal.parse("1.0238917"), + id: "someID", + receivingAddress: '', + buyWithFiat: true, + ); + SimplexOrder order = SimplexOrder( + quote: SimplexQuote( + crypto: + Crypto.fromJson({'ticker': 'BTC', 'name': 'Bitcoin', 'image': ''}), + fiat: Fiat.fromJson( + {'ticker': 'USD', 'name': 'United States Dollar', 'image': ''}), + youPayFiatPrice: Decimal.parse("100"), + youReceiveCryptoAmount: Decimal.parse("1.0238917"), + id: "someID", + receivingAddress: '', + buyWithFiat: true, + ), + orderId: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', + paymentId: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee', + userId: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'); + + void updateSupportedCryptos(List newCryptos) { + supportedCryptos = newCryptos; + } + + void updateSupportedFiats(List newFiats) { + supportedFiats = newFiats; + } + + void updateQuote(SimplexQuote newQuote) { + quote = newQuote; + } + + void updateOrder(SimplexOrder newOrder) { + order = newOrder; + } +} diff --git a/lib/models/contact.dart b/lib/models/contact.dart index 93ff779bb..37961476a 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:stackwallet/models/contact_address_entry.dart'; import 'package:uuid/uuid.dart'; +@Deprecated("Use lib/models/isar/models/contact_entry.dart instead") class Contact { final String? emojiChar; final String name; diff --git a/lib/models/contact_address_entry.dart b/lib/models/contact_address_entry.dart index ce85228b6..d917b47c0 100644 --- a/lib/models/contact_address_entry.dart +++ b/lib/models/contact_address_entry.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; +@Deprecated("Use lib/models/isar/models/contact_entry.dart instead") class ContactAddressEntry { final Coin coin; final String address; diff --git a/lib/models/contact_address_entry_data.dart b/lib/models/contact_address_entry_data.dart index 25677b13d..b67299a3e 100644 --- a/lib/models/contact_address_entry_data.dart +++ b/lib/models/contact_address_entry_data.dart @@ -1,5 +1,5 @@ import 'package:flutter/cupertino.dart'; -import 'package:stackwallet/models/contact_address_entry.dart'; +import 'package:stackwallet/models/isar/models/contact_entry.dart'; import 'package:stackwallet/utilities/address_utils.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; @@ -57,15 +57,21 @@ class AddressEntryData extends ChangeNotifier { } bool get isValidAddress { - if (_address == null || coin == null) { + if (coin == null) { + return true; + } + if (_address == null) { return false; } return AddressUtils.validateAddress(_address!, _coin!); } ContactAddressEntry buildAddressEntry() { - return ContactAddressEntry( - coin: coin!, address: address!, label: addressLabel!); + return ContactAddressEntry() + ..coinName = coin!.name + ..address = address! + ..other = null + ..label = addressLabel!; } @override diff --git a/lib/models/epicbox_config_model.dart b/lib/models/epicbox_config_model.dart new file mode 100644 index 000000000..7927fd165 --- /dev/null +++ b/lib/models/epicbox_config_model.dart @@ -0,0 +1,121 @@ +import 'dart:convert'; + +import 'package:hive/hive.dart'; +import 'package:stackwallet/models/epicbox_server_model.dart'; + +part 'type_adaptors/epicbox_config_model.g.dart'; + +// @HiveType(typeId: 72) +class EpicBoxConfigModel { + // @HiveField(1) + final String host; + // @HiveField(2) + final int? port; + // @HiveField(3) + final bool? protocolInsecure; + // @HiveField(4) + final int? addressIndex; + // // @HiveField(5) + // final String? id; + // // @HiveField(6) + // final String? name; + + EpicBoxConfigModel({ + required this.host, + this.port, + this.protocolInsecure, + this.addressIndex, + // this.id, + // this.name, + }); + + EpicBoxConfigModel copyWith({ + int? port, + bool? protocolInsecure, + int? addressIndex, + // String? id, + // String? name, + }) { + return EpicBoxConfigModel( + host: host, + port: this.port ?? 443, + protocolInsecure: this.protocolInsecure ?? false, + addressIndex: this.addressIndex ?? 0, + // id: id ?? this.id, + // name: name ?? this.name, + ); + } + + Map toMap() { + Map map = {}; + map['epicbox_domain'] = host; + map['epicbox_port'] = port; + map['epicbox_protocol_insecure'] = protocolInsecure; + map['epicbox_address_index'] = addressIndex; + // map['id'] = id; + // map['name'] = name; + return map; + } + + Map toJson() { + return { + 'epicbox_domain': host, + 'epicbox_port': port, + 'epicbox_protocol_insecure': protocolInsecure, + 'epicbox_address_index': addressIndex, + // 'id': id, + // 'name': name, + }; + } + + @override + String toString() { + return json.encode(toJson()); + } + + static EpicBoxConfigModel fromString(String epicBoxConfigString) { + dynamic _epicBox = json.decode(epicBoxConfigString); + + // handle old epicbox config formats + final oldDomain = _epicBox["domain"] ?? "empty"; + if (oldDomain != "empty") { + _epicBox['epicbox_domain'] = _epicBox['domain']; + } + final oldPort = _epicBox["port"] ?? "empty"; + if (oldPort != "empty") { + _epicBox['epicbox_port'] = _epicBox['port']; + } + final oldProtocolInsecure = _epicBox["protocol_insecure"] ?? "empty"; + if (oldProtocolInsecure != "empty") { + _epicBox['epicbox_protocol_insecure'] = _epicBox['protocol_insecure']; + } + final oldAddressIndex = _epicBox["address_index"] ?? "empty"; + if (oldAddressIndex != "empty") { + _epicBox['epicbox_address_index'] = _epicBox['address_index']; + } + + _epicBox['epicbox_protocol_insecure'] ??= false; + _epicBox['epicbox_address_index'] ??= 0; + + return EpicBoxConfigModel( + host: _epicBox['epicbox_domain'] as String, + port: _epicBox['epicbox_port'] as int, + protocolInsecure: _epicBox['epicbox_protocol_insecure'] as bool, + addressIndex: _epicBox['epicbox_address_index'] as int, + // name: fields[5] as String, + // id: fields[6] as String, + ); + } + + static EpicBoxConfigModel fromServer(EpicBoxServerModel server, + {bool? protocolInsecure, int? addressIndex}) { + return EpicBoxConfigModel( + host: server.host, + port: server.port ?? 443, + protocolInsecure: protocolInsecure ?? false, + addressIndex: addressIndex ?? 0, + // name: fields[5] as String, + // id: fields[6] as String, + ); + } +} diff --git a/lib/models/epicbox_server_model.dart b/lib/models/epicbox_server_model.dart new file mode 100644 index 000000000..8bb431348 --- /dev/null +++ b/lib/models/epicbox_server_model.dart @@ -0,0 +1,83 @@ +import 'package:hive/hive.dart'; + +part 'type_adaptors/epicbox_server_model.g.dart'; + +// @HiveType(typeId: 71) +class EpicBoxServerModel { + // @HiveField(0) + final String id; + // @HiveField(1) + final String host; + // @HiveField(2) + final int? port; + // @HiveField(3) + final String name; + // @HiveField(4) + final bool? useSSL; + // @HiveField(5) + final bool? enabled; + // @HiveField(6) + final bool? isFailover; + // @HiveField(7) + final bool? isDown; + + EpicBoxServerModel({ + required this.id, + required this.host, + this.port, + required this.name, + this.useSSL, + this.enabled, + this.isFailover, + this.isDown, + }); + + EpicBoxServerModel copyWith({ + String? host, + int? port, + String? name, + bool? useSSL, + bool? enabled, + bool? isFailover, + bool? isDown, + }) { + return EpicBoxServerModel( + id: id, + host: host ?? this.host, + port: port ?? this.port, + name: name ?? this.name, + useSSL: useSSL ?? this.useSSL, + enabled: enabled ?? this.enabled, + isFailover: isFailover ?? this.isFailover, + isDown: isDown ?? this.isDown, + ); + } + + Map toMap() { + Map map = {}; + map['id'] = id; + map['host'] = host; + map['port'] = port; + map['name'] = name; + map['useSSL'] = useSSL; + map['enabled'] = enabled; + map['isFailover'] = isFailover; + map['isDown'] = isDown; + return map; + } + + bool get isDefault => id.startsWith("default_"); + + Map toJson() { + return { + 'id': id, + 'host': host, + 'port': port, + 'name': name, + 'useSSL': useSSL, + 'enabled': enabled, + 'isFailover': isFailover, + 'isDown': isDown, + }; + } +} diff --git a/lib/models/exchange/active_pair.dart b/lib/models/exchange/active_pair.dart new file mode 100644 index 000000000..4a2e80eba --- /dev/null +++ b/lib/models/exchange/active_pair.dart @@ -0,0 +1,35 @@ +import 'package:flutter/foundation.dart'; +import 'package:stackwallet/models/exchange/aggregate_currency.dart'; + +class ActivePair extends ChangeNotifier { + AggregateCurrency? _send; + AggregateCurrency? _receive; + + AggregateCurrency? get send => _send; + AggregateCurrency? get receive => _receive; + + void setSend( + AggregateCurrency? newSend, { + bool notifyListeners = false, + }) { + _send = newSend; + if (notifyListeners) { + this.notifyListeners(); + } + } + + void setReceive( + AggregateCurrency? newReceive, { + bool notifyListeners = false, + }) { + _receive = newReceive; + if (notifyListeners) { + this.notifyListeners(); + } + } + + @override + String toString() { + return "ActivePair{ send: $send, receive: $receive }"; + } +} diff --git a/lib/models/exchange/aggregate_currency.dart b/lib/models/exchange/aggregate_currency.dart new file mode 100644 index 000000000..1bbc767a5 --- /dev/null +++ b/lib/models/exchange/aggregate_currency.dart @@ -0,0 +1,41 @@ +import 'package:stackwallet/models/isar/exchange_cache/currency.dart'; +import 'package:stackwallet/models/isar/exchange_cache/pair.dart'; +import 'package:tuple/tuple.dart'; + +class AggregateCurrency { + final Map _map = {}; + + AggregateCurrency({ + required List> exchangeCurrencyPairs, + }) { + assert(exchangeCurrencyPairs.isNotEmpty); + + for (final item in exchangeCurrencyPairs) { + _map[item.item1] = item.item2; + } + } + + Currency? forExchange(String exchangeName) { + return _map[exchangeName]; + } + + String get ticker => _map.values.first!.ticker; + + String get name => _map.values.first!.name; + + String get image => _map.values.first!.image; + + SupportedRateType get rateType => _map.values.first!.rateType; + + bool get isStackCoin => _map.values.first!.isStackCoin; + + @override + String toString() { + String str = "AggregateCurrency: {"; + for (final key in _map.keys) { + str += " $key: ${_map[key]},"; + } + str += " }"; + return str; + } +} diff --git a/lib/models/exchange/change_now/cn_available_currencies.dart b/lib/models/exchange/change_now/cn_available_currencies.dart deleted file mode 100644 index aaf6a5d05..000000000 --- a/lib/models/exchange/change_now/cn_available_currencies.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:stackwallet/models/exchange/response_objects/currency.dart'; -import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; -import 'package:stackwallet/models/exchange/response_objects/pair.dart'; - -class CNAvailableCurrencies { - final List currencies = []; - final List pairs = []; - final List markets = []; - - void updateCurrencies(List newCurrencies) { - currencies.clear(); - currencies.addAll(newCurrencies); - } - - void updateFloatingPairs(List newPairs) { - pairs.clear(); - pairs.addAll(newPairs); - } - - void updateMarkets(List newMarkets) { - markets.clear(); - markets.addAll(newMarkets); - } -} diff --git a/lib/models/exchange/exchange_form_state.dart b/lib/models/exchange/exchange_form_state.dart deleted file mode 100644 index 5efa446a2..000000000 --- a/lib/models/exchange/exchange_form_state.dart +++ /dev/null @@ -1,432 +0,0 @@ -import 'package:decimal/decimal.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:stackwallet/models/exchange/response_objects/currency.dart'; -import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; -import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; -import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; -import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; -import 'package:stackwallet/services/exchange/exchange.dart'; -import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; -import 'package:stackwallet/utilities/logger.dart'; - -class ExchangeFormState extends ChangeNotifier { - Exchange? _exchange; - Exchange? get exchange => _exchange; - set exchange(Exchange? value) { - _exchange = value; - _onExchangeTypeChanged(); - } - - ExchangeRateType _exchangeType = ExchangeRateType.estimated; - ExchangeRateType get exchangeType => _exchangeType; - set exchangeType(ExchangeRateType value) { - _exchangeType = value; - _onExchangeRateTypeChanged(); - } - - bool reversed = false; - - Decimal? fromAmount; - Decimal? toAmount; - - Decimal? minAmount; - Decimal? maxAmount; - - Decimal? rate; - Estimate? estimate; - - FixedRateMarket? _market; - FixedRateMarket? get market => _market; - - Currency? _from; - Currency? _to; - - @override - String toString() { - return 'ExchangeFormState: {_exchange: $_exchange, _exchangeType: $_exchangeType, reversed: $reversed, fromAmount: $fromAmount, toAmount: $toAmount, minAmount: $minAmount, maxAmount: $maxAmount, rate: $rate, estimate: $estimate, _market: $_market, _from: $_from, _to: $_to, _onError: $_onError}'; - } - - String? get fromTicker { - switch (exchangeType) { - case ExchangeRateType.estimated: - return _from?.ticker; - case ExchangeRateType.fixed: - switch (exchange?.name) { - case SimpleSwapExchange.exchangeName: - return _from?.ticker; - case ChangeNowExchange.exchangeName: - return market?.from; - default: - return null; - } - } - } - - String? get toTicker { - switch (exchangeType) { - case ExchangeRateType.estimated: - return _to?.ticker; - case ExchangeRateType.fixed: - switch (exchange?.name) { - case SimpleSwapExchange.exchangeName: - return _to?.ticker; - case ChangeNowExchange.exchangeName: - return market?.to; - default: - return null; - } - } - } - - void Function(String)? _onError; - - Currency? get from => _from; - Currency? get to => _to; - - void setCurrencies(Currency from, Currency to) { - _from = from; - _to = to; - } - - String get warning { - if (reversed) { - if (toTicker != null && toAmount != null) { - if (minAmount != null && - toAmount! < minAmount! && - toAmount! > Decimal.zero) { - return "Minimum amount ${minAmount!.toString()} ${toTicker!.toUpperCase()}"; - } else if (maxAmount != null && toAmount! > maxAmount!) { - return "Maximum amount ${maxAmount!.toString()} ${toTicker!.toUpperCase()}"; - } - } - } else { - if (fromTicker != null && fromAmount != null) { - if (minAmount != null && - fromAmount! < minAmount! && - fromAmount! > Decimal.zero) { - return "Minimum amount ${minAmount!.toString()} ${fromTicker!.toUpperCase()}"; - } else if (maxAmount != null && fromAmount! > maxAmount!) { - return "Maximum amount ${maxAmount!.toString()} ${fromTicker!.toUpperCase()}"; - } - } - } - - return ""; - } - - String get fromAmountString => fromAmount?.toStringAsFixed(8) ?? ""; - String get toAmountString => toAmount?.toStringAsFixed(8) ?? ""; - - bool get canExchange { - if (exchange?.name == ChangeNowExchange.exchangeName && - exchangeType == ExchangeRateType.fixed) { - return _market != null && - fromAmount != null && - toAmount != null && - warning.isEmpty; - } else { - return fromAmount != null && - fromAmount != Decimal.zero && - toAmount != null && - rate != null && - warning.isEmpty; - } - } - - void clearAmounts(bool shouldNotifyListeners) { - fromAmount = null; - toAmount = null; - minAmount = null; - maxAmount = null; - rate = null; - - if (shouldNotifyListeners) { - notifyListeners(); - } - } - - Future setFromAmountAndCalculateToAmount( - Decimal newFromAmount, - bool shouldNotifyListeners, - ) async { - if (newFromAmount == Decimal.zero) { - toAmount = Decimal.zero; - } - - fromAmount = newFromAmount; - reversed = false; - - await updateRanges(shouldNotifyListeners: false); - - await updateEstimate( - shouldNotifyListeners: false, - reversed: reversed, - ); - - if (shouldNotifyListeners) { - notifyListeners(); - } - } - - Future setToAmountAndCalculateFromAmount( - Decimal newToAmount, - bool shouldNotifyListeners, - ) async { - if (newToAmount == Decimal.zero) { - fromAmount = Decimal.zero; - } - - toAmount = newToAmount; - reversed = true; - - await updateRanges(shouldNotifyListeners: false); - - await updateEstimate( - shouldNotifyListeners: false, - reversed: reversed, - ); - - if (shouldNotifyListeners) { - notifyListeners(); - } - } - - Future updateTo(Currency to, bool shouldNotifyListeners) async { - try { - _to = to; - if (_from == null) { - rate = null; - notifyListeners(); - return; - } - - await updateRanges(shouldNotifyListeners: false); - - await updateEstimate( - shouldNotifyListeners: false, - reversed: reversed, - ); - - //todo: check if print needed - // debugPrint( - // "_updated TO: _from=${_from!.ticker} _to=${_to!.ticker} _fromAmount=$fromAmount _toAmount=$toAmount rate:$rate for: $exchange"); - - if (shouldNotifyListeners) { - notifyListeners(); - } - } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Error); - } - } - - Future updateFrom(Currency from, bool shouldNotifyListeners) async { - try { - _from = from; - - if (_to == null) { - rate = null; - notifyListeners(); - return; - } - - await updateRanges(shouldNotifyListeners: false); - - await updateEstimate( - shouldNotifyListeners: false, - reversed: reversed, - ); - - //todo: check if print needed - // debugPrint( - // "_updated FROM: _from=${_from!.ticker} _to=${_to!.ticker} _fromAmount=$fromAmount _toAmount=$toAmount rate:$rate for: $exchange"); - if (shouldNotifyListeners) { - notifyListeners(); - } - } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Error); - } - } - - Future updateMarket( - FixedRateMarket? market, - bool shouldNotifyListeners, - ) async { - _market = market; - - if (_market == null) { - fromAmount = null; - toAmount = null; - } else { - if (fromAmount != null) { - if (fromAmount! <= Decimal.zero) { - toAmount = Decimal.zero; - } else { - await updateRanges(shouldNotifyListeners: false); - await updateEstimate( - shouldNotifyListeners: false, - reversed: reversed, - ); - } - } - } - - if (shouldNotifyListeners) { - notifyListeners(); - } - } - - void _onExchangeRateTypeChanged() { - print("_onExchangeRateTypeChanged"); - updateRanges(shouldNotifyListeners: true).then( - (_) => updateEstimate( - shouldNotifyListeners: true, - reversed: reversed, - ), - ); - } - - void _onExchangeTypeChanged() { - updateRanges(shouldNotifyListeners: true).then( - (_) => updateEstimate( - shouldNotifyListeners: true, - reversed: reversed, - ), - ); - } - - Future updateRanges({required bool shouldNotifyListeners}) async { - if (exchange?.name == SimpleSwapExchange.exchangeName) { - reversed = false; - } - final _fromTicker = reversed ? toTicker : fromTicker; - final _toTicker = reversed ? fromTicker : toTicker; - if (_fromTicker == null || _toTicker == null) { - Logging.instance.log( - "Tried to $runtimeType.updateRanges where (from: $_fromTicker || to: $_toTicker) for: $exchange", - level: LogLevel.Info, - ); - return; - } - final response = await exchange?.getRange( - _fromTicker, - _toTicker, - exchangeType == ExchangeRateType.fixed, - ); - - if (response?.value == null) { - Logging.instance.log( - "Tried to $runtimeType.updateRanges for: $exchange where response: $response", - level: LogLevel.Info, - ); - return; - } - - final range = response!.value!; - - minAmount = range.min; - maxAmount = range.max; - - //todo: check if print needed - // debugPrint( - // "updated range for: $exchange for $_fromTicker-$_toTicker: $range"); - - if (shouldNotifyListeners) { - notifyListeners(); - } - } - - Future updateEstimate({ - required bool shouldNotifyListeners, - required bool reversed, - }) async { - if (exchange?.name == SimpleSwapExchange.exchangeName) { - reversed = false; - } - final amount = reversed ? toAmount : fromAmount; - if (fromTicker == null || - toTicker == null || - amount == null || - amount <= Decimal.zero) { - Logging.instance.log( - "Tried to $runtimeType.updateEstimate for: $exchange where (from: $fromTicker || to: $toTicker || amount: $amount)", - level: LogLevel.Info, - ); - return; - } - final response = await exchange?.getEstimate( - fromTicker!, - toTicker!, - amount, - exchangeType == ExchangeRateType.fixed, - reversed, - ); - - if (response?.value == null) { - Logging.instance.log( - "Tried to $runtimeType.updateEstimate for: $exchange where response: $response", - level: LogLevel.Info, - ); - return; - } - - estimate = response!.value!; - - if (reversed) { - fromAmount = estimate!.estimatedAmount; - } else { - toAmount = estimate!.estimatedAmount; - } - - rate = (toAmount! / fromAmount!).toDecimal(scaleOnInfinitePrecision: 12); - - //todo: check if print needed - // debugPrint( - // "updated estimate for: $exchange for $fromTicker-$toTicker: $estimate"); - - if (shouldNotifyListeners) { - notifyListeners(); - } - } - - void setOnError({ - required void Function(String)? onError, - bool shouldNotifyListeners = false, - }) { - _onError = onError; - if (shouldNotifyListeners) { - notifyListeners(); - } - } - - Future swap({FixedRateMarket? market}) async { - final Decimal? newToAmount = fromAmount; - final Decimal? newFromAmount = toAmount; - - fromAmount = newFromAmount; - toAmount = newToAmount; - - minAmount = null; - maxAmount = null; - - if (exchangeType == ExchangeRateType.fixed && - exchange?.name == ChangeNowExchange.exchangeName) { - await updateMarket(market, false); - } else { - final Currency? newTo = from; - final Currency? newFrom = to; - - _to = newTo; - _from = newFrom; - - await updateRanges(shouldNotifyListeners: false); - - await updateEstimate( - shouldNotifyListeners: false, - reversed: reversed, - ); - } - - notifyListeners(); - } -} diff --git a/lib/models/exchange/incomplete_exchange.dart b/lib/models/exchange/incomplete_exchange.dart index 46e7ffd68..2680b24e0 100644 --- a/lib/models/exchange/incomplete_exchange.dart +++ b/lib/models/exchange/incomplete_exchange.dart @@ -1,7 +1,8 @@ import 'package:decimal/decimal.dart'; import 'package:flutter/foundation.dart'; +import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; -import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; +import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart'; class IncompleteExchangeModel extends ChangeNotifier { final String sendTicker; @@ -15,6 +16,7 @@ class IncompleteExchangeModel extends ChangeNotifier { final ExchangeRateType rateType; final bool reversed; + final bool walletInitiated; String? _recipientAddress; @@ -38,13 +40,13 @@ class IncompleteExchangeModel extends ChangeNotifier { } } - String? _rateId; + Estimate? _estimate; - String? get rateId => _rateId; + Estimate? get estimate => _estimate; - set rateId(String? rateId) { - if (_rateId != rateId) { - _rateId = rateId; + set estimate(Estimate? estimate) { + if (_estimate != estimate) { + _estimate = estimate; notifyListeners(); } } @@ -68,6 +70,7 @@ class IncompleteExchangeModel extends ChangeNotifier { required this.receiveAmount, required this.rateType, required this.reversed, - String? rateId, - }) : _rateId = rateId; + required this.walletInitiated, + Estimate? estimate, + }) : _estimate = estimate; } diff --git a/lib/models/exchange/majestic_bank/mb_limit.dart b/lib/models/exchange/majestic_bank/mb_limit.dart new file mode 100644 index 000000000..baa002d56 --- /dev/null +++ b/lib/models/exchange/majestic_bank/mb_limit.dart @@ -0,0 +1,19 @@ +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/models/exchange/majestic_bank/mb_object.dart'; + +class MBLimit extends MBObject { + MBLimit({ + required this.currency, + required this.min, + required this.max, + }); + + final String currency; + final Decimal min; + final Decimal max; + + @override + String toString() { + return "MBLimit: { $currency: { min: $min, max: $max } }"; + } +} diff --git a/lib/models/exchange/majestic_bank/mb_object.dart b/lib/models/exchange/majestic_bank/mb_object.dart new file mode 100644 index 000000000..e3810131c --- /dev/null +++ b/lib/models/exchange/majestic_bank/mb_object.dart @@ -0,0 +1 @@ +abstract class MBObject {} diff --git a/lib/models/exchange/majestic_bank/mb_order.dart b/lib/models/exchange/majestic_bank/mb_order.dart new file mode 100644 index 000000000..f5dde038e --- /dev/null +++ b/lib/models/exchange/majestic_bank/mb_order.dart @@ -0,0 +1,43 @@ +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/models/exchange/majestic_bank/mb_object.dart'; + +enum MBOrderType { + fixed, + floating, +} + +class MBOrder extends MBObject { + MBOrder({ + required this.orderId, + required this.fromCurrency, + required this.fromAmount, + required this.receiveCurrency, + required this.receiveAmount, + required this.address, + required this.orderType, + required this.expiration, + required this.createdAt, + }); + + final String orderId; + final String fromCurrency; + final Decimal fromAmount; + final String receiveCurrency; + final String address; + final Decimal receiveAmount; + final MBOrderType orderType; + + /// minutes + final int expiration; + + final DateTime createdAt; + + bool isExpired() => + (DateTime.now().difference(createdAt) >= Duration(minutes: expiration)); + + @override + String toString() { + // todo: full toString + return orderId; + } +} diff --git a/lib/models/exchange/majestic_bank/mb_order_calculation.dart b/lib/models/exchange/majestic_bank/mb_order_calculation.dart new file mode 100644 index 000000000..931ca440f --- /dev/null +++ b/lib/models/exchange/majestic_bank/mb_order_calculation.dart @@ -0,0 +1,21 @@ +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/models/exchange/majestic_bank/mb_object.dart'; + +class MBOrderCalculation extends MBObject { + MBOrderCalculation({ + required this.fromCurrency, + required this.fromAmount, + required this.receiveCurrency, + required this.receiveAmount, + }); + + final String fromCurrency; + final Decimal fromAmount; + final String receiveCurrency; + final Decimal receiveAmount; + + @override + String toString() { + return "MBOrderCalculation: { $fromCurrency: $fromAmount, $receiveCurrency: $receiveAmount }"; + } +} diff --git a/lib/models/exchange/majestic_bank/mb_order_status.dart b/lib/models/exchange/majestic_bank/mb_order_status.dart new file mode 100644 index 000000000..030fe1ddf --- /dev/null +++ b/lib/models/exchange/majestic_bank/mb_order_status.dart @@ -0,0 +1,41 @@ +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/models/exchange/majestic_bank/mb_object.dart'; + +class MBOrderStatus extends MBObject { + MBOrderStatus({ + required this.orderId, + required this.status, + required this.fromCurrency, + required this.fromAmount, + required this.receiveCurrency, + required this.receiveAmount, + required this.address, + required this.received, + required this.confirmed, + }); + + final String orderId; + final String status; + final String fromCurrency; + final Decimal fromAmount; + final String receiveCurrency; + final Decimal receiveAmount; + final String address; + final Decimal received; + final Decimal confirmed; + + Map toMap() => { + "orderId": orderId, + "status": status, + "fromCurrency": fromCurrency, + "fromAmount": fromAmount, + "receiveCurrency": receiveCurrency, + "receiveAmount": receiveAmount, + "address": address, + "received": received, + "confirmed": confirmed, + }; + + @override + String toString() => toMap().toString(); +} diff --git a/lib/models/exchange/majestic_bank/mb_rate.dart b/lib/models/exchange/majestic_bank/mb_rate.dart new file mode 100644 index 000000000..60d71cdf0 --- /dev/null +++ b/lib/models/exchange/majestic_bank/mb_rate.dart @@ -0,0 +1,15 @@ +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/models/exchange/majestic_bank/mb_object.dart'; + +class MBRate extends MBObject { + MBRate({required this.fromCurrency, required this.toCurrency, required this.rate,}); + + final String fromCurrency; + final String toCurrency; + final Decimal rate; + + @override + String toString() { + return "MBRate: { $fromCurrency-$toCurrency: $rate }"; + } +} diff --git a/lib/models/exchange/response_objects/currency.dart b/lib/models/exchange/response_objects/currency.dart deleted file mode 100644 index 0850f9a38..000000000 --- a/lib/models/exchange/response_objects/currency.dart +++ /dev/null @@ -1,123 +0,0 @@ -class Currency { - /// Currency ticker - final String ticker; - - /// Currency name - final String name; - - /// Currency network - final String network; - - /// Currency logo url - final String image; - - /// Indicates if a currency has an Extra ID - final bool hasExternalId; - - /// external id if it exists - final String? externalId; - - /// Indicates if a currency is a fiat currency (EUR, USD) - final bool isFiat; - - /// Indicates if a currency is popular - final bool featured; - - /// Indicates if a currency is stable - final bool isStable; - - /// Indicates if a currency is available on a fixed-rate flow - final bool supportsFixedRate; - - /// (Optional - based on api call) Indicates whether the pair is - /// currently supported by change now - final bool? isAvailable; - - Currency({ - required this.ticker, - required this.name, - required this.network, - required this.image, - required this.hasExternalId, - this.externalId, - required this.isFiat, - required this.featured, - required this.isStable, - required this.supportsFixedRate, - this.isAvailable, - }); - - factory Currency.fromJson(Map json) { - try { - return Currency( - ticker: json["ticker"] as String, - name: json["name"] as String, - network: json["network"] as String? ?? "", - image: json["image"] as String, - hasExternalId: json["hasExternalId"] as bool, - externalId: json["externalId"] as String?, - isFiat: json["isFiat"] as bool, - featured: json["featured"] as bool, - isStable: json["isStable"] as bool, - supportsFixedRate: json["supportsFixedRate"] as bool, - isAvailable: json["isAvailable"] as bool?, - ); - } catch (e) { - rethrow; - } - } - - Map toJson() { - final map = { - "ticker": ticker, - "name": name, - "network": network, - "image": image, - "hasExternalId": hasExternalId, - "externalId": externalId, - "isFiat": isFiat, - "featured": featured, - "isStable": isStable, - "supportsFixedRate": supportsFixedRate, - }; - - if (isAvailable != null) { - map["isAvailable"] = isAvailable!; - } - - return map; - } - - Currency copyWith({ - String? ticker, - String? name, - String? network, - String? image, - bool? hasExternalId, - String? externalId, - bool? isFiat, - bool? featured, - bool? isStable, - bool? supportsFixedRate, - bool? isAvailable, - }) { - return Currency( - ticker: ticker ?? this.ticker, - name: name ?? this.name, - network: network ?? this.network, - image: image ?? this.image, - hasExternalId: hasExternalId ?? this.hasExternalId, - externalId: externalId ?? this.externalId, - isFiat: isFiat ?? this.isFiat, - featured: featured ?? this.featured, - isStable: isStable ?? this.isStable, - supportsFixedRate: supportsFixedRate ?? this.supportsFixedRate, - isAvailable: isAvailable ?? this.isAvailable, - ); - } - - @override - String toString() { - return "Currency: ${toJson()}"; - } -} diff --git a/lib/models/exchange/response_objects/estimate.dart b/lib/models/exchange/response_objects/estimate.dart index 7df490079..9284c8340 100644 --- a/lib/models/exchange/response_objects/estimate.dart +++ b/lib/models/exchange/response_objects/estimate.dart @@ -7,6 +7,8 @@ class Estimate { final bool reversed; final String? warningMessage; final String? rateId; + final String exchangeProvider; + final String? kycRating; Estimate({ required this.estimatedAmount, @@ -14,9 +16,15 @@ class Estimate { required this.reversed, this.warningMessage, this.rateId, + required this.exchangeProvider, + this.kycRating, }); - factory Estimate.fromMap(Map map) { + factory Estimate.fromMap( + Map map, { + required String exchangeProvider, + String? kycRating, + }) { try { return Estimate( estimatedAmount: Decimal.parse(map["estimatedAmount"] as String), @@ -24,6 +32,8 @@ class Estimate { reversed: map["reversed"] as bool, warningMessage: map["warningMessage"] as String?, rateId: map["rateId"] as String?, + exchangeProvider: exchangeProvider, + kycRating: kycRating, ); } catch (e, s) { Logging.instance.log("Estimate.fromMap(): $e\n$s", level: LogLevel.Error); @@ -38,6 +48,8 @@ class Estimate { "reversed": reversed, "warningMessage": warningMessage, "rateId": rateId, + "exchangeProvider": exchangeProvider, + "kycRating": kycRating, }; } diff --git a/lib/models/exchange/response_objects/pair.dart b/lib/models/exchange/response_objects/pair.dart deleted file mode 100644 index 1f12acf08..000000000 --- a/lib/models/exchange/response_objects/pair.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'dart:ui'; - -import 'package:stackwallet/utilities/logger.dart'; - -class Pair { - final String from; - final String fromNetwork; - - final String to; - final String toNetwork; - - final bool fixedRate; - final bool floatingRate; - - Pair({ - required this.from, - required this.fromNetwork, - required this.to, - required this.toNetwork, - required this.fixedRate, - required this.floatingRate, - }); - - factory Pair.fromMap(Map map) { - try { - return Pair( - from: map["from"] as String, - fromNetwork: map["fromNetwork"] as String, - to: map["to"] as String, - toNetwork: map["toNetwork"] as String, - fixedRate: map["fixedRate"] as bool, - floatingRate: map["floatingRate"] as bool, - ); - } catch (e, s) { - Logging.instance.log("Pair.fromMap(): $e\n$s", level: LogLevel.Error); - rethrow; - } - } - - Map toMap() { - return { - "from": from, - "fromNetwork": fromNetwork, - "to": to, - "toNetwork": toNetwork, - "fixedRate": fixedRate, - "floatingRate": floatingRate, - }; - } - - @override - bool operator ==(other) => - other is Pair && - from == other.from && - fromNetwork == other.fromNetwork && - to == other.to && - toNetwork == other.toNetwork && - fixedRate == other.fixedRate && - floatingRate == other.floatingRate; - - @override - int get hashCode => hashValues( - from, - fromNetwork, - to, - toNetwork, - fixedRate, - floatingRate, - ); - - @override - String toString() => "Pair: ${toMap()}"; -} diff --git a/lib/models/exchange/simpleswap/sp_available_currencies.dart b/lib/models/exchange/simpleswap/sp_available_currencies.dart deleted file mode 100644 index 787ad88ab..000000000 --- a/lib/models/exchange/simpleswap/sp_available_currencies.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:stackwallet/models/exchange/response_objects/currency.dart'; -import 'package:stackwallet/models/exchange/response_objects/pair.dart'; - -class SPAvailableCurrencies { - final List floatingRateCurrencies = []; - final List fixedRateCurrencies = []; - - final List floatingRatePairs = []; - final List fixedRatePairs = []; - - void updateFloatingCurrencies(List newCurrencies) { - floatingRateCurrencies.clear(); - floatingRateCurrencies.addAll(newCurrencies); - } - - void updateFixedCurrencies(List newCurrencies) { - fixedRateCurrencies.clear(); - fixedRateCurrencies.addAll(newCurrencies); - } - - void updateFloatingPairs(List newPairs) { - floatingRatePairs.clear(); - floatingRatePairs.addAll(newPairs); - } - - void updateFixedPairs(List newPairs) { - fixedRatePairs.clear(); - fixedRatePairs.addAll(newPairs); - } -} diff --git a/lib/models/isar/exchange_cache/currency.dart b/lib/models/isar/exchange_cache/currency.dart new file mode 100644 index 000000000..1744f9350 --- /dev/null +++ b/lib/models/isar/exchange_cache/currency.dart @@ -0,0 +1,160 @@ +import 'package:isar/isar.dart'; +import 'package:stackwallet/models/isar/exchange_cache/pair.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; + +part 'currency.g.dart'; + +@Collection(accessor: "currencies") +class Currency { + Id? id; + + @Index() + final String exchangeName; + + /// Currency ticker + @Index(composite: [ + CompositeIndex("exchangeName"), + CompositeIndex("name"), + ]) + final String ticker; + + /// Currency name + final String name; + + /// Currency network + final String network; + + /// Currency logo url + final String image; + + /// external id if it exists + final String? externalId; + + /// Indicates if a currency is a fiat currency (EUR, USD) + final bool isFiat; + + /// Indicates if a currency is available on a fixed-rate flow + @enumerated + final SupportedRateType rateType; + + /// (Optional - based on api call) Indicates whether the pair is + /// currently supported by change now + final bool? isAvailable; + + @Index() + final bool isStackCoin; + + final String? tokenContract; + + @ignore + bool get supportsFixedRate => + rateType == SupportedRateType.fixed || rateType == SupportedRateType.both; + + @ignore + bool get supportsEstimatedRate => + rateType == SupportedRateType.estimated || + rateType == SupportedRateType.both; + + Currency({ + required this.exchangeName, + required this.ticker, + required this.name, + required this.network, + required this.image, + this.externalId, + required this.isFiat, + required this.rateType, + this.isAvailable, + required this.isStackCoin, + required this.tokenContract, + }); + + factory Currency.fromJson( + Map json, { + required String exchangeName, + required SupportedRateType rateType, + }) { + try { + final ticker = (json["ticker"] as String).toUpperCase(); + + return Currency( + exchangeName: exchangeName, + ticker: ticker, + name: json["name"] as String, + network: json["network"] as String? ?? "", + image: json["image"] as String, + externalId: json["externalId"] as String?, + isFiat: json["isFiat"] as bool, + rateType: rateType, + isAvailable: json["isAvailable"] as bool?, + isStackCoin: + json["isStackCoin"] as bool? ?? Currency.checkIsStackCoin(ticker), + tokenContract: json["tokenContract"] as String?, + )..id = json["id"] as int?; + } catch (e) { + rethrow; + } + } + + Map toJson() { + final map = { + "id": id, + "exchangeName": exchangeName, + "ticker": ticker, + "name": name, + "network": network, + "image": image, + "externalId": externalId, + "isFiat": isFiat, + "rateType": rateType, + "isAvailable": isAvailable, + "isStackCoin": isStackCoin, + "tokenContract": tokenContract, + }; + + return map; + } + + Currency copyWith({ + Id? id, + String? exchangeName, + String? ticker, + String? name, + String? network, + String? image, + String? externalId, + bool? isFiat, + SupportedRateType? rateType, + bool? isAvailable, + bool? isStackCoin, + String? tokenContract, + }) { + return Currency( + exchangeName: exchangeName ?? this.exchangeName, + ticker: ticker ?? this.ticker, + name: name ?? this.name, + network: network ?? this.network, + image: image ?? this.image, + externalId: externalId ?? this.externalId, + isFiat: isFiat ?? this.isFiat, + rateType: rateType ?? this.rateType, + isAvailable: isAvailable ?? this.isAvailable, + isStackCoin: isStackCoin ?? this.isStackCoin, + tokenContract: tokenContract ?? this.tokenContract, + )..id = id ?? this.id; + } + + @override + String toString() { + return "Currency: ${toJson()}"; + } + + static bool checkIsStackCoin(String ticker) { + try { + coinFromTickerCaseInsensitive(ticker); + return true; + } catch (_) { + return false; + } + } +} diff --git a/lib/models/isar/exchange_cache/currency.g.dart b/lib/models/isar/exchange_cache/currency.g.dart new file mode 100644 index 000000000..8f81bfdc0 --- /dev/null +++ b/lib/models/isar/exchange_cache/currency.g.dart @@ -0,0 +1,2143 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'currency.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters + +extension GetCurrencyCollection on Isar { + IsarCollection get currencies => this.collection(); +} + +const CurrencySchema = CollectionSchema( + name: r'Currency', + id: 8290149502090171821, + properties: { + r'exchangeName': PropertySchema( + id: 0, + name: r'exchangeName', + type: IsarType.string, + ), + r'externalId': PropertySchema( + id: 1, + name: r'externalId', + type: IsarType.string, + ), + r'image': PropertySchema( + id: 2, + name: r'image', + type: IsarType.string, + ), + r'isAvailable': PropertySchema( + id: 3, + name: r'isAvailable', + type: IsarType.bool, + ), + r'isFiat': PropertySchema( + id: 4, + name: r'isFiat', + type: IsarType.bool, + ), + r'isStackCoin': PropertySchema( + id: 5, + name: r'isStackCoin', + type: IsarType.bool, + ), + r'name': PropertySchema( + id: 6, + name: r'name', + type: IsarType.string, + ), + r'network': PropertySchema( + id: 7, + name: r'network', + type: IsarType.string, + ), + r'rateType': PropertySchema( + id: 8, + name: r'rateType', + type: IsarType.byte, + enumMap: _CurrencyrateTypeEnumValueMap, + ), + r'ticker': PropertySchema( + id: 9, + name: r'ticker', + type: IsarType.string, + ), + r'tokenContract': PropertySchema( + id: 10, + name: r'tokenContract', + type: IsarType.string, + ) + }, + estimateSize: _currencyEstimateSize, + serialize: _currencySerialize, + deserialize: _currencyDeserialize, + deserializeProp: _currencyDeserializeProp, + idName: r'id', + indexes: { + r'exchangeName': IndexSchema( + id: 3599278165711581955, + name: r'exchangeName', + unique: false, + replace: false, + properties: [ + IndexPropertySchema( + name: r'exchangeName', + type: IndexType.hash, + caseSensitive: true, + ) + ], + ), + r'ticker_exchangeName_name': IndexSchema( + id: 6345943517929964748, + name: r'ticker_exchangeName_name', + unique: false, + replace: false, + properties: [ + IndexPropertySchema( + name: r'ticker', + type: IndexType.hash, + caseSensitive: true, + ), + IndexPropertySchema( + name: r'exchangeName', + type: IndexType.hash, + caseSensitive: true, + ), + IndexPropertySchema( + name: r'name', + type: IndexType.hash, + caseSensitive: true, + ) + ], + ), + r'isStackCoin': IndexSchema( + id: 1994111521912746776, + name: r'isStackCoin', + unique: false, + replace: false, + properties: [ + IndexPropertySchema( + name: r'isStackCoin', + type: IndexType.value, + caseSensitive: false, + ) + ], + ) + }, + links: {}, + embeddedSchemas: {}, + getId: _currencyGetId, + getLinks: _currencyGetLinks, + attach: _currencyAttach, + version: '3.0.5', +); + +int _currencyEstimateSize( + Currency object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + bytesCount += 3 + object.exchangeName.length * 3; + { + final value = object.externalId; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + bytesCount += 3 + object.image.length * 3; + bytesCount += 3 + object.name.length * 3; + bytesCount += 3 + object.network.length * 3; + bytesCount += 3 + object.ticker.length * 3; + { + final value = object.tokenContract; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + return bytesCount; +} + +void _currencySerialize( + Currency object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeString(offsets[0], object.exchangeName); + writer.writeString(offsets[1], object.externalId); + writer.writeString(offsets[2], object.image); + writer.writeBool(offsets[3], object.isAvailable); + writer.writeBool(offsets[4], object.isFiat); + writer.writeBool(offsets[5], object.isStackCoin); + writer.writeString(offsets[6], object.name); + writer.writeString(offsets[7], object.network); + writer.writeByte(offsets[8], object.rateType.index); + writer.writeString(offsets[9], object.ticker); + writer.writeString(offsets[10], object.tokenContract); +} + +Currency _currencyDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = Currency( + exchangeName: reader.readString(offsets[0]), + externalId: reader.readStringOrNull(offsets[1]), + image: reader.readString(offsets[2]), + isAvailable: reader.readBoolOrNull(offsets[3]), + isFiat: reader.readBool(offsets[4]), + isStackCoin: reader.readBool(offsets[5]), + name: reader.readString(offsets[6]), + network: reader.readString(offsets[7]), + rateType: + _CurrencyrateTypeValueEnumMap[reader.readByteOrNull(offsets[8])] ?? + SupportedRateType.fixed, + ticker: reader.readString(offsets[9]), + tokenContract: reader.readStringOrNull(offsets[10]), + ); + object.id = id; + return object; +} + +P _currencyDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readString(offset)) as P; + case 1: + return (reader.readStringOrNull(offset)) as P; + case 2: + return (reader.readString(offset)) as P; + case 3: + return (reader.readBoolOrNull(offset)) as P; + case 4: + return (reader.readBool(offset)) as P; + case 5: + return (reader.readBool(offset)) as P; + case 6: + return (reader.readString(offset)) as P; + case 7: + return (reader.readString(offset)) as P; + case 8: + return (_CurrencyrateTypeValueEnumMap[reader.readByteOrNull(offset)] ?? + SupportedRateType.fixed) as P; + case 9: + return (reader.readString(offset)) as P; + case 10: + return (reader.readStringOrNull(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +const _CurrencyrateTypeEnumValueMap = { + 'fixed': 0, + 'estimated': 1, + 'both': 2, +}; +const _CurrencyrateTypeValueEnumMap = { + 0: SupportedRateType.fixed, + 1: SupportedRateType.estimated, + 2: SupportedRateType.both, +}; + +Id _currencyGetId(Currency object) { + return object.id ?? Isar.autoIncrement; +} + +List> _currencyGetLinks(Currency object) { + return []; +} + +void _currencyAttach(IsarCollection col, Id id, Currency object) { + object.id = id; +} + +extension CurrencyQueryWhereSort on QueryBuilder { + QueryBuilder anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } + + QueryBuilder anyIsStackCoin() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + const IndexWhereClause.any(indexName: r'isStackCoin'), + ); + }); + } +} + +extension CurrencyQueryWhere on QueryBuilder { + QueryBuilder idEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: id, + upper: id, + )); + }); + } + + QueryBuilder idNotEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder idGreaterThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder idLessThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder exchangeNameEqualTo( + String exchangeName) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'exchangeName', + value: [exchangeName], + )); + }); + } + + QueryBuilder exchangeNameNotEqualTo( + String exchangeName) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'exchangeName', + lower: [], + upper: [exchangeName], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'exchangeName', + lower: [exchangeName], + includeLower: false, + upper: [], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'exchangeName', + lower: [exchangeName], + includeLower: false, + upper: [], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'exchangeName', + lower: [], + upper: [exchangeName], + includeUpper: false, + )); + } + }); + } + + QueryBuilder + tickerEqualToAnyExchangeNameName(String ticker) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'ticker_exchangeName_name', + value: [ticker], + )); + }); + } + + QueryBuilder + tickerNotEqualToAnyExchangeNameName(String ticker) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'ticker_exchangeName_name', + lower: [], + upper: [ticker], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'ticker_exchangeName_name', + lower: [ticker], + includeLower: false, + upper: [], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'ticker_exchangeName_name', + lower: [ticker], + includeLower: false, + upper: [], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'ticker_exchangeName_name', + lower: [], + upper: [ticker], + includeUpper: false, + )); + } + }); + } + + QueryBuilder + tickerExchangeNameEqualToAnyName(String ticker, String exchangeName) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'ticker_exchangeName_name', + value: [ticker, exchangeName], + )); + }); + } + + QueryBuilder + tickerEqualToExchangeNameNotEqualToAnyName( + String ticker, String exchangeName) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'ticker_exchangeName_name', + lower: [ticker], + upper: [ticker, exchangeName], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'ticker_exchangeName_name', + lower: [ticker, exchangeName], + includeLower: false, + upper: [ticker], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'ticker_exchangeName_name', + lower: [ticker, exchangeName], + includeLower: false, + upper: [ticker], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'ticker_exchangeName_name', + lower: [ticker], + upper: [ticker, exchangeName], + includeUpper: false, + )); + } + }); + } + + QueryBuilder + tickerExchangeNameNameEqualTo( + String ticker, String exchangeName, String name) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'ticker_exchangeName_name', + value: [ticker, exchangeName, name], + )); + }); + } + + QueryBuilder + tickerExchangeNameEqualToNameNotEqualTo( + String ticker, String exchangeName, String name) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'ticker_exchangeName_name', + lower: [ticker, exchangeName], + upper: [ticker, exchangeName, name], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'ticker_exchangeName_name', + lower: [ticker, exchangeName, name], + includeLower: false, + upper: [ticker, exchangeName], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'ticker_exchangeName_name', + lower: [ticker, exchangeName, name], + includeLower: false, + upper: [ticker, exchangeName], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'ticker_exchangeName_name', + lower: [ticker, exchangeName], + upper: [ticker, exchangeName, name], + includeUpper: false, + )); + } + }); + } + + QueryBuilder isStackCoinEqualTo( + bool isStackCoin) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'isStackCoin', + value: [isStackCoin], + )); + }); + } + + QueryBuilder isStackCoinNotEqualTo( + bool isStackCoin) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'isStackCoin', + lower: [], + upper: [isStackCoin], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'isStackCoin', + lower: [isStackCoin], + includeLower: false, + upper: [], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'isStackCoin', + lower: [isStackCoin], + includeLower: false, + upper: [], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'isStackCoin', + lower: [], + upper: [isStackCoin], + includeUpper: false, + )); + } + }); + } +} + +extension CurrencyQueryFilter + on QueryBuilder { + QueryBuilder exchangeNameEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'exchangeName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + exchangeNameGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'exchangeName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder exchangeNameLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'exchangeName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder exchangeNameBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'exchangeName', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + exchangeNameStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'exchangeName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder exchangeNameEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'exchangeName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder exchangeNameContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'exchangeName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder exchangeNameMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'exchangeName', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + exchangeNameIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'exchangeName', + value: '', + )); + }); + } + + QueryBuilder + exchangeNameIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'exchangeName', + value: '', + )); + }); + } + + QueryBuilder externalIdIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'externalId', + )); + }); + } + + QueryBuilder + externalIdIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'externalId', + )); + }); + } + + QueryBuilder externalIdEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'externalId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder externalIdGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'externalId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder externalIdLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'externalId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder externalIdBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'externalId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder externalIdStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'externalId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder externalIdEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'externalId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder externalIdContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'externalId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder externalIdMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'externalId', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder externalIdIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'externalId', + value: '', + )); + }); + } + + QueryBuilder + externalIdIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'externalId', + value: '', + )); + }); + } + + QueryBuilder idIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'id', + )); + }); + } + + QueryBuilder idIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'id', + )); + }); + } + + QueryBuilder idEqualTo(Id? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idGreaterThan( + Id? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idLessThan( + Id? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idBetween( + Id? lower, + Id? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder imageEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'image', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder imageGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'image', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder imageLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'image', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder imageBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'image', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder imageStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'image', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder imageEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'image', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder imageContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'image', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder imageMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'image', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder imageIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'image', + value: '', + )); + }); + } + + QueryBuilder imageIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'image', + value: '', + )); + }); + } + + QueryBuilder isAvailableIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'isAvailable', + )); + }); + } + + QueryBuilder + isAvailableIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'isAvailable', + )); + }); + } + + QueryBuilder isAvailableEqualTo( + bool? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'isAvailable', + value: value, + )); + }); + } + + QueryBuilder isFiatEqualTo( + bool value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'isFiat', + value: value, + )); + }); + } + + QueryBuilder isStackCoinEqualTo( + bool value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'isStackCoin', + value: value, + )); + }); + } + + QueryBuilder nameEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'name', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'name', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'name', + value: '', + )); + }); + } + + QueryBuilder nameIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'name', + value: '', + )); + }); + } + + QueryBuilder networkEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'network', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder networkGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'network', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder networkLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'network', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder networkBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'network', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder networkStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'network', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder networkEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'network', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder networkContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'network', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder networkMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'network', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder networkIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'network', + value: '', + )); + }); + } + + QueryBuilder networkIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'network', + value: '', + )); + }); + } + + QueryBuilder rateTypeEqualTo( + SupportedRateType value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'rateType', + value: value, + )); + }); + } + + QueryBuilder rateTypeGreaterThan( + SupportedRateType value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'rateType', + value: value, + )); + }); + } + + QueryBuilder rateTypeLessThan( + SupportedRateType value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'rateType', + value: value, + )); + }); + } + + QueryBuilder rateTypeBetween( + SupportedRateType lower, + SupportedRateType upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'rateType', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder tickerEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'ticker', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder tickerGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'ticker', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder tickerLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'ticker', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder tickerBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'ticker', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder tickerStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'ticker', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder tickerEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'ticker', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder tickerContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'ticker', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder tickerMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'ticker', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder tickerIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'ticker', + value: '', + )); + }); + } + + QueryBuilder tickerIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'ticker', + value: '', + )); + }); + } + + QueryBuilder + tokenContractIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'tokenContract', + )); + }); + } + + QueryBuilder + tokenContractIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'tokenContract', + )); + }); + } + + QueryBuilder tokenContractEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'tokenContract', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + tokenContractGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'tokenContract', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder tokenContractLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'tokenContract', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder tokenContractBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'tokenContract', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + tokenContractStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'tokenContract', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder tokenContractEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'tokenContract', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder tokenContractContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'tokenContract', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder tokenContractMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'tokenContract', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + tokenContractIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'tokenContract', + value: '', + )); + }); + } + + QueryBuilder + tokenContractIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'tokenContract', + value: '', + )); + }); + } +} + +extension CurrencyQueryObject + on QueryBuilder {} + +extension CurrencyQueryLinks + on QueryBuilder {} + +extension CurrencyQuerySortBy on QueryBuilder { + QueryBuilder sortByExchangeName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'exchangeName', Sort.asc); + }); + } + + QueryBuilder sortByExchangeNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'exchangeName', Sort.desc); + }); + } + + QueryBuilder sortByExternalId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'externalId', Sort.asc); + }); + } + + QueryBuilder sortByExternalIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'externalId', Sort.desc); + }); + } + + QueryBuilder sortByImage() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'image', Sort.asc); + }); + } + + QueryBuilder sortByImageDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'image', Sort.desc); + }); + } + + QueryBuilder sortByIsAvailable() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isAvailable', Sort.asc); + }); + } + + QueryBuilder sortByIsAvailableDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isAvailable', Sort.desc); + }); + } + + QueryBuilder sortByIsFiat() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isFiat', Sort.asc); + }); + } + + QueryBuilder sortByIsFiatDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isFiat', Sort.desc); + }); + } + + QueryBuilder sortByIsStackCoin() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isStackCoin', Sort.asc); + }); + } + + QueryBuilder sortByIsStackCoinDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isStackCoin', Sort.desc); + }); + } + + QueryBuilder sortByName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.asc); + }); + } + + QueryBuilder sortByNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.desc); + }); + } + + QueryBuilder sortByNetwork() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'network', Sort.asc); + }); + } + + QueryBuilder sortByNetworkDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'network', Sort.desc); + }); + } + + QueryBuilder sortByRateType() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'rateType', Sort.asc); + }); + } + + QueryBuilder sortByRateTypeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'rateType', Sort.desc); + }); + } + + QueryBuilder sortByTicker() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ticker', Sort.asc); + }); + } + + QueryBuilder sortByTickerDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ticker', Sort.desc); + }); + } + + QueryBuilder sortByTokenContract() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'tokenContract', Sort.asc); + }); + } + + QueryBuilder sortByTokenContractDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'tokenContract', Sort.desc); + }); + } +} + +extension CurrencyQuerySortThenBy + on QueryBuilder { + QueryBuilder thenByExchangeName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'exchangeName', Sort.asc); + }); + } + + QueryBuilder thenByExchangeNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'exchangeName', Sort.desc); + }); + } + + QueryBuilder thenByExternalId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'externalId', Sort.asc); + }); + } + + QueryBuilder thenByExternalIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'externalId', Sort.desc); + }); + } + + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder thenByImage() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'image', Sort.asc); + }); + } + + QueryBuilder thenByImageDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'image', Sort.desc); + }); + } + + QueryBuilder thenByIsAvailable() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isAvailable', Sort.asc); + }); + } + + QueryBuilder thenByIsAvailableDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isAvailable', Sort.desc); + }); + } + + QueryBuilder thenByIsFiat() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isFiat', Sort.asc); + }); + } + + QueryBuilder thenByIsFiatDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isFiat', Sort.desc); + }); + } + + QueryBuilder thenByIsStackCoin() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isStackCoin', Sort.asc); + }); + } + + QueryBuilder thenByIsStackCoinDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isStackCoin', Sort.desc); + }); + } + + QueryBuilder thenByName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.asc); + }); + } + + QueryBuilder thenByNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.desc); + }); + } + + QueryBuilder thenByNetwork() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'network', Sort.asc); + }); + } + + QueryBuilder thenByNetworkDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'network', Sort.desc); + }); + } + + QueryBuilder thenByRateType() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'rateType', Sort.asc); + }); + } + + QueryBuilder thenByRateTypeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'rateType', Sort.desc); + }); + } + + QueryBuilder thenByTicker() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ticker', Sort.asc); + }); + } + + QueryBuilder thenByTickerDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ticker', Sort.desc); + }); + } + + QueryBuilder thenByTokenContract() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'tokenContract', Sort.asc); + }); + } + + QueryBuilder thenByTokenContractDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'tokenContract', Sort.desc); + }); + } +} + +extension CurrencyQueryWhereDistinct + on QueryBuilder { + QueryBuilder distinctByExchangeName( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'exchangeName', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByExternalId( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'externalId', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByImage( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'image', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByIsAvailable() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'isAvailable'); + }); + } + + QueryBuilder distinctByIsFiat() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'isFiat'); + }); + } + + QueryBuilder distinctByIsStackCoin() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'isStackCoin'); + }); + } + + QueryBuilder distinctByName( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'name', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByNetwork( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'network', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByRateType() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'rateType'); + }); + } + + QueryBuilder distinctByTicker( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'ticker', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByTokenContract( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'tokenContract', + caseSensitive: caseSensitive); + }); + } +} + +extension CurrencyQueryProperty + on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder exchangeNameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'exchangeName'); + }); + } + + QueryBuilder externalIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'externalId'); + }); + } + + QueryBuilder imageProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'image'); + }); + } + + QueryBuilder isAvailableProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isAvailable'); + }); + } + + QueryBuilder isFiatProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isFiat'); + }); + } + + QueryBuilder isStackCoinProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isStackCoin'); + }); + } + + QueryBuilder nameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'name'); + }); + } + + QueryBuilder networkProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'network'); + }); + } + + QueryBuilder + rateTypeProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'rateType'); + }); + } + + QueryBuilder tickerProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'ticker'); + }); + } + + QueryBuilder tokenContractProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'tokenContract'); + }); + } +} diff --git a/lib/models/isar/exchange_cache/pair.dart b/lib/models/isar/exchange_cache/pair.dart new file mode 100644 index 000000000..4d7ebe8e8 --- /dev/null +++ b/lib/models/isar/exchange_cache/pair.dart @@ -0,0 +1,61 @@ +import 'package:isar/isar.dart'; + +part 'pair.g.dart'; + +// embedded enum // no not modify +enum SupportedRateType { fixed, estimated, both } + +@collection +class Pair { + Pair({ + required this.exchangeName, + required this.from, + required this.to, + required this.rateType, + }); + + Id? id; + + @Index() + final String exchangeName; + + @Index(composite: [ + CompositeIndex("exchangeName"), + CompositeIndex("to"), + ]) + final String from; + + final String to; + + @enumerated + final SupportedRateType rateType; + + Map toMap() { + return { + "id": id, + "exchangeName": exchangeName, + "from": from, + "to": to, + "rateType": rateType, + }; + } + + @override + bool operator ==(other) => + other is Pair && + exchangeName == other.exchangeName && + from == other.from && + to == other.to && + rateType == other.rateType; + + @override + int get hashCode => Object.hash( + exchangeName, + from, + to, + rateType, + ); + + @override + String toString() => "Pair: ${toMap()}"; +} diff --git a/lib/models/isar/exchange_cache/pair.g.dart b/lib/models/isar/exchange_cache/pair.g.dart new file mode 100644 index 000000000..eae9ee5b5 --- /dev/null +++ b/lib/models/isar/exchange_cache/pair.g.dart @@ -0,0 +1,1214 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'pair.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters + +extension GetPairCollection on Isar { + IsarCollection get pairs => this.collection(); +} + +const PairSchema = CollectionSchema( + name: r'Pair', + id: -3124465371488267306, + properties: { + r'exchangeName': PropertySchema( + id: 0, + name: r'exchangeName', + type: IsarType.string, + ), + r'from': PropertySchema( + id: 1, + name: r'from', + type: IsarType.string, + ), + r'hashCode': PropertySchema( + id: 2, + name: r'hashCode', + type: IsarType.long, + ), + r'rateType': PropertySchema( + id: 3, + name: r'rateType', + type: IsarType.byte, + enumMap: _PairrateTypeEnumValueMap, + ), + r'to': PropertySchema( + id: 4, + name: r'to', + type: IsarType.string, + ) + }, + estimateSize: _pairEstimateSize, + serialize: _pairSerialize, + deserialize: _pairDeserialize, + deserializeProp: _pairDeserializeProp, + idName: r'id', + indexes: { + r'exchangeName': IndexSchema( + id: 3599278165711581955, + name: r'exchangeName', + unique: false, + replace: false, + properties: [ + IndexPropertySchema( + name: r'exchangeName', + type: IndexType.hash, + caseSensitive: true, + ) + ], + ), + r'from_exchangeName_to': IndexSchema( + id: 817716734160134079, + name: r'from_exchangeName_to', + unique: false, + replace: false, + properties: [ + IndexPropertySchema( + name: r'from', + type: IndexType.hash, + caseSensitive: true, + ), + IndexPropertySchema( + name: r'exchangeName', + type: IndexType.hash, + caseSensitive: true, + ), + IndexPropertySchema( + name: r'to', + type: IndexType.hash, + caseSensitive: true, + ) + ], + ) + }, + links: {}, + embeddedSchemas: {}, + getId: _pairGetId, + getLinks: _pairGetLinks, + attach: _pairAttach, + version: '3.0.5', +); + +int _pairEstimateSize( + Pair object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + bytesCount += 3 + object.exchangeName.length * 3; + bytesCount += 3 + object.from.length * 3; + bytesCount += 3 + object.to.length * 3; + return bytesCount; +} + +void _pairSerialize( + Pair object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeString(offsets[0], object.exchangeName); + writer.writeString(offsets[1], object.from); + writer.writeLong(offsets[2], object.hashCode); + writer.writeByte(offsets[3], object.rateType.index); + writer.writeString(offsets[4], object.to); +} + +Pair _pairDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = Pair( + exchangeName: reader.readString(offsets[0]), + from: reader.readString(offsets[1]), + rateType: _PairrateTypeValueEnumMap[reader.readByteOrNull(offsets[3])] ?? + SupportedRateType.fixed, + to: reader.readString(offsets[4]), + ); + object.id = id; + return object; +} + +P _pairDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readString(offset)) as P; + case 1: + return (reader.readString(offset)) as P; + case 2: + return (reader.readLong(offset)) as P; + case 3: + return (_PairrateTypeValueEnumMap[reader.readByteOrNull(offset)] ?? + SupportedRateType.fixed) as P; + case 4: + return (reader.readString(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +const _PairrateTypeEnumValueMap = { + 'fixed': 0, + 'estimated': 1, + 'both': 2, +}; +const _PairrateTypeValueEnumMap = { + 0: SupportedRateType.fixed, + 1: SupportedRateType.estimated, + 2: SupportedRateType.both, +}; + +Id _pairGetId(Pair object) { + return object.id ?? Isar.autoIncrement; +} + +List> _pairGetLinks(Pair object) { + return []; +} + +void _pairAttach(IsarCollection col, Id id, Pair object) { + object.id = id; +} + +extension PairQueryWhereSort on QueryBuilder { + QueryBuilder anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension PairQueryWhere on QueryBuilder { + QueryBuilder idEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: id, + upper: id, + )); + }); + } + + QueryBuilder idNotEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder idGreaterThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder idLessThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder exchangeNameEqualTo( + String exchangeName) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'exchangeName', + value: [exchangeName], + )); + }); + } + + QueryBuilder exchangeNameNotEqualTo( + String exchangeName) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'exchangeName', + lower: [], + upper: [exchangeName], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'exchangeName', + lower: [exchangeName], + includeLower: false, + upper: [], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'exchangeName', + lower: [exchangeName], + includeLower: false, + upper: [], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'exchangeName', + lower: [], + upper: [exchangeName], + includeUpper: false, + )); + } + }); + } + + QueryBuilder fromEqualToAnyExchangeNameTo( + String from) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'from_exchangeName_to', + value: [from], + )); + }); + } + + QueryBuilder fromNotEqualToAnyExchangeNameTo( + String from) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'from_exchangeName_to', + lower: [], + upper: [from], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'from_exchangeName_to', + lower: [from], + includeLower: false, + upper: [], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'from_exchangeName_to', + lower: [from], + includeLower: false, + upper: [], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'from_exchangeName_to', + lower: [], + upper: [from], + includeUpper: false, + )); + } + }); + } + + QueryBuilder fromExchangeNameEqualToAnyTo( + String from, String exchangeName) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'from_exchangeName_to', + value: [from, exchangeName], + )); + }); + } + + QueryBuilder + fromEqualToExchangeNameNotEqualToAnyTo(String from, String exchangeName) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'from_exchangeName_to', + lower: [from], + upper: [from, exchangeName], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'from_exchangeName_to', + lower: [from, exchangeName], + includeLower: false, + upper: [from], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'from_exchangeName_to', + lower: [from, exchangeName], + includeLower: false, + upper: [from], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'from_exchangeName_to', + lower: [from], + upper: [from, exchangeName], + includeUpper: false, + )); + } + }); + } + + QueryBuilder fromExchangeNameToEqualTo( + String from, String exchangeName, String to) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'from_exchangeName_to', + value: [from, exchangeName, to], + )); + }); + } + + QueryBuilder + fromExchangeNameEqualToToNotEqualTo( + String from, String exchangeName, String to) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'from_exchangeName_to', + lower: [from, exchangeName], + upper: [from, exchangeName, to], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'from_exchangeName_to', + lower: [from, exchangeName, to], + includeLower: false, + upper: [from, exchangeName], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'from_exchangeName_to', + lower: [from, exchangeName, to], + includeLower: false, + upper: [from, exchangeName], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'from_exchangeName_to', + lower: [from, exchangeName], + upper: [from, exchangeName, to], + includeUpper: false, + )); + } + }); + } +} + +extension PairQueryFilter on QueryBuilder { + QueryBuilder exchangeNameEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'exchangeName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder exchangeNameGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'exchangeName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder exchangeNameLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'exchangeName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder exchangeNameBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'exchangeName', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder exchangeNameStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'exchangeName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder exchangeNameEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'exchangeName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder exchangeNameContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'exchangeName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder exchangeNameMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'exchangeName', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder exchangeNameIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'exchangeName', + value: '', + )); + }); + } + + QueryBuilder exchangeNameIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'exchangeName', + value: '', + )); + }); + } + + QueryBuilder fromEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'from', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder fromGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'from', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder fromLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'from', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder fromBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'from', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder fromStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'from', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder fromEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'from', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder fromContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'from', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder fromMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'from', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder fromIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'from', + value: '', + )); + }); + } + + QueryBuilder fromIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'from', + value: '', + )); + }); + } + + QueryBuilder hashCodeEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'hashCode', + value: value, + )); + }); + } + + QueryBuilder hashCodeGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'hashCode', + value: value, + )); + }); + } + + QueryBuilder hashCodeLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'hashCode', + value: value, + )); + }); + } + + QueryBuilder hashCodeBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'hashCode', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder idIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'id', + )); + }); + } + + QueryBuilder idIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'id', + )); + }); + } + + QueryBuilder idEqualTo(Id? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idGreaterThan( + Id? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idLessThan( + Id? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idBetween( + Id? lower, + Id? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder rateTypeEqualTo( + SupportedRateType value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'rateType', + value: value, + )); + }); + } + + QueryBuilder rateTypeGreaterThan( + SupportedRateType value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'rateType', + value: value, + )); + }); + } + + QueryBuilder rateTypeLessThan( + SupportedRateType value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'rateType', + value: value, + )); + }); + } + + QueryBuilder rateTypeBetween( + SupportedRateType lower, + SupportedRateType upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'rateType', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder toEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'to', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder toGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'to', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder toLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'to', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder toBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'to', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder toStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'to', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder toEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'to', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder toContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'to', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder toMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'to', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder toIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'to', + value: '', + )); + }); + } + + QueryBuilder toIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'to', + value: '', + )); + }); + } +} + +extension PairQueryObject on QueryBuilder {} + +extension PairQueryLinks on QueryBuilder {} + +extension PairQuerySortBy on QueryBuilder { + QueryBuilder sortByExchangeName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'exchangeName', Sort.asc); + }); + } + + QueryBuilder sortByExchangeNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'exchangeName', Sort.desc); + }); + } + + QueryBuilder sortByFrom() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'from', Sort.asc); + }); + } + + QueryBuilder sortByFromDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'from', Sort.desc); + }); + } + + QueryBuilder sortByHashCode() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'hashCode', Sort.asc); + }); + } + + QueryBuilder sortByHashCodeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'hashCode', Sort.desc); + }); + } + + QueryBuilder sortByRateType() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'rateType', Sort.asc); + }); + } + + QueryBuilder sortByRateTypeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'rateType', Sort.desc); + }); + } + + QueryBuilder sortByTo() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'to', Sort.asc); + }); + } + + QueryBuilder sortByToDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'to', Sort.desc); + }); + } +} + +extension PairQuerySortThenBy on QueryBuilder { + QueryBuilder thenByExchangeName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'exchangeName', Sort.asc); + }); + } + + QueryBuilder thenByExchangeNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'exchangeName', Sort.desc); + }); + } + + QueryBuilder thenByFrom() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'from', Sort.asc); + }); + } + + QueryBuilder thenByFromDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'from', Sort.desc); + }); + } + + QueryBuilder thenByHashCode() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'hashCode', Sort.asc); + }); + } + + QueryBuilder thenByHashCodeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'hashCode', Sort.desc); + }); + } + + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder thenByRateType() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'rateType', Sort.asc); + }); + } + + QueryBuilder thenByRateTypeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'rateType', Sort.desc); + }); + } + + QueryBuilder thenByTo() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'to', Sort.asc); + }); + } + + QueryBuilder thenByToDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'to', Sort.desc); + }); + } +} + +extension PairQueryWhereDistinct on QueryBuilder { + QueryBuilder distinctByExchangeName( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'exchangeName', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByFrom( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'from', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByHashCode() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'hashCode'); + }); + } + + QueryBuilder distinctByRateType() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'rateType'); + }); + } + + QueryBuilder distinctByTo( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'to', caseSensitive: caseSensitive); + }); + } +} + +extension PairQueryProperty on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder exchangeNameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'exchangeName'); + }); + } + + QueryBuilder fromProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'from'); + }); + } + + QueryBuilder hashCodeProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'hashCode'); + }); + } + + QueryBuilder rateTypeProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'rateType'); + }); + } + + QueryBuilder toProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'to'); + }); + } +} diff --git a/lib/models/isar/models/address_label.dart b/lib/models/isar/models/address_label.dart new file mode 100644 index 000000000..9988c83c0 --- /dev/null +++ b/lib/models/isar/models/address_label.dart @@ -0,0 +1,36 @@ +import 'package:isar/isar.dart'; + +part 'address_label.g.dart'; + +@Collection() +class AddressLabel { + AddressLabel({ + required this.walletId, + required this.addressString, + required this.value, + required this.tags, + }); + + Id id = Isar.autoIncrement; + + @Index() + late final String walletId; + + @Index(unique: true, composite: [CompositeIndex("walletId")]) + late final String addressString; + + late final String value; + + late final List? tags; + + AddressLabel copyWith({String? label, Id? id, List? tags}) { + final addressLabel = AddressLabel( + walletId: walletId, + addressString: addressString, + value: label ?? value, + tags: tags ?? this.tags, + ); + addressLabel.id = id ?? this.id; + return addressLabel; + } +} diff --git a/lib/models/isar/models/address_label.g.dart b/lib/models/isar/models/address_label.g.dart new file mode 100644 index 000000000..2a6b41e70 --- /dev/null +++ b/lib/models/isar/models/address_label.g.dart @@ -0,0 +1,1346 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'address_label.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters + +extension GetAddressLabelCollection on Isar { + IsarCollection get addressLabels => this.collection(); +} + +const AddressLabelSchema = CollectionSchema( + name: r'AddressLabel', + id: 75734181566197428, + properties: { + r'addressString': PropertySchema( + id: 0, + name: r'addressString', + type: IsarType.string, + ), + r'tags': PropertySchema( + id: 1, + name: r'tags', + type: IsarType.stringList, + ), + r'value': PropertySchema( + id: 2, + name: r'value', + type: IsarType.string, + ), + r'walletId': PropertySchema( + id: 3, + name: r'walletId', + type: IsarType.string, + ) + }, + estimateSize: _addressLabelEstimateSize, + serialize: _addressLabelSerialize, + deserialize: _addressLabelDeserialize, + deserializeProp: _addressLabelDeserializeProp, + idName: r'id', + indexes: { + r'walletId': IndexSchema( + id: -1783113319798776304, + name: r'walletId', + unique: false, + replace: false, + properties: [ + IndexPropertySchema( + name: r'walletId', + type: IndexType.hash, + caseSensitive: true, + ) + ], + ), + r'addressString_walletId': IndexSchema( + id: -3907148833323637842, + name: r'addressString_walletId', + unique: true, + replace: false, + properties: [ + IndexPropertySchema( + name: r'addressString', + type: IndexType.hash, + caseSensitive: true, + ), + IndexPropertySchema( + name: r'walletId', + type: IndexType.hash, + caseSensitive: true, + ) + ], + ) + }, + links: {}, + embeddedSchemas: {}, + getId: _addressLabelGetId, + getLinks: _addressLabelGetLinks, + attach: _addressLabelAttach, + version: '3.0.5', +); + +int _addressLabelEstimateSize( + AddressLabel object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + bytesCount += 3 + object.addressString.length * 3; + { + final list = object.tags; + if (list != null) { + bytesCount += 3 + list.length * 3; + { + for (var i = 0; i < list.length; i++) { + final value = list[i]; + bytesCount += value.length * 3; + } + } + } + } + bytesCount += 3 + object.value.length * 3; + bytesCount += 3 + object.walletId.length * 3; + return bytesCount; +} + +void _addressLabelSerialize( + AddressLabel object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeString(offsets[0], object.addressString); + writer.writeStringList(offsets[1], object.tags); + writer.writeString(offsets[2], object.value); + writer.writeString(offsets[3], object.walletId); +} + +AddressLabel _addressLabelDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = AddressLabel( + addressString: reader.readString(offsets[0]), + tags: reader.readStringList(offsets[1]), + value: reader.readString(offsets[2]), + walletId: reader.readString(offsets[3]), + ); + object.id = id; + return object; +} + +P _addressLabelDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readString(offset)) as P; + case 1: + return (reader.readStringList(offset)) as P; + case 2: + return (reader.readString(offset)) as P; + case 3: + return (reader.readString(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +Id _addressLabelGetId(AddressLabel object) { + return object.id; +} + +List> _addressLabelGetLinks(AddressLabel object) { + return []; +} + +void _addressLabelAttach( + IsarCollection col, Id id, AddressLabel object) { + object.id = id; +} + +extension AddressLabelByIndex on IsarCollection { + Future getByAddressStringWalletId( + String addressString, String walletId) { + return getByIndex(r'addressString_walletId', [addressString, walletId]); + } + + AddressLabel? getByAddressStringWalletIdSync( + String addressString, String walletId) { + return getByIndexSync(r'addressString_walletId', [addressString, walletId]); + } + + Future deleteByAddressStringWalletId( + String addressString, String walletId) { + return deleteByIndex(r'addressString_walletId', [addressString, walletId]); + } + + bool deleteByAddressStringWalletIdSync( + String addressString, String walletId) { + return deleteByIndexSync( + r'addressString_walletId', [addressString, walletId]); + } + + Future> getAllByAddressStringWalletId( + List addressStringValues, List walletIdValues) { + final len = addressStringValues.length; + assert(walletIdValues.length == len, + 'All index values must have the same length'); + final values = >[]; + for (var i = 0; i < len; i++) { + values.add([addressStringValues[i], walletIdValues[i]]); + } + + return getAllByIndex(r'addressString_walletId', values); + } + + List getAllByAddressStringWalletIdSync( + List addressStringValues, List walletIdValues) { + final len = addressStringValues.length; + assert(walletIdValues.length == len, + 'All index values must have the same length'); + final values = >[]; + for (var i = 0; i < len; i++) { + values.add([addressStringValues[i], walletIdValues[i]]); + } + + return getAllByIndexSync(r'addressString_walletId', values); + } + + Future deleteAllByAddressStringWalletId( + List addressStringValues, List walletIdValues) { + final len = addressStringValues.length; + assert(walletIdValues.length == len, + 'All index values must have the same length'); + final values = >[]; + for (var i = 0; i < len; i++) { + values.add([addressStringValues[i], walletIdValues[i]]); + } + + return deleteAllByIndex(r'addressString_walletId', values); + } + + int deleteAllByAddressStringWalletIdSync( + List addressStringValues, List walletIdValues) { + final len = addressStringValues.length; + assert(walletIdValues.length == len, + 'All index values must have the same length'); + final values = >[]; + for (var i = 0; i < len; i++) { + values.add([addressStringValues[i], walletIdValues[i]]); + } + + return deleteAllByIndexSync(r'addressString_walletId', values); + } + + Future putByAddressStringWalletId(AddressLabel object) { + return putByIndex(r'addressString_walletId', object); + } + + Id putByAddressStringWalletIdSync(AddressLabel object, + {bool saveLinks = true}) { + return putByIndexSync(r'addressString_walletId', object, + saveLinks: saveLinks); + } + + Future> putAllByAddressStringWalletId(List objects) { + return putAllByIndex(r'addressString_walletId', objects); + } + + List putAllByAddressStringWalletIdSync(List objects, + {bool saveLinks = true}) { + return putAllByIndexSync(r'addressString_walletId', objects, + saveLinks: saveLinks); + } +} + +extension AddressLabelQueryWhereSort + on QueryBuilder { + QueryBuilder anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension AddressLabelQueryWhere + on QueryBuilder { + QueryBuilder idEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: id, + upper: id, + )); + }); + } + + QueryBuilder idNotEqualTo( + Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder idGreaterThan( + Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder idLessThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder walletIdEqualTo( + String walletId) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'walletId', + value: [walletId], + )); + }); + } + + QueryBuilder + walletIdNotEqualTo(String walletId) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'walletId', + lower: [], + upper: [walletId], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'walletId', + lower: [walletId], + includeLower: false, + upper: [], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'walletId', + lower: [walletId], + includeLower: false, + upper: [], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'walletId', + lower: [], + upper: [walletId], + includeUpper: false, + )); + } + }); + } + + QueryBuilder + addressStringEqualToAnyWalletId(String addressString) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'addressString_walletId', + value: [addressString], + )); + }); + } + + QueryBuilder + addressStringNotEqualToAnyWalletId(String addressString) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'addressString_walletId', + lower: [], + upper: [addressString], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'addressString_walletId', + lower: [addressString], + includeLower: false, + upper: [], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'addressString_walletId', + lower: [addressString], + includeLower: false, + upper: [], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'addressString_walletId', + lower: [], + upper: [addressString], + includeUpper: false, + )); + } + }); + } + + QueryBuilder + addressStringWalletIdEqualTo(String addressString, String walletId) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'addressString_walletId', + value: [addressString, walletId], + )); + }); + } + + QueryBuilder + addressStringEqualToWalletIdNotEqualTo( + String addressString, String walletId) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'addressString_walletId', + lower: [addressString], + upper: [addressString, walletId], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'addressString_walletId', + lower: [addressString, walletId], + includeLower: false, + upper: [addressString], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'addressString_walletId', + lower: [addressString, walletId], + includeLower: false, + upper: [addressString], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'addressString_walletId', + lower: [addressString], + upper: [addressString, walletId], + includeUpper: false, + )); + } + }); + } +} + +extension AddressLabelQueryFilter + on QueryBuilder { + QueryBuilder + addressStringEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'addressString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + addressStringGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'addressString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + addressStringLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'addressString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + addressStringBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'addressString', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + addressStringStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'addressString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + addressStringEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'addressString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + addressStringContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'addressString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + addressStringMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'addressString', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + addressStringIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'addressString', + value: '', + )); + }); + } + + QueryBuilder + addressStringIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'addressString', + value: '', + )); + }); + } + + QueryBuilder idEqualTo( + Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idGreaterThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idLessThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder tagsIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'tags', + )); + }); + } + + QueryBuilder + tagsIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'tags', + )); + }); + } + + QueryBuilder + tagsElementEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'tags', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + tagsElementGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'tags', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + tagsElementLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'tags', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + tagsElementBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'tags', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + tagsElementStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'tags', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + tagsElementEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'tags', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + tagsElementContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'tags', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + tagsElementMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'tags', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + tagsElementIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'tags', + value: '', + )); + }); + } + + QueryBuilder + tagsElementIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'tags', + value: '', + )); + }); + } + + QueryBuilder + tagsLengthEqualTo(int length) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'tags', + length, + true, + length, + true, + ); + }); + } + + QueryBuilder + tagsIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'tags', + 0, + true, + 0, + true, + ); + }); + } + + QueryBuilder + tagsIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'tags', + 0, + false, + 999999, + true, + ); + }); + } + + QueryBuilder + tagsLengthLessThan( + int length, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'tags', + 0, + true, + length, + include, + ); + }); + } + + QueryBuilder + tagsLengthGreaterThan( + int length, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'tags', + length, + include, + 999999, + true, + ); + }); + } + + QueryBuilder + tagsLengthBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'tags', + lower, + includeLower, + upper, + includeUpper, + ); + }); + } + + QueryBuilder valueEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + valueGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder valueLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder valueBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'value', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + valueStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder valueEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder valueContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder valueMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'value', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + valueIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'value', + value: '', + )); + }); + } + + QueryBuilder + valueIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'value', + value: '', + )); + }); + } + + QueryBuilder + walletIdEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + walletIdGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + walletIdLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + walletIdBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'walletId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + walletIdStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + walletIdEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + walletIdContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + walletIdMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'walletId', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + walletIdIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'walletId', + value: '', + )); + }); + } + + QueryBuilder + walletIdIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'walletId', + value: '', + )); + }); + } +} + +extension AddressLabelQueryObject + on QueryBuilder {} + +extension AddressLabelQueryLinks + on QueryBuilder {} + +extension AddressLabelQuerySortBy + on QueryBuilder { + QueryBuilder sortByAddressString() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'addressString', Sort.asc); + }); + } + + QueryBuilder + sortByAddressStringDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'addressString', Sort.desc); + }); + } + + QueryBuilder sortByValue() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'value', Sort.asc); + }); + } + + QueryBuilder sortByValueDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'value', Sort.desc); + }); + } + + QueryBuilder sortByWalletId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'walletId', Sort.asc); + }); + } + + QueryBuilder sortByWalletIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'walletId', Sort.desc); + }); + } +} + +extension AddressLabelQuerySortThenBy + on QueryBuilder { + QueryBuilder thenByAddressString() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'addressString', Sort.asc); + }); + } + + QueryBuilder + thenByAddressStringDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'addressString', Sort.desc); + }); + } + + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder thenByValue() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'value', Sort.asc); + }); + } + + QueryBuilder thenByValueDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'value', Sort.desc); + }); + } + + QueryBuilder thenByWalletId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'walletId', Sort.asc); + }); + } + + QueryBuilder thenByWalletIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'walletId', Sort.desc); + }); + } +} + +extension AddressLabelQueryWhereDistinct + on QueryBuilder { + QueryBuilder distinctByAddressString( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'addressString', + caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByTags() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'tags'); + }); + } + + QueryBuilder distinctByValue( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'value', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByWalletId( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'walletId', caseSensitive: caseSensitive); + }); + } +} + +extension AddressLabelQueryProperty + on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder addressStringProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'addressString'); + }); + } + + QueryBuilder?, QQueryOperations> tagsProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'tags'); + }); + } + + QueryBuilder valueProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'value'); + }); + } + + QueryBuilder walletIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'walletId'); + }); + } +} diff --git a/lib/models/isar/models/block_explorer.dart b/lib/models/isar/models/block_explorer.dart new file mode 100644 index 000000000..cf01fa5e2 --- /dev/null +++ b/lib/models/isar/models/block_explorer.dart @@ -0,0 +1,35 @@ +import 'package:isar/isar.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; + +part 'block_explorer.g.dart'; + +@collection +class TransactionBlockExplorer { + TransactionBlockExplorer({ + required this.ticker, + required this.url, + }); + + Id id = Isar.autoIncrement; + + @Index(unique: true, replace: true) + late final String ticker; + + late final String url; + + @ignore + Coin? get coin { + try { + return coinFromTickerCaseInsensitive(ticker); + } catch (_) { + return null; + } + } + + Uri? getUrlFor({required String txid}) => Uri.tryParse( + url.replaceFirst( + "%5BTXID%5D", + txid, + ), + ); +} diff --git a/lib/models/isar/models/block_explorer.g.dart b/lib/models/isar/models/block_explorer.g.dart new file mode 100644 index 000000000..f524392d5 --- /dev/null +++ b/lib/models/isar/models/block_explorer.g.dart @@ -0,0 +1,764 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'block_explorer.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters + +extension GetTransactionBlockExplorerCollection on Isar { + IsarCollection get transactionBlockExplorers => + this.collection(); +} + +const TransactionBlockExplorerSchema = CollectionSchema( + name: r'TransactionBlockExplorer', + id: 4209077296238413906, + properties: { + r'ticker': PropertySchema( + id: 0, + name: r'ticker', + type: IsarType.string, + ), + r'url': PropertySchema( + id: 1, + name: r'url', + type: IsarType.string, + ) + }, + estimateSize: _transactionBlockExplorerEstimateSize, + serialize: _transactionBlockExplorerSerialize, + deserialize: _transactionBlockExplorerDeserialize, + deserializeProp: _transactionBlockExplorerDeserializeProp, + idName: r'id', + indexes: { + r'ticker': IndexSchema( + id: -8264639257510259247, + name: r'ticker', + unique: true, + replace: true, + properties: [ + IndexPropertySchema( + name: r'ticker', + type: IndexType.hash, + caseSensitive: true, + ) + ], + ) + }, + links: {}, + embeddedSchemas: {}, + getId: _transactionBlockExplorerGetId, + getLinks: _transactionBlockExplorerGetLinks, + attach: _transactionBlockExplorerAttach, + version: '3.0.5', +); + +int _transactionBlockExplorerEstimateSize( + TransactionBlockExplorer object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + bytesCount += 3 + object.ticker.length * 3; + bytesCount += 3 + object.url.length * 3; + return bytesCount; +} + +void _transactionBlockExplorerSerialize( + TransactionBlockExplorer object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeString(offsets[0], object.ticker); + writer.writeString(offsets[1], object.url); +} + +TransactionBlockExplorer _transactionBlockExplorerDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = TransactionBlockExplorer( + ticker: reader.readString(offsets[0]), + url: reader.readString(offsets[1]), + ); + object.id = id; + return object; +} + +P _transactionBlockExplorerDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readString(offset)) as P; + case 1: + return (reader.readString(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +Id _transactionBlockExplorerGetId(TransactionBlockExplorer object) { + return object.id; +} + +List> _transactionBlockExplorerGetLinks( + TransactionBlockExplorer object) { + return []; +} + +void _transactionBlockExplorerAttach( + IsarCollection col, Id id, TransactionBlockExplorer object) { + object.id = id; +} + +extension TransactionBlockExplorerByIndex + on IsarCollection { + Future getByTicker(String ticker) { + return getByIndex(r'ticker', [ticker]); + } + + TransactionBlockExplorer? getByTickerSync(String ticker) { + return getByIndexSync(r'ticker', [ticker]); + } + + Future deleteByTicker(String ticker) { + return deleteByIndex(r'ticker', [ticker]); + } + + bool deleteByTickerSync(String ticker) { + return deleteByIndexSync(r'ticker', [ticker]); + } + + Future> getAllByTicker( + List tickerValues) { + final values = tickerValues.map((e) => [e]).toList(); + return getAllByIndex(r'ticker', values); + } + + List getAllByTickerSync( + List tickerValues) { + final values = tickerValues.map((e) => [e]).toList(); + return getAllByIndexSync(r'ticker', values); + } + + Future deleteAllByTicker(List tickerValues) { + final values = tickerValues.map((e) => [e]).toList(); + return deleteAllByIndex(r'ticker', values); + } + + int deleteAllByTickerSync(List tickerValues) { + final values = tickerValues.map((e) => [e]).toList(); + return deleteAllByIndexSync(r'ticker', values); + } + + Future putByTicker(TransactionBlockExplorer object) { + return putByIndex(r'ticker', object); + } + + Id putByTickerSync(TransactionBlockExplorer object, {bool saveLinks = true}) { + return putByIndexSync(r'ticker', object, saveLinks: saveLinks); + } + + Future> putAllByTicker(List objects) { + return putAllByIndex(r'ticker', objects); + } + + List putAllByTickerSync(List objects, + {bool saveLinks = true}) { + return putAllByIndexSync(r'ticker', objects, saveLinks: saveLinks); + } +} + +extension TransactionBlockExplorerQueryWhereSort on QueryBuilder< + TransactionBlockExplorer, TransactionBlockExplorer, QWhere> { + QueryBuilder + anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension TransactionBlockExplorerQueryWhere on QueryBuilder< + TransactionBlockExplorer, TransactionBlockExplorer, QWhereClause> { + QueryBuilder idEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: id, + upper: id, + )); + }); + } + + QueryBuilder idNotEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder idGreaterThan(Id id, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder idLessThan(Id id, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder tickerEqualTo(String ticker) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'ticker', + value: [ticker], + )); + }); + } + + QueryBuilder tickerNotEqualTo(String ticker) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'ticker', + lower: [], + upper: [ticker], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'ticker', + lower: [ticker], + includeLower: false, + upper: [], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'ticker', + lower: [ticker], + includeLower: false, + upper: [], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'ticker', + lower: [], + upper: [ticker], + includeUpper: false, + )); + } + }); + } +} + +extension TransactionBlockExplorerQueryFilter on QueryBuilder< + TransactionBlockExplorer, TransactionBlockExplorer, QFilterCondition> { + QueryBuilder idEqualTo(Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idGreaterThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idLessThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder tickerEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'ticker', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder tickerGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'ticker', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder tickerLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'ticker', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder tickerBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'ticker', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder tickerStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'ticker', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder tickerEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'ticker', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + tickerContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'ticker', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + tickerMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'ticker', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder tickerIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'ticker', + value: '', + )); + }); + } + + QueryBuilder tickerIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'ticker', + value: '', + )); + }); + } + + QueryBuilder urlEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'url', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder urlGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'url', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder urlLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'url', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder urlBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'url', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder urlStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'url', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder urlEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'url', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + urlContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'url', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + urlMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'url', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder urlIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'url', + value: '', + )); + }); + } + + QueryBuilder urlIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'url', + value: '', + )); + }); + } +} + +extension TransactionBlockExplorerQueryObject on QueryBuilder< + TransactionBlockExplorer, TransactionBlockExplorer, QFilterCondition> {} + +extension TransactionBlockExplorerQueryLinks on QueryBuilder< + TransactionBlockExplorer, TransactionBlockExplorer, QFilterCondition> {} + +extension TransactionBlockExplorerQuerySortBy on QueryBuilder< + TransactionBlockExplorer, TransactionBlockExplorer, QSortBy> { + QueryBuilder + sortByTicker() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ticker', Sort.asc); + }); + } + + QueryBuilder + sortByTickerDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ticker', Sort.desc); + }); + } + + QueryBuilder + sortByUrl() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'url', Sort.asc); + }); + } + + QueryBuilder + sortByUrlDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'url', Sort.desc); + }); + } +} + +extension TransactionBlockExplorerQuerySortThenBy on QueryBuilder< + TransactionBlockExplorer, TransactionBlockExplorer, QSortThenBy> { + QueryBuilder + thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder + thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder + thenByTicker() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ticker', Sort.asc); + }); + } + + QueryBuilder + thenByTickerDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ticker', Sort.desc); + }); + } + + QueryBuilder + thenByUrl() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'url', Sort.asc); + }); + } + + QueryBuilder + thenByUrlDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'url', Sort.desc); + }); + } +} + +extension TransactionBlockExplorerQueryWhereDistinct on QueryBuilder< + TransactionBlockExplorer, TransactionBlockExplorer, QDistinct> { + QueryBuilder + distinctByTicker({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'ticker', caseSensitive: caseSensitive); + }); + } + + QueryBuilder + distinctByUrl({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'url', caseSensitive: caseSensitive); + }); + } +} + +extension TransactionBlockExplorerQueryProperty on QueryBuilder< + TransactionBlockExplorer, TransactionBlockExplorer, QQueryProperty> { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder + tickerProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'ticker'); + }); + } + + QueryBuilder + urlProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'url'); + }); + } +} diff --git a/lib/models/isar/models/blockchain_data/address.dart b/lib/models/isar/models/blockchain_data/address.dart new file mode 100644 index 000000000..25281a629 --- /dev/null +++ b/lib/models/isar/models/blockchain_data/address.dart @@ -0,0 +1,198 @@ +import 'dart:convert'; + +import 'package:isar/isar.dart'; +import 'package:stackwallet/exceptions/address/address_exception.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/crypto_currency_address.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart'; + +part 'address.g.dart'; + +@Collection(accessor: "addresses") +class Address extends CryptoCurrencyAddress { + Address({ + required this.walletId, + required this.value, + required this.publicKey, + required this.derivationIndex, + required this.derivationPath, + required this.type, + required this.subType, + this.otherData, + }); + + Id id = Isar.autoIncrement; + + @Index() + late final String walletId; + + @Index(unique: true, composite: [CompositeIndex("walletId")]) + late final String value; + + late final List publicKey; + + @Index() + late final int derivationIndex; // -1 generally means unknown + + @enumerated + late final AddressType type; + + @enumerated + late final AddressSubType subType; + + late final DerivationPath? derivationPath; + + late final String? otherData; + + final transactions = IsarLinks(); + + int derivationChain() { + if (subType == AddressSubType.receiving) { + return 0; // 0 for receiving (external) + } else if (subType == AddressSubType.change) { + return 1; // 1 for change (internal) + } else { + throw AddressException("Could not imply derivation chain value"); + } + } + + bool isPaynymAddress() => + subType == AddressSubType.paynymNotification || + subType == AddressSubType.paynymSend || + subType == AddressSubType.paynymReceive; + + @override + String toString() => "{ " + "id: $id, " + "walletId: $walletId, " + "value: $value, " + "publicKey: $publicKey, " + "derivationIndex: $derivationIndex, " + "type: ${type.name}, " + "subType: ${subType.name}, " + "transactionsLength: ${transactions.length} " + "derivationPath: $derivationPath, " + "otherData: $otherData, " + "}"; + + String toJsonString() { + final Map result = { + "walletId": walletId, + "value": value, + "publicKey": publicKey, + "derivationIndex": derivationIndex, + "type": type.name, + "subType": subType.name, + "derivationPath": derivationPath?.value, + "otherData": otherData, + }; + return jsonEncode(result); + } + + static Address fromJsonString( + String jsonString, { + String? overrideWalletId, + }) { + final json = jsonDecode(jsonString); + final derivationPathString = json["derivationPath"] as String?; + + final DerivationPath? derivationPath = + derivationPathString == null ? null : DerivationPath(); + if (derivationPath != null) { + derivationPath.value = derivationPathString!; + } + + return Address( + walletId: overrideWalletId ?? json["walletId"] as String, + value: json["value"] as String, + publicKey: List.from(json["publicKey"] as List), + derivationIndex: json["derivationIndex"] as int, + derivationPath: derivationPath, + type: AddressType.values.byName(json["type"] as String), + subType: AddressSubType.values.byName(json["subType"] as String), + otherData: json["otherData"] as String?, + ); + } +} + +// do not modify +enum AddressType { + p2pkh, + p2sh, + p2wpkh, + cryptonote, + mimbleWimble, + unknown, + nonWallet, + ethereum; + + String get readableName { + switch (this) { + case AddressType.p2pkh: + return "Legacy"; + case AddressType.p2sh: + return "Wrapped segwit"; + case AddressType.p2wpkh: + return "Segwit"; + case AddressType.cryptonote: + return "Cryptonote"; + case AddressType.mimbleWimble: + return "Mimble Wimble"; + case AddressType.unknown: + return "Unknown"; + case AddressType.nonWallet: + return "Non wallet/unknown"; + case AddressType.ethereum: + return "Ethereum"; + } + } +} + +// do not modify +enum AddressSubType { + receiving, + change, + paynymNotification, + paynymSend, + paynymReceive, + unknown, + nonWallet; + + String get prettyName { + switch (this) { + case AddressSubType.receiving: + return "Receiving"; + case AddressSubType.change: + return "Change"; + case AddressSubType.paynymNotification: + return "PayNym Notification"; + case AddressSubType.paynymSend: + return "PayNym Send"; + case AddressSubType.paynymReceive: + return "PayNym Receiving"; + case AddressSubType.unknown: + return "Unknown"; + case AddressSubType.nonWallet: + return "Non wallet/unknown"; + } + } +} + +@Embedded(inheritance: false) +class DerivationPath { + late final String value; + + List getComponents() => value.split("/"); + + String getPurpose() => getComponents()[1]; + + @override + toString() => value; + + @override + bool operator ==(Object other) => + identical(this, other) || other is DerivationPath && value == other.value; + + @ignore + @override + int get hashCode => value.hashCode; +} diff --git a/lib/models/isar/models/blockchain_data/address.g.dart b/lib/models/isar/models/blockchain_data/address.g.dart new file mode 100644 index 000000000..49188b395 --- /dev/null +++ b/lib/models/isar/models/blockchain_data/address.g.dart @@ -0,0 +1,2010 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'address.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters + +extension GetAddressCollection on Isar { + IsarCollection

get addresses => this.collection(); +} + +const AddressSchema = CollectionSchema( + name: r'Address', + id: 3544600503126319553, + properties: { + r'derivationIndex': PropertySchema( + id: 0, + name: r'derivationIndex', + type: IsarType.long, + ), + r'derivationPath': PropertySchema( + id: 1, + name: r'derivationPath', + type: IsarType.object, + target: r'DerivationPath', + ), + r'otherData': PropertySchema( + id: 2, + name: r'otherData', + type: IsarType.string, + ), + r'publicKey': PropertySchema( + id: 3, + name: r'publicKey', + type: IsarType.byteList, + ), + r'subType': PropertySchema( + id: 4, + name: r'subType', + type: IsarType.byte, + enumMap: _AddresssubTypeEnumValueMap, + ), + r'type': PropertySchema( + id: 5, + name: r'type', + type: IsarType.byte, + enumMap: _AddresstypeEnumValueMap, + ), + r'value': PropertySchema( + id: 6, + name: r'value', + type: IsarType.string, + ), + r'walletId': PropertySchema( + id: 7, + name: r'walletId', + type: IsarType.string, + ) + }, + estimateSize: _addressEstimateSize, + serialize: _addressSerialize, + deserialize: _addressDeserialize, + deserializeProp: _addressDeserializeProp, + idName: r'id', + indexes: { + r'walletId': IndexSchema( + id: -1783113319798776304, + name: r'walletId', + unique: false, + replace: false, + properties: [ + IndexPropertySchema( + name: r'walletId', + type: IndexType.hash, + caseSensitive: true, + ) + ], + ), + r'value_walletId': IndexSchema( + id: -7332188919457190704, + name: r'value_walletId', + unique: true, + replace: false, + properties: [ + IndexPropertySchema( + name: r'value', + type: IndexType.hash, + caseSensitive: true, + ), + IndexPropertySchema( + name: r'walletId', + type: IndexType.hash, + caseSensitive: true, + ) + ], + ), + r'derivationIndex': IndexSchema( + id: -6950711977521998012, + name: r'derivationIndex', + unique: false, + replace: false, + properties: [ + IndexPropertySchema( + name: r'derivationIndex', + type: IndexType.value, + caseSensitive: false, + ) + ], + ) + }, + links: { + r'transactions': LinkSchema( + id: -20231914767662480, + name: r'transactions', + target: r'Transaction', + single: false, + ) + }, + embeddedSchemas: {r'DerivationPath': DerivationPathSchema}, + getId: _addressGetId, + getLinks: _addressGetLinks, + attach: _addressAttach, + version: '3.0.5', +); + +int _addressEstimateSize( + Address object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + { + final value = object.derivationPath; + if (value != null) { + bytesCount += 3 + + DerivationPathSchema.estimateSize( + value, allOffsets[DerivationPath]!, allOffsets); + } + } + { + final value = object.otherData; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + bytesCount += 3 + object.publicKey.length; + bytesCount += 3 + object.value.length * 3; + bytesCount += 3 + object.walletId.length * 3; + return bytesCount; +} + +void _addressSerialize( + Address object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeLong(offsets[0], object.derivationIndex); + writer.writeObject( + offsets[1], + allOffsets, + DerivationPathSchema.serialize, + object.derivationPath, + ); + writer.writeString(offsets[2], object.otherData); + writer.writeByteList(offsets[3], object.publicKey); + writer.writeByte(offsets[4], object.subType.index); + writer.writeByte(offsets[5], object.type.index); + writer.writeString(offsets[6], object.value); + writer.writeString(offsets[7], object.walletId); +} + +Address _addressDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = Address( + derivationIndex: reader.readLong(offsets[0]), + derivationPath: reader.readObjectOrNull( + offsets[1], + DerivationPathSchema.deserialize, + allOffsets, + ), + otherData: reader.readStringOrNull(offsets[2]), + publicKey: reader.readByteList(offsets[3]) ?? [], + subType: _AddresssubTypeValueEnumMap[reader.readByteOrNull(offsets[4])] ?? + AddressSubType.receiving, + type: _AddresstypeValueEnumMap[reader.readByteOrNull(offsets[5])] ?? + AddressType.p2pkh, + value: reader.readString(offsets[6]), + walletId: reader.readString(offsets[7]), + ); + object.id = id; + return object; +} + +P _addressDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readLong(offset)) as P; + case 1: + return (reader.readObjectOrNull( + offset, + DerivationPathSchema.deserialize, + allOffsets, + )) as P; + case 2: + return (reader.readStringOrNull(offset)) as P; + case 3: + return (reader.readByteList(offset) ?? []) as P; + case 4: + return (_AddresssubTypeValueEnumMap[reader.readByteOrNull(offset)] ?? + AddressSubType.receiving) as P; + case 5: + return (_AddresstypeValueEnumMap[reader.readByteOrNull(offset)] ?? + AddressType.p2pkh) as P; + case 6: + return (reader.readString(offset)) as P; + case 7: + return (reader.readString(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +const _AddresssubTypeEnumValueMap = { + 'receiving': 0, + 'change': 1, + 'paynymNotification': 2, + 'paynymSend': 3, + 'paynymReceive': 4, + 'unknown': 5, + 'nonWallet': 6, +}; +const _AddresssubTypeValueEnumMap = { + 0: AddressSubType.receiving, + 1: AddressSubType.change, + 2: AddressSubType.paynymNotification, + 3: AddressSubType.paynymSend, + 4: AddressSubType.paynymReceive, + 5: AddressSubType.unknown, + 6: AddressSubType.nonWallet, +}; +const _AddresstypeEnumValueMap = { + 'p2pkh': 0, + 'p2sh': 1, + 'p2wpkh': 2, + 'cryptonote': 3, + 'mimbleWimble': 4, + 'unknown': 5, + 'nonWallet': 6, + 'ethereum': 7, +}; +const _AddresstypeValueEnumMap = { + 0: AddressType.p2pkh, + 1: AddressType.p2sh, + 2: AddressType.p2wpkh, + 3: AddressType.cryptonote, + 4: AddressType.mimbleWimble, + 5: AddressType.unknown, + 6: AddressType.nonWallet, + 7: AddressType.ethereum, +}; + +Id _addressGetId(Address object) { + return object.id; +} + +List> _addressGetLinks(Address object) { + return [object.transactions]; +} + +void _addressAttach(IsarCollection col, Id id, Address object) { + object.id = id; + object.transactions + .attach(col, col.isar.collection(), r'transactions', id); +} + +extension AddressByIndex on IsarCollection

{ + Future getByValueWalletId(String value, String walletId) { + return getByIndex(r'value_walletId', [value, walletId]); + } + + Address? getByValueWalletIdSync(String value, String walletId) { + return getByIndexSync(r'value_walletId', [value, walletId]); + } + + Future deleteByValueWalletId(String value, String walletId) { + return deleteByIndex(r'value_walletId', [value, walletId]); + } + + bool deleteByValueWalletIdSync(String value, String walletId) { + return deleteByIndexSync(r'value_walletId', [value, walletId]); + } + + Future> getAllByValueWalletId( + List valueValues, List walletIdValues) { + final len = valueValues.length; + assert(walletIdValues.length == len, + 'All index values must have the same length'); + final values = >[]; + for (var i = 0; i < len; i++) { + values.add([valueValues[i], walletIdValues[i]]); + } + + return getAllByIndex(r'value_walletId', values); + } + + List getAllByValueWalletIdSync( + List valueValues, List walletIdValues) { + final len = valueValues.length; + assert(walletIdValues.length == len, + 'All index values must have the same length'); + final values = >[]; + for (var i = 0; i < len; i++) { + values.add([valueValues[i], walletIdValues[i]]); + } + + return getAllByIndexSync(r'value_walletId', values); + } + + Future deleteAllByValueWalletId( + List valueValues, List walletIdValues) { + final len = valueValues.length; + assert(walletIdValues.length == len, + 'All index values must have the same length'); + final values = >[]; + for (var i = 0; i < len; i++) { + values.add([valueValues[i], walletIdValues[i]]); + } + + return deleteAllByIndex(r'value_walletId', values); + } + + int deleteAllByValueWalletIdSync( + List valueValues, List walletIdValues) { + final len = valueValues.length; + assert(walletIdValues.length == len, + 'All index values must have the same length'); + final values = >[]; + for (var i = 0; i < len; i++) { + values.add([valueValues[i], walletIdValues[i]]); + } + + return deleteAllByIndexSync(r'value_walletId', values); + } + + Future putByValueWalletId(Address object) { + return putByIndex(r'value_walletId', object); + } + + Id putByValueWalletIdSync(Address object, {bool saveLinks = true}) { + return putByIndexSync(r'value_walletId', object, saveLinks: saveLinks); + } + + Future> putAllByValueWalletId(List
objects) { + return putAllByIndex(r'value_walletId', objects); + } + + List putAllByValueWalletIdSync(List
objects, + {bool saveLinks = true}) { + return putAllByIndexSync(r'value_walletId', objects, saveLinks: saveLinks); + } +} + +extension AddressQueryWhereSort on QueryBuilder { + QueryBuilder anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } + + QueryBuilder anyDerivationIndex() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + const IndexWhereClause.any(indexName: r'derivationIndex'), + ); + }); + } +} + +extension AddressQueryWhere on QueryBuilder { + QueryBuilder idEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: id, + upper: id, + )); + }); + } + + QueryBuilder idNotEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder idGreaterThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder idLessThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder walletIdEqualTo( + String walletId) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'walletId', + value: [walletId], + )); + }); + } + + QueryBuilder walletIdNotEqualTo( + String walletId) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'walletId', + lower: [], + upper: [walletId], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'walletId', + lower: [walletId], + includeLower: false, + upper: [], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'walletId', + lower: [walletId], + includeLower: false, + upper: [], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'walletId', + lower: [], + upper: [walletId], + includeUpper: false, + )); + } + }); + } + + QueryBuilder valueEqualToAnyWalletId( + String value) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'value_walletId', + value: [value], + )); + }); + } + + QueryBuilder valueNotEqualToAnyWalletId( + String value) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'value_walletId', + lower: [], + upper: [value], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'value_walletId', + lower: [value], + includeLower: false, + upper: [], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'value_walletId', + lower: [value], + includeLower: false, + upper: [], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'value_walletId', + lower: [], + upper: [value], + includeUpper: false, + )); + } + }); + } + + QueryBuilder valueWalletIdEqualTo( + String value, String walletId) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'value_walletId', + value: [value, walletId], + )); + }); + } + + QueryBuilder + valueEqualToWalletIdNotEqualTo(String value, String walletId) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'value_walletId', + lower: [value], + upper: [value, walletId], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'value_walletId', + lower: [value, walletId], + includeLower: false, + upper: [value], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'value_walletId', + lower: [value, walletId], + includeLower: false, + upper: [value], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'value_walletId', + lower: [value], + upper: [value, walletId], + includeUpper: false, + )); + } + }); + } + + QueryBuilder derivationIndexEqualTo( + int derivationIndex) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'derivationIndex', + value: [derivationIndex], + )); + }); + } + + QueryBuilder derivationIndexNotEqualTo( + int derivationIndex) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'derivationIndex', + lower: [], + upper: [derivationIndex], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'derivationIndex', + lower: [derivationIndex], + includeLower: false, + upper: [], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'derivationIndex', + lower: [derivationIndex], + includeLower: false, + upper: [], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'derivationIndex', + lower: [], + upper: [derivationIndex], + includeUpper: false, + )); + } + }); + } + + QueryBuilder derivationIndexGreaterThan( + int derivationIndex, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.between( + indexName: r'derivationIndex', + lower: [derivationIndex], + includeLower: include, + upper: [], + )); + }); + } + + QueryBuilder derivationIndexLessThan( + int derivationIndex, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.between( + indexName: r'derivationIndex', + lower: [], + upper: [derivationIndex], + includeUpper: include, + )); + }); + } + + QueryBuilder derivationIndexBetween( + int lowerDerivationIndex, + int upperDerivationIndex, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.between( + indexName: r'derivationIndex', + lower: [lowerDerivationIndex], + includeLower: includeLower, + upper: [upperDerivationIndex], + includeUpper: includeUpper, + )); + }); + } +} + +extension AddressQueryFilter + on QueryBuilder { + QueryBuilder derivationIndexEqualTo( + int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'derivationIndex', + value: value, + )); + }); + } + + QueryBuilder + derivationIndexGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'derivationIndex', + value: value, + )); + }); + } + + QueryBuilder derivationIndexLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'derivationIndex', + value: value, + )); + }); + } + + QueryBuilder derivationIndexBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'derivationIndex', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder derivationPathIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'derivationPath', + )); + }); + } + + QueryBuilder + derivationPathIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'derivationPath', + )); + }); + } + + QueryBuilder idEqualTo(Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idGreaterThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idLessThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder otherDataIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'otherData', + )); + }); + } + + QueryBuilder otherDataIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'otherData', + )); + }); + } + + QueryBuilder otherDataEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'otherData', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder otherDataGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'otherData', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder otherDataLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'otherData', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder otherDataBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'otherData', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder otherDataStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'otherData', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder otherDataEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'otherData', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder otherDataContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'otherData', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder otherDataMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'otherData', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder otherDataIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'otherData', + value: '', + )); + }); + } + + QueryBuilder otherDataIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'otherData', + value: '', + )); + }); + } + + QueryBuilder publicKeyElementEqualTo( + int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'publicKey', + value: value, + )); + }); + } + + QueryBuilder + publicKeyElementGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'publicKey', + value: value, + )); + }); + } + + QueryBuilder + publicKeyElementLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'publicKey', + value: value, + )); + }); + } + + QueryBuilder publicKeyElementBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'publicKey', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder publicKeyLengthEqualTo( + int length) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'publicKey', + length, + true, + length, + true, + ); + }); + } + + QueryBuilder publicKeyIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'publicKey', + 0, + true, + 0, + true, + ); + }); + } + + QueryBuilder publicKeyIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'publicKey', + 0, + false, + 999999, + true, + ); + }); + } + + QueryBuilder publicKeyLengthLessThan( + int length, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'publicKey', + 0, + true, + length, + include, + ); + }); + } + + QueryBuilder + publicKeyLengthGreaterThan( + int length, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'publicKey', + length, + include, + 999999, + true, + ); + }); + } + + QueryBuilder publicKeyLengthBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'publicKey', + lower, + includeLower, + upper, + includeUpper, + ); + }); + } + + QueryBuilder subTypeEqualTo( + AddressSubType value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'subType', + value: value, + )); + }); + } + + QueryBuilder subTypeGreaterThan( + AddressSubType value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'subType', + value: value, + )); + }); + } + + QueryBuilder subTypeLessThan( + AddressSubType value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'subType', + value: value, + )); + }); + } + + QueryBuilder subTypeBetween( + AddressSubType lower, + AddressSubType upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'subType', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder typeEqualTo( + AddressType value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'type', + value: value, + )); + }); + } + + QueryBuilder typeGreaterThan( + AddressType value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'type', + value: value, + )); + }); + } + + QueryBuilder typeLessThan( + AddressType value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'type', + value: value, + )); + }); + } + + QueryBuilder typeBetween( + AddressType lower, + AddressType upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'type', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder valueEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder valueGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder valueLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder valueBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'value', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder valueStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder valueEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder valueContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder valueMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'value', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder valueIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'value', + value: '', + )); + }); + } + + QueryBuilder valueIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'value', + value: '', + )); + }); + } + + QueryBuilder walletIdEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder walletIdGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder walletIdLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder walletIdBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'walletId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder walletIdStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder walletIdEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder walletIdContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder walletIdMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'walletId', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder walletIdIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'walletId', + value: '', + )); + }); + } + + QueryBuilder walletIdIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'walletId', + value: '', + )); + }); + } +} + +extension AddressQueryObject + on QueryBuilder { + QueryBuilder derivationPath( + FilterQuery q) { + return QueryBuilder.apply(this, (query) { + return query.object(q, r'derivationPath'); + }); + } +} + +extension AddressQueryLinks + on QueryBuilder { + QueryBuilder transactions( + FilterQuery q) { + return QueryBuilder.apply(this, (query) { + return query.link(q, r'transactions'); + }); + } + + QueryBuilder + transactionsLengthEqualTo(int length) { + return QueryBuilder.apply(this, (query) { + return query.linkLength(r'transactions', length, true, length, true); + }); + } + + QueryBuilder transactionsIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.linkLength(r'transactions', 0, true, 0, true); + }); + } + + QueryBuilder + transactionsIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.linkLength(r'transactions', 0, false, 999999, true); + }); + } + + QueryBuilder + transactionsLengthLessThan( + int length, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.linkLength(r'transactions', 0, true, length, include); + }); + } + + QueryBuilder + transactionsLengthGreaterThan( + int length, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.linkLength(r'transactions', length, include, 999999, true); + }); + } + + QueryBuilder + transactionsLengthBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.linkLength( + r'transactions', lower, includeLower, upper, includeUpper); + }); + } +} + +extension AddressQuerySortBy on QueryBuilder { + QueryBuilder sortByDerivationIndex() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'derivationIndex', Sort.asc); + }); + } + + QueryBuilder sortByDerivationIndexDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'derivationIndex', Sort.desc); + }); + } + + QueryBuilder sortByOtherData() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'otherData', Sort.asc); + }); + } + + QueryBuilder sortByOtherDataDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'otherData', Sort.desc); + }); + } + + QueryBuilder sortBySubType() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'subType', Sort.asc); + }); + } + + QueryBuilder sortBySubTypeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'subType', Sort.desc); + }); + } + + QueryBuilder sortByType() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'type', Sort.asc); + }); + } + + QueryBuilder sortByTypeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'type', Sort.desc); + }); + } + + QueryBuilder sortByValue() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'value', Sort.asc); + }); + } + + QueryBuilder sortByValueDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'value', Sort.desc); + }); + } + + QueryBuilder sortByWalletId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'walletId', Sort.asc); + }); + } + + QueryBuilder sortByWalletIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'walletId', Sort.desc); + }); + } +} + +extension AddressQuerySortThenBy + on QueryBuilder { + QueryBuilder thenByDerivationIndex() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'derivationIndex', Sort.asc); + }); + } + + QueryBuilder thenByDerivationIndexDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'derivationIndex', Sort.desc); + }); + } + + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder thenByOtherData() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'otherData', Sort.asc); + }); + } + + QueryBuilder thenByOtherDataDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'otherData', Sort.desc); + }); + } + + QueryBuilder thenBySubType() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'subType', Sort.asc); + }); + } + + QueryBuilder thenBySubTypeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'subType', Sort.desc); + }); + } + + QueryBuilder thenByType() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'type', Sort.asc); + }); + } + + QueryBuilder thenByTypeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'type', Sort.desc); + }); + } + + QueryBuilder thenByValue() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'value', Sort.asc); + }); + } + + QueryBuilder thenByValueDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'value', Sort.desc); + }); + } + + QueryBuilder thenByWalletId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'walletId', Sort.asc); + }); + } + + QueryBuilder thenByWalletIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'walletId', Sort.desc); + }); + } +} + +extension AddressQueryWhereDistinct + on QueryBuilder { + QueryBuilder distinctByDerivationIndex() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'derivationIndex'); + }); + } + + QueryBuilder distinctByOtherData( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'otherData', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByPublicKey() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'publicKey'); + }); + } + + QueryBuilder distinctBySubType() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'subType'); + }); + } + + QueryBuilder distinctByType() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'type'); + }); + } + + QueryBuilder distinctByValue( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'value', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByWalletId( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'walletId', caseSensitive: caseSensitive); + }); + } +} + +extension AddressQueryProperty + on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder derivationIndexProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'derivationIndex'); + }); + } + + QueryBuilder + derivationPathProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'derivationPath'); + }); + } + + QueryBuilder otherDataProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'otherData'); + }); + } + + QueryBuilder, QQueryOperations> publicKeyProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'publicKey'); + }); + } + + QueryBuilder subTypeProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'subType'); + }); + } + + QueryBuilder typeProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'type'); + }); + } + + QueryBuilder valueProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'value'); + }); + } + + QueryBuilder walletIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'walletId'); + }); + } +} + +// ************************************************************************** +// IsarEmbeddedGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters + +const DerivationPathSchema = Schema( + name: r'DerivationPath', + id: -7377061614393881103, + properties: { + r'value': PropertySchema( + id: 0, + name: r'value', + type: IsarType.string, + ) + }, + estimateSize: _derivationPathEstimateSize, + serialize: _derivationPathSerialize, + deserialize: _derivationPathDeserialize, + deserializeProp: _derivationPathDeserializeProp, +); + +int _derivationPathEstimateSize( + DerivationPath object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + bytesCount += 3 + object.value.length * 3; + return bytesCount; +} + +void _derivationPathSerialize( + DerivationPath object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeString(offsets[0], object.value); +} + +DerivationPath _derivationPathDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = DerivationPath(); + object.value = reader.readString(offsets[0]); + return object; +} + +P _derivationPathDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readString(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +extension DerivationPathQueryFilter + on QueryBuilder { + QueryBuilder + valueEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + valueGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + valueLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + valueBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'value', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + valueStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + valueEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + valueContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + valueMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'value', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + valueIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'value', + value: '', + )); + }); + } + + QueryBuilder + valueIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'value', + value: '', + )); + }); + } +} + +extension DerivationPathQueryObject + on QueryBuilder {} diff --git a/lib/models/isar/models/blockchain_data/crypto_currency_address.dart b/lib/models/isar/models/blockchain_data/crypto_currency_address.dart new file mode 100644 index 000000000..4c8670a30 --- /dev/null +++ b/lib/models/isar/models/blockchain_data/crypto_currency_address.dart @@ -0,0 +1,3 @@ +abstract class CryptoCurrencyAddress { +// future use? +} diff --git a/lib/models/isar/models/blockchain_data/input.dart b/lib/models/isar/models/blockchain_data/input.dart new file mode 100644 index 000000000..c97cff73d --- /dev/null +++ b/lib/models/isar/models/blockchain_data/input.dart @@ -0,0 +1,63 @@ +import 'dart:convert'; + +import 'package:isar/isar.dart'; + +part 'input.g.dart'; + +@embedded +class Input { + Input({ + this.txid = "error", + this.vout = -1, + this.scriptSig, + this.scriptSigAsm, + this.witness, + this.isCoinbase, + this.sequence, + this.innerRedeemScriptAsm, + }); + + late final String txid; + + late final int vout; + + late final String? scriptSig; + + late final String? scriptSigAsm; + + late final String? witness; + + late final bool? isCoinbase; + + late final int? sequence; + + late final String? innerRedeemScriptAsm; + + String toJsonString() { + final Map result = { + "txid": txid, + "vout": vout, + "scriptSig": scriptSig, + "scriptSigAsm": scriptSigAsm, + "witness": witness, + "isCoinbase": isCoinbase, + "sequence": sequence, + "innerRedeemScriptAsm": innerRedeemScriptAsm, + }; + return jsonEncode(result); + } + + static Input fromJsonString(String jsonString) { + final json = jsonDecode(jsonString); + return Input( + txid: json["txid"] as String, + vout: json["vout"] as int, + scriptSig: json["scriptSig"] as String?, + scriptSigAsm: json["scriptSigAsm"] as String?, + witness: json["witness"] as String?, + isCoinbase: json["isCoinbase"] as bool?, + sequence: json["sequence"] as int?, + innerRedeemScriptAsm: json["innerRedeemScriptAsm"] as String?, + ); + } +} diff --git a/lib/models/isar/models/blockchain_data/input.g.dart b/lib/models/isar/models/blockchain_data/input.g.dart new file mode 100644 index 000000000..608446fea --- /dev/null +++ b/lib/models/isar/models/blockchain_data/input.g.dart @@ -0,0 +1,1028 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'input.dart'; + +// ************************************************************************** +// IsarEmbeddedGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters + +const InputSchema = Schema( + name: r'Input', + id: 1962449150546623042, + properties: { + r'innerRedeemScriptAsm': PropertySchema( + id: 0, + name: r'innerRedeemScriptAsm', + type: IsarType.string, + ), + r'isCoinbase': PropertySchema( + id: 1, + name: r'isCoinbase', + type: IsarType.bool, + ), + r'scriptSig': PropertySchema( + id: 2, + name: r'scriptSig', + type: IsarType.string, + ), + r'scriptSigAsm': PropertySchema( + id: 3, + name: r'scriptSigAsm', + type: IsarType.string, + ), + r'sequence': PropertySchema( + id: 4, + name: r'sequence', + type: IsarType.long, + ), + r'txid': PropertySchema( + id: 5, + name: r'txid', + type: IsarType.string, + ), + r'vout': PropertySchema( + id: 6, + name: r'vout', + type: IsarType.long, + ), + r'witness': PropertySchema( + id: 7, + name: r'witness', + type: IsarType.string, + ) + }, + estimateSize: _inputEstimateSize, + serialize: _inputSerialize, + deserialize: _inputDeserialize, + deserializeProp: _inputDeserializeProp, +); + +int _inputEstimateSize( + Input object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + { + final value = object.innerRedeemScriptAsm; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.scriptSig; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.scriptSigAsm; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + bytesCount += 3 + object.txid.length * 3; + { + final value = object.witness; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + return bytesCount; +} + +void _inputSerialize( + Input object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeString(offsets[0], object.innerRedeemScriptAsm); + writer.writeBool(offsets[1], object.isCoinbase); + writer.writeString(offsets[2], object.scriptSig); + writer.writeString(offsets[3], object.scriptSigAsm); + writer.writeLong(offsets[4], object.sequence); + writer.writeString(offsets[5], object.txid); + writer.writeLong(offsets[6], object.vout); + writer.writeString(offsets[7], object.witness); +} + +Input _inputDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = Input( + innerRedeemScriptAsm: reader.readStringOrNull(offsets[0]), + isCoinbase: reader.readBoolOrNull(offsets[1]), + scriptSig: reader.readStringOrNull(offsets[2]), + scriptSigAsm: reader.readStringOrNull(offsets[3]), + sequence: reader.readLongOrNull(offsets[4]), + txid: reader.readStringOrNull(offsets[5]) ?? "error", + vout: reader.readLongOrNull(offsets[6]) ?? -1, + witness: reader.readStringOrNull(offsets[7]), + ); + return object; +} + +P _inputDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readStringOrNull(offset)) as P; + case 1: + return (reader.readBoolOrNull(offset)) as P; + case 2: + return (reader.readStringOrNull(offset)) as P; + case 3: + return (reader.readStringOrNull(offset)) as P; + case 4: + return (reader.readLongOrNull(offset)) as P; + case 5: + return (reader.readStringOrNull(offset) ?? "error") as P; + case 6: + return (reader.readLongOrNull(offset) ?? -1) as P; + case 7: + return (reader.readStringOrNull(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +extension InputQueryFilter on QueryBuilder { + QueryBuilder + innerRedeemScriptAsmIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'innerRedeemScriptAsm', + )); + }); + } + + QueryBuilder + innerRedeemScriptAsmIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'innerRedeemScriptAsm', + )); + }); + } + + QueryBuilder innerRedeemScriptAsmEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'innerRedeemScriptAsm', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + innerRedeemScriptAsmGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'innerRedeemScriptAsm', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + innerRedeemScriptAsmLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'innerRedeemScriptAsm', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder innerRedeemScriptAsmBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'innerRedeemScriptAsm', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + innerRedeemScriptAsmStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'innerRedeemScriptAsm', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + innerRedeemScriptAsmEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'innerRedeemScriptAsm', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + innerRedeemScriptAsmContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'innerRedeemScriptAsm', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder innerRedeemScriptAsmMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'innerRedeemScriptAsm', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + innerRedeemScriptAsmIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'innerRedeemScriptAsm', + value: '', + )); + }); + } + + QueryBuilder + innerRedeemScriptAsmIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'innerRedeemScriptAsm', + value: '', + )); + }); + } + + QueryBuilder isCoinbaseIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'isCoinbase', + )); + }); + } + + QueryBuilder isCoinbaseIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'isCoinbase', + )); + }); + } + + QueryBuilder isCoinbaseEqualTo( + bool? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'isCoinbase', + value: value, + )); + }); + } + + QueryBuilder scriptSigIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'scriptSig', + )); + }); + } + + QueryBuilder scriptSigIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'scriptSig', + )); + }); + } + + QueryBuilder scriptSigEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'scriptSig', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptSigGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'scriptSig', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptSigLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'scriptSig', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptSigBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'scriptSig', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptSigStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'scriptSig', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptSigEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'scriptSig', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptSigContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'scriptSig', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptSigMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'scriptSig', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptSigIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'scriptSig', + value: '', + )); + }); + } + + QueryBuilder scriptSigIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'scriptSig', + value: '', + )); + }); + } + + QueryBuilder scriptSigAsmIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'scriptSigAsm', + )); + }); + } + + QueryBuilder scriptSigAsmIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'scriptSigAsm', + )); + }); + } + + QueryBuilder scriptSigAsmEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'scriptSigAsm', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptSigAsmGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'scriptSigAsm', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptSigAsmLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'scriptSigAsm', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptSigAsmBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'scriptSigAsm', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptSigAsmStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'scriptSigAsm', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptSigAsmEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'scriptSigAsm', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptSigAsmContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'scriptSigAsm', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptSigAsmMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'scriptSigAsm', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptSigAsmIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'scriptSigAsm', + value: '', + )); + }); + } + + QueryBuilder scriptSigAsmIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'scriptSigAsm', + value: '', + )); + }); + } + + QueryBuilder sequenceIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'sequence', + )); + }); + } + + QueryBuilder sequenceIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'sequence', + )); + }); + } + + QueryBuilder sequenceEqualTo( + int? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'sequence', + value: value, + )); + }); + } + + QueryBuilder sequenceGreaterThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'sequence', + value: value, + )); + }); + } + + QueryBuilder sequenceLessThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'sequence', + value: value, + )); + }); + } + + QueryBuilder sequenceBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'sequence', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder txidEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'txid', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder txidGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'txid', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder txidLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'txid', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder txidBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'txid', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder txidStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'txid', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder txidEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'txid', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder txidContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'txid', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder txidMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'txid', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder txidIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'txid', + value: '', + )); + }); + } + + QueryBuilder txidIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'txid', + value: '', + )); + }); + } + + QueryBuilder voutEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'vout', + value: value, + )); + }); + } + + QueryBuilder voutGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'vout', + value: value, + )); + }); + } + + QueryBuilder voutLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'vout', + value: value, + )); + }); + } + + QueryBuilder voutBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'vout', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder witnessIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'witness', + )); + }); + } + + QueryBuilder witnessIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'witness', + )); + }); + } + + QueryBuilder witnessEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'witness', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder witnessGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'witness', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder witnessLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'witness', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder witnessBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'witness', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder witnessStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'witness', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder witnessEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'witness', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder witnessContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'witness', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder witnessMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'witness', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder witnessIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'witness', + value: '', + )); + }); + } + + QueryBuilder witnessIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'witness', + value: '', + )); + }); + } +} + +extension InputQueryObject on QueryBuilder {} diff --git a/lib/models/isar/models/blockchain_data/output.dart b/lib/models/isar/models/blockchain_data/output.dart new file mode 100644 index 000000000..959fc37a2 --- /dev/null +++ b/lib/models/isar/models/blockchain_data/output.dart @@ -0,0 +1,48 @@ +import 'dart:convert'; + +import 'package:isar/isar.dart'; + +part 'output.g.dart'; + +@embedded +class Output { + Output({ + this.scriptPubKey, + this.scriptPubKeyAsm, + this.scriptPubKeyType, + this.scriptPubKeyAddress = "", + this.value = 0, + }); + + late final String? scriptPubKey; + + late final String? scriptPubKeyAsm; + + late final String? scriptPubKeyType; + + late final String scriptPubKeyAddress; + + late final int value; + + String toJsonString() { + final Map result = { + "scriptPubKey": scriptPubKey, + "scriptPubKeyAsm": scriptPubKeyAsm, + "scriptPubKeyType": scriptPubKeyType, + "scriptPubKeyAddress": scriptPubKeyAddress, + "value": value, + }; + return jsonEncode(result); + } + + static Output fromJsonString(String jsonString) { + final json = jsonDecode(jsonString); + return Output( + scriptPubKey: json["scriptPubKey"] as String?, + scriptPubKeyAsm: json["scriptPubKeyAsm"] as String?, + scriptPubKeyType: json["scriptPubKeyType"] as String?, + scriptPubKeyAddress: json["scriptPubKeyAddress"] as String, + value: json["value"] as int, + ); + } +} diff --git a/lib/models/isar/models/blockchain_data/output.g.dart b/lib/models/isar/models/blockchain_data/output.g.dart new file mode 100644 index 000000000..5e3bbf02d --- /dev/null +++ b/lib/models/isar/models/blockchain_data/output.g.dart @@ -0,0 +1,763 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'output.dart'; + +// ************************************************************************** +// IsarEmbeddedGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters + +const OutputSchema = Schema( + name: r'Output', + id: 3359341097909611106, + properties: { + r'scriptPubKey': PropertySchema( + id: 0, + name: r'scriptPubKey', + type: IsarType.string, + ), + r'scriptPubKeyAddress': PropertySchema( + id: 1, + name: r'scriptPubKeyAddress', + type: IsarType.string, + ), + r'scriptPubKeyAsm': PropertySchema( + id: 2, + name: r'scriptPubKeyAsm', + type: IsarType.string, + ), + r'scriptPubKeyType': PropertySchema( + id: 3, + name: r'scriptPubKeyType', + type: IsarType.string, + ), + r'value': PropertySchema( + id: 4, + name: r'value', + type: IsarType.long, + ) + }, + estimateSize: _outputEstimateSize, + serialize: _outputSerialize, + deserialize: _outputDeserialize, + deserializeProp: _outputDeserializeProp, +); + +int _outputEstimateSize( + Output object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + { + final value = object.scriptPubKey; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + bytesCount += 3 + object.scriptPubKeyAddress.length * 3; + { + final value = object.scriptPubKeyAsm; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.scriptPubKeyType; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + return bytesCount; +} + +void _outputSerialize( + Output object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeString(offsets[0], object.scriptPubKey); + writer.writeString(offsets[1], object.scriptPubKeyAddress); + writer.writeString(offsets[2], object.scriptPubKeyAsm); + writer.writeString(offsets[3], object.scriptPubKeyType); + writer.writeLong(offsets[4], object.value); +} + +Output _outputDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = Output( + scriptPubKey: reader.readStringOrNull(offsets[0]), + scriptPubKeyAddress: reader.readStringOrNull(offsets[1]) ?? "", + scriptPubKeyAsm: reader.readStringOrNull(offsets[2]), + scriptPubKeyType: reader.readStringOrNull(offsets[3]), + value: reader.readLongOrNull(offsets[4]) ?? 0, + ); + return object; +} + +P _outputDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readStringOrNull(offset)) as P; + case 1: + return (reader.readStringOrNull(offset) ?? "") as P; + case 2: + return (reader.readStringOrNull(offset)) as P; + case 3: + return (reader.readStringOrNull(offset)) as P; + case 4: + return (reader.readLongOrNull(offset) ?? 0) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +extension OutputQueryFilter on QueryBuilder { + QueryBuilder scriptPubKeyIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'scriptPubKey', + )); + }); + } + + QueryBuilder scriptPubKeyIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'scriptPubKey', + )); + }); + } + + QueryBuilder scriptPubKeyEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'scriptPubKey', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptPubKeyGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'scriptPubKey', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptPubKeyLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'scriptPubKey', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptPubKeyBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'scriptPubKey', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptPubKeyStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'scriptPubKey', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptPubKeyEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'scriptPubKey', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptPubKeyContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'scriptPubKey', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptPubKeyMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'scriptPubKey', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptPubKeyIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'scriptPubKey', + value: '', + )); + }); + } + + QueryBuilder scriptPubKeyIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'scriptPubKey', + value: '', + )); + }); + } + + QueryBuilder + scriptPubKeyAddressEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'scriptPubKeyAddress', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + scriptPubKeyAddressGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'scriptPubKeyAddress', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + scriptPubKeyAddressLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'scriptPubKeyAddress', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + scriptPubKeyAddressBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'scriptPubKeyAddress', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + scriptPubKeyAddressStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'scriptPubKeyAddress', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + scriptPubKeyAddressEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'scriptPubKeyAddress', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + scriptPubKeyAddressContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'scriptPubKeyAddress', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + scriptPubKeyAddressMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'scriptPubKeyAddress', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + scriptPubKeyAddressIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'scriptPubKeyAddress', + value: '', + )); + }); + } + + QueryBuilder + scriptPubKeyAddressIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'scriptPubKeyAddress', + value: '', + )); + }); + } + + QueryBuilder scriptPubKeyAsmIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'scriptPubKeyAsm', + )); + }); + } + + QueryBuilder + scriptPubKeyAsmIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'scriptPubKeyAsm', + )); + }); + } + + QueryBuilder scriptPubKeyAsmEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'scriptPubKeyAsm', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + scriptPubKeyAsmGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'scriptPubKeyAsm', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptPubKeyAsmLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'scriptPubKeyAsm', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptPubKeyAsmBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'scriptPubKeyAsm', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptPubKeyAsmStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'scriptPubKeyAsm', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptPubKeyAsmEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'scriptPubKeyAsm', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptPubKeyAsmContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'scriptPubKeyAsm', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptPubKeyAsmMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'scriptPubKeyAsm', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptPubKeyAsmIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'scriptPubKeyAsm', + value: '', + )); + }); + } + + QueryBuilder + scriptPubKeyAsmIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'scriptPubKeyAsm', + value: '', + )); + }); + } + + QueryBuilder scriptPubKeyTypeIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'scriptPubKeyType', + )); + }); + } + + QueryBuilder + scriptPubKeyTypeIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'scriptPubKeyType', + )); + }); + } + + QueryBuilder scriptPubKeyTypeEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'scriptPubKeyType', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + scriptPubKeyTypeGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'scriptPubKeyType', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptPubKeyTypeLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'scriptPubKeyType', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptPubKeyTypeBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'scriptPubKeyType', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + scriptPubKeyTypeStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'scriptPubKeyType', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptPubKeyTypeEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'scriptPubKeyType', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptPubKeyTypeContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'scriptPubKeyType', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder scriptPubKeyTypeMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'scriptPubKeyType', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + scriptPubKeyTypeIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'scriptPubKeyType', + value: '', + )); + }); + } + + QueryBuilder + scriptPubKeyTypeIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'scriptPubKeyType', + value: '', + )); + }); + } + + QueryBuilder valueEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'value', + value: value, + )); + }); + } + + QueryBuilder valueGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'value', + value: value, + )); + }); + } + + QueryBuilder valueLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'value', + value: value, + )); + }); + } + + QueryBuilder valueBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'value', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } +} + +extension OutputQueryObject on QueryBuilder {} diff --git a/lib/models/isar/models/blockchain_data/transaction.dart b/lib/models/isar/models/blockchain_data/transaction.dart new file mode 100644 index 000000000..39d2cc0af --- /dev/null +++ b/lib/models/isar/models/blockchain_data/transaction.dart @@ -0,0 +1,237 @@ +import 'dart:convert'; +import 'dart:math'; + +import 'package:isar/isar.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/address.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/input.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/output.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:tuple/tuple.dart'; + +part 'transaction.g.dart'; + +@Collection() +class Transaction { + Transaction({ + required this.walletId, + required this.txid, + required this.timestamp, + required this.type, + required this.subType, + required this.amount, + required this.amountString, + required this.fee, + required this.height, + required this.isCancelled, + required this.isLelantus, + required this.slateId, + required this.otherData, + required this.inputs, + required this.outputs, + required this.nonce, + }); + + Tuple2 copyWith({ + String? walletId, + String? txid, + int? timestamp, + TransactionType? type, + TransactionSubType? subType, + int? amount, + String? amountString, + int? fee, + int? height, + bool? isCancelled, + bool? isLelantus, + String? slateId, + String? otherData, + List? inputs, + List? outputs, + int? nonce, + Id? id, + Address? address, + }) { + return Tuple2( + Transaction( + walletId: walletId ?? this.walletId, + txid: txid ?? this.txid, + timestamp: timestamp ?? this.timestamp, + type: type ?? this.type, + subType: subType ?? this.subType, + amount: amount ?? this.amount, + amountString: amountString ?? this.amountString, + fee: fee ?? this.fee, + height: height ?? this.height, + isCancelled: isCancelled ?? this.isCancelled, + isLelantus: isLelantus ?? this.isLelantus, + slateId: slateId ?? this.slateId, + otherData: otherData ?? this.otherData, + nonce: nonce ?? this.nonce, + inputs: inputs ?? this.inputs, + outputs: outputs ?? this.outputs) + ..id = id ?? this.id, + address ?? this.address.value, + ); + } + + Id id = Isar.autoIncrement; + + @Index() + late final String walletId; + + @Index(unique: true, composite: [CompositeIndex("walletId")]) + late final String txid; + + @Index() + late final int timestamp; + + @enumerated + late final TransactionType type; + + @enumerated + late final TransactionSubType subType; + + @Deprecated("May be inaccurate for large amounts (eth for example)") + late final int amount; + + late String? amountString; + + late final int fee; + + late final int? height; + + late final bool isCancelled; + + late bool? isLelantus; + + late final String? slateId; + + late final String? otherData; + + late final int? nonce; + + late final List inputs; + + late final List outputs; + + @Backlink(to: "transactions") + final address = IsarLink

(); + + @ignore + Amount? _cachedAmount; + + @ignore + Amount get realAmount => + _cachedAmount ??= Amount.fromSerializedJsonString(amountString!); + + int getConfirmations(int currentChainHeight) { + if (height == null || height! <= 0) return 0; + return max(0, currentChainHeight - (height! - 1)); + } + + bool isConfirmed(int currentChainHeight, int minimumConfirms) { + final confirmations = getConfirmations(currentChainHeight); + return confirmations >= minimumConfirms; + } + + @override + toString() => "{ " + "id: $id, " + "walletId: $walletId, " + "txid: $txid, " + "timestamp: $timestamp, " + "type: ${type.name}, " + "subType: ${subType.name}, " + "amount: $amount, " + "amountString: $amountString, " + "fee: $fee, " + "height: $height, " + "isCancelled: $isCancelled, " + "isLelantus: $isLelantus, " + "slateId: $slateId, " + "otherData: $otherData, " + "nonce: $nonce, " + "address: ${address.value}, " + "inputsLength: ${inputs.length}, " + "outputsLength: ${outputs.length}, " + "}"; + + String toJsonString() { + final Map result = { + "walletId": walletId, + "txid": txid, + "timestamp": timestamp, + "type": type.name, + "subType": subType.name, + "amount": amount, + "amountString": amountString, + "fee": fee, + "height": height, + "isCancelled": isCancelled, + "isLelantus": isLelantus, + "slateId": slateId, + "otherData": otherData, + "nonce": nonce, + "address": address.value?.toJsonString(), + "inputs": inputs.map((e) => e.toJsonString()).toList(), + "outputs": outputs.map((e) => e.toJsonString()).toList(), + }; + return jsonEncode(result); + } + + static Tuple2 fromJsonString( + String jsonString, { + String? overrideWalletId, + }) { + final json = jsonDecode(jsonString); + final transaction = Transaction( + walletId: overrideWalletId ?? json["walletId"] as String, + txid: json["txid"] as String, + timestamp: json["timestamp"] as int, + type: TransactionType.values.byName(json["type"] as String), + subType: TransactionSubType.values.byName(json["subType"] as String), + amount: json["amount"] as int, + amountString: json["amountString"] as String, + fee: json["fee"] as int, + height: json["height"] as int?, + isCancelled: json["isCancelled"] as bool, + isLelantus: json["isLelantus"] as bool?, + slateId: json["slateId"] as String?, + otherData: json["otherData"] as String?, + nonce: json["nonce"] as int?, + inputs: List.from(json["inputs"] as List) + .map((e) => Input.fromJsonString(e)) + .toList(), + outputs: List.from(json["outputs"] as List) + .map((e) => Output.fromJsonString(e)) + .toList(), + ); + if (json["address"] == null) { + return Tuple2(transaction, null); + } else { + final address = Address.fromJsonString(json["address"] as String); + return Tuple2(transaction, address); + } + } +} + +// Used in Isar db and stored there as int indexes so adding/removing values +// in this definition should be done extremely carefully in production +enum TransactionType { + // TODO: add more types before prod release? + outgoing, + incoming, + sentToSelf, // should we keep this? + unknown; +} + +// Used in Isar db and stored there as int indexes so adding/removing values +// in this definition should be done extremely carefully in production +enum TransactionSubType { + // TODO: add more types before prod release? + none, + bip47Notification, // bip47 payment code notification transaction flag + mint, // firo specific + join, // firo specific + ethToken; // eth token +} diff --git a/lib/models/isar/models/blockchain_data/transaction.g.dart b/lib/models/isar/models/blockchain_data/transaction.g.dart new file mode 100644 index 000000000..74a5a1652 --- /dev/null +++ b/lib/models/isar/models/blockchain_data/transaction.g.dart @@ -0,0 +1,2788 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'transaction.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters + +extension GetTransactionCollection on Isar { + IsarCollection get transactions => this.collection(); +} + +const TransactionSchema = CollectionSchema( + name: r'Transaction', + id: 5320225499417954855, + properties: { + r'amount': PropertySchema( + id: 0, + name: r'amount', + type: IsarType.long, + ), + r'amountString': PropertySchema( + id: 1, + name: r'amountString', + type: IsarType.string, + ), + r'fee': PropertySchema( + id: 2, + name: r'fee', + type: IsarType.long, + ), + r'height': PropertySchema( + id: 3, + name: r'height', + type: IsarType.long, + ), + r'inputs': PropertySchema( + id: 4, + name: r'inputs', + type: IsarType.objectList, + target: r'Input', + ), + r'isCancelled': PropertySchema( + id: 5, + name: r'isCancelled', + type: IsarType.bool, + ), + r'isLelantus': PropertySchema( + id: 6, + name: r'isLelantus', + type: IsarType.bool, + ), + r'nonce': PropertySchema( + id: 7, + name: r'nonce', + type: IsarType.long, + ), + r'otherData': PropertySchema( + id: 8, + name: r'otherData', + type: IsarType.string, + ), + r'outputs': PropertySchema( + id: 9, + name: r'outputs', + type: IsarType.objectList, + target: r'Output', + ), + r'slateId': PropertySchema( + id: 10, + name: r'slateId', + type: IsarType.string, + ), + r'subType': PropertySchema( + id: 11, + name: r'subType', + type: IsarType.byte, + enumMap: _TransactionsubTypeEnumValueMap, + ), + r'timestamp': PropertySchema( + id: 12, + name: r'timestamp', + type: IsarType.long, + ), + r'txid': PropertySchema( + id: 13, + name: r'txid', + type: IsarType.string, + ), + r'type': PropertySchema( + id: 14, + name: r'type', + type: IsarType.byte, + enumMap: _TransactiontypeEnumValueMap, + ), + r'walletId': PropertySchema( + id: 15, + name: r'walletId', + type: IsarType.string, + ) + }, + estimateSize: _transactionEstimateSize, + serialize: _transactionSerialize, + deserialize: _transactionDeserialize, + deserializeProp: _transactionDeserializeProp, + idName: r'id', + indexes: { + r'walletId': IndexSchema( + id: -1783113319798776304, + name: r'walletId', + unique: false, + replace: false, + properties: [ + IndexPropertySchema( + name: r'walletId', + type: IndexType.hash, + caseSensitive: true, + ) + ], + ), + r'txid_walletId': IndexSchema( + id: -2771771174176035985, + name: r'txid_walletId', + unique: true, + replace: false, + properties: [ + IndexPropertySchema( + name: r'txid', + type: IndexType.hash, + caseSensitive: true, + ), + IndexPropertySchema( + name: r'walletId', + type: IndexType.hash, + caseSensitive: true, + ) + ], + ), + r'timestamp': IndexSchema( + id: 1852253767416892198, + name: r'timestamp', + unique: false, + replace: false, + properties: [ + IndexPropertySchema( + name: r'timestamp', + type: IndexType.value, + caseSensitive: false, + ) + ], + ) + }, + links: { + r'address': LinkSchema( + id: 7060979817661293320, + name: r'address', + target: r'Address', + single: true, + linkName: r'transactions', + ) + }, + embeddedSchemas: {r'Input': InputSchema, r'Output': OutputSchema}, + getId: _transactionGetId, + getLinks: _transactionGetLinks, + attach: _transactionAttach, + version: '3.0.5', +); + +int _transactionEstimateSize( + Transaction object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + { + final value = object.amountString; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + bytesCount += 3 + object.inputs.length * 3; + { + final offsets = allOffsets[Input]!; + for (var i = 0; i < object.inputs.length; i++) { + final value = object.inputs[i]; + bytesCount += InputSchema.estimateSize(value, offsets, allOffsets); + } + } + { + final value = object.otherData; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + bytesCount += 3 + object.outputs.length * 3; + { + final offsets = allOffsets[Output]!; + for (var i = 0; i < object.outputs.length; i++) { + final value = object.outputs[i]; + bytesCount += OutputSchema.estimateSize(value, offsets, allOffsets); + } + } + { + final value = object.slateId; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + bytesCount += 3 + object.txid.length * 3; + bytesCount += 3 + object.walletId.length * 3; + return bytesCount; +} + +void _transactionSerialize( + Transaction object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeLong(offsets[0], object.amount); + writer.writeString(offsets[1], object.amountString); + writer.writeLong(offsets[2], object.fee); + writer.writeLong(offsets[3], object.height); + writer.writeObjectList( + offsets[4], + allOffsets, + InputSchema.serialize, + object.inputs, + ); + writer.writeBool(offsets[5], object.isCancelled); + writer.writeBool(offsets[6], object.isLelantus); + writer.writeLong(offsets[7], object.nonce); + writer.writeString(offsets[8], object.otherData); + writer.writeObjectList( + offsets[9], + allOffsets, + OutputSchema.serialize, + object.outputs, + ); + writer.writeString(offsets[10], object.slateId); + writer.writeByte(offsets[11], object.subType.index); + writer.writeLong(offsets[12], object.timestamp); + writer.writeString(offsets[13], object.txid); + writer.writeByte(offsets[14], object.type.index); + writer.writeString(offsets[15], object.walletId); +} + +Transaction _transactionDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = Transaction( + amount: reader.readLong(offsets[0]), + amountString: reader.readStringOrNull(offsets[1]), + fee: reader.readLong(offsets[2]), + height: reader.readLongOrNull(offsets[3]), + inputs: reader.readObjectList( + offsets[4], + InputSchema.deserialize, + allOffsets, + Input(), + ) ?? + [], + isCancelled: reader.readBool(offsets[5]), + isLelantus: reader.readBoolOrNull(offsets[6]), + nonce: reader.readLongOrNull(offsets[7]), + otherData: reader.readStringOrNull(offsets[8]), + outputs: reader.readObjectList( + offsets[9], + OutputSchema.deserialize, + allOffsets, + Output(), + ) ?? + [], + slateId: reader.readStringOrNull(offsets[10]), + subType: + _TransactionsubTypeValueEnumMap[reader.readByteOrNull(offsets[11])] ?? + TransactionSubType.none, + timestamp: reader.readLong(offsets[12]), + txid: reader.readString(offsets[13]), + type: _TransactiontypeValueEnumMap[reader.readByteOrNull(offsets[14])] ?? + TransactionType.outgoing, + walletId: reader.readString(offsets[15]), + ); + object.id = id; + return object; +} + +P _transactionDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readLong(offset)) as P; + case 1: + return (reader.readStringOrNull(offset)) as P; + case 2: + return (reader.readLong(offset)) as P; + case 3: + return (reader.readLongOrNull(offset)) as P; + case 4: + return (reader.readObjectList( + offset, + InputSchema.deserialize, + allOffsets, + Input(), + ) ?? + []) as P; + case 5: + return (reader.readBool(offset)) as P; + case 6: + return (reader.readBoolOrNull(offset)) as P; + case 7: + return (reader.readLongOrNull(offset)) as P; + case 8: + return (reader.readStringOrNull(offset)) as P; + case 9: + return (reader.readObjectList( + offset, + OutputSchema.deserialize, + allOffsets, + Output(), + ) ?? + []) as P; + case 10: + return (reader.readStringOrNull(offset)) as P; + case 11: + return (_TransactionsubTypeValueEnumMap[reader.readByteOrNull(offset)] ?? + TransactionSubType.none) as P; + case 12: + return (reader.readLong(offset)) as P; + case 13: + return (reader.readString(offset)) as P; + case 14: + return (_TransactiontypeValueEnumMap[reader.readByteOrNull(offset)] ?? + TransactionType.outgoing) as P; + case 15: + return (reader.readString(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +const _TransactionsubTypeEnumValueMap = { + 'none': 0, + 'bip47Notification': 1, + 'mint': 2, + 'join': 3, + 'ethToken': 4, +}; +const _TransactionsubTypeValueEnumMap = { + 0: TransactionSubType.none, + 1: TransactionSubType.bip47Notification, + 2: TransactionSubType.mint, + 3: TransactionSubType.join, + 4: TransactionSubType.ethToken, +}; +const _TransactiontypeEnumValueMap = { + 'outgoing': 0, + 'incoming': 1, + 'sentToSelf': 2, + 'unknown': 3, +}; +const _TransactiontypeValueEnumMap = { + 0: TransactionType.outgoing, + 1: TransactionType.incoming, + 2: TransactionType.sentToSelf, + 3: TransactionType.unknown, +}; + +Id _transactionGetId(Transaction object) { + return object.id; +} + +List> _transactionGetLinks(Transaction object) { + return [object.address]; +} + +void _transactionAttach( + IsarCollection col, Id id, Transaction object) { + object.id = id; + object.address.attach(col, col.isar.collection

(), r'address', id); +} + +extension TransactionByIndex on IsarCollection { + Future getByTxidWalletId(String txid, String walletId) { + return getByIndex(r'txid_walletId', [txid, walletId]); + } + + Transaction? getByTxidWalletIdSync(String txid, String walletId) { + return getByIndexSync(r'txid_walletId', [txid, walletId]); + } + + Future deleteByTxidWalletId(String txid, String walletId) { + return deleteByIndex(r'txid_walletId', [txid, walletId]); + } + + bool deleteByTxidWalletIdSync(String txid, String walletId) { + return deleteByIndexSync(r'txid_walletId', [txid, walletId]); + } + + Future> getAllByTxidWalletId( + List txidValues, List walletIdValues) { + final len = txidValues.length; + assert(walletIdValues.length == len, + 'All index values must have the same length'); + final values = >[]; + for (var i = 0; i < len; i++) { + values.add([txidValues[i], walletIdValues[i]]); + } + + return getAllByIndex(r'txid_walletId', values); + } + + List getAllByTxidWalletIdSync( + List txidValues, List walletIdValues) { + final len = txidValues.length; + assert(walletIdValues.length == len, + 'All index values must have the same length'); + final values = >[]; + for (var i = 0; i < len; i++) { + values.add([txidValues[i], walletIdValues[i]]); + } + + return getAllByIndexSync(r'txid_walletId', values); + } + + Future deleteAllByTxidWalletId( + List txidValues, List walletIdValues) { + final len = txidValues.length; + assert(walletIdValues.length == len, + 'All index values must have the same length'); + final values = >[]; + for (var i = 0; i < len; i++) { + values.add([txidValues[i], walletIdValues[i]]); + } + + return deleteAllByIndex(r'txid_walletId', values); + } + + int deleteAllByTxidWalletIdSync( + List txidValues, List walletIdValues) { + final len = txidValues.length; + assert(walletIdValues.length == len, + 'All index values must have the same length'); + final values = >[]; + for (var i = 0; i < len; i++) { + values.add([txidValues[i], walletIdValues[i]]); + } + + return deleteAllByIndexSync(r'txid_walletId', values); + } + + Future putByTxidWalletId(Transaction object) { + return putByIndex(r'txid_walletId', object); + } + + Id putByTxidWalletIdSync(Transaction object, {bool saveLinks = true}) { + return putByIndexSync(r'txid_walletId', object, saveLinks: saveLinks); + } + + Future> putAllByTxidWalletId(List objects) { + return putAllByIndex(r'txid_walletId', objects); + } + + List putAllByTxidWalletIdSync(List objects, + {bool saveLinks = true}) { + return putAllByIndexSync(r'txid_walletId', objects, saveLinks: saveLinks); + } +} + +extension TransactionQueryWhereSort + on QueryBuilder { + QueryBuilder anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } + + QueryBuilder anyTimestamp() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + const IndexWhereClause.any(indexName: r'timestamp'), + ); + }); + } +} + +extension TransactionQueryWhere + on QueryBuilder { + QueryBuilder idEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: id, + upper: id, + )); + }); + } + + QueryBuilder idNotEqualTo( + Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder idGreaterThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder idLessThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder walletIdEqualTo( + String walletId) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'walletId', + value: [walletId], + )); + }); + } + + QueryBuilder walletIdNotEqualTo( + String walletId) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'walletId', + lower: [], + upper: [walletId], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'walletId', + lower: [walletId], + includeLower: false, + upper: [], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'walletId', + lower: [walletId], + includeLower: false, + upper: [], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'walletId', + lower: [], + upper: [walletId], + includeUpper: false, + )); + } + }); + } + + QueryBuilder + txidEqualToAnyWalletId(String txid) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'txid_walletId', + value: [txid], + )); + }); + } + + QueryBuilder + txidNotEqualToAnyWalletId(String txid) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId', + lower: [], + upper: [txid], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId', + lower: [txid], + includeLower: false, + upper: [], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId', + lower: [txid], + includeLower: false, + upper: [], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId', + lower: [], + upper: [txid], + includeUpper: false, + )); + } + }); + } + + QueryBuilder txidWalletIdEqualTo( + String txid, String walletId) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'txid_walletId', + value: [txid, walletId], + )); + }); + } + + QueryBuilder + txidEqualToWalletIdNotEqualTo(String txid, String walletId) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId', + lower: [txid], + upper: [txid, walletId], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId', + lower: [txid, walletId], + includeLower: false, + upper: [txid], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId', + lower: [txid, walletId], + includeLower: false, + upper: [txid], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId', + lower: [txid], + upper: [txid, walletId], + includeUpper: false, + )); + } + }); + } + + QueryBuilder timestampEqualTo( + int timestamp) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'timestamp', + value: [timestamp], + )); + }); + } + + QueryBuilder timestampNotEqualTo( + int timestamp) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'timestamp', + lower: [], + upper: [timestamp], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'timestamp', + lower: [timestamp], + includeLower: false, + upper: [], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'timestamp', + lower: [timestamp], + includeLower: false, + upper: [], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'timestamp', + lower: [], + upper: [timestamp], + includeUpper: false, + )); + } + }); + } + + QueryBuilder + timestampGreaterThan( + int timestamp, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.between( + indexName: r'timestamp', + lower: [timestamp], + includeLower: include, + upper: [], + )); + }); + } + + QueryBuilder timestampLessThan( + int timestamp, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.between( + indexName: r'timestamp', + lower: [], + upper: [timestamp], + includeUpper: include, + )); + }); + } + + QueryBuilder timestampBetween( + int lowerTimestamp, + int upperTimestamp, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.between( + indexName: r'timestamp', + lower: [lowerTimestamp], + includeLower: includeLower, + upper: [upperTimestamp], + includeUpper: includeUpper, + )); + }); + } +} + +extension TransactionQueryFilter + on QueryBuilder { + QueryBuilder amountEqualTo( + int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'amount', + value: value, + )); + }); + } + + QueryBuilder + amountGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'amount', + value: value, + )); + }); + } + + QueryBuilder amountLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'amount', + value: value, + )); + }); + } + + QueryBuilder amountBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'amount', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + amountStringIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'amountString', + )); + }); + } + + QueryBuilder + amountStringIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'amountString', + )); + }); + } + + QueryBuilder + amountStringEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'amountString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + amountStringGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'amountString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + amountStringLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'amountString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + amountStringBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'amountString', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + amountStringStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'amountString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + amountStringEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'amountString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + amountStringContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'amountString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + amountStringMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'amountString', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + amountStringIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'amountString', + value: '', + )); + }); + } + + QueryBuilder + amountStringIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'amountString', + value: '', + )); + }); + } + + QueryBuilder feeEqualTo( + int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'fee', + value: value, + )); + }); + } + + QueryBuilder feeGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'fee', + value: value, + )); + }); + } + + QueryBuilder feeLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'fee', + value: value, + )); + }); + } + + QueryBuilder feeBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'fee', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder heightIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'height', + )); + }); + } + + QueryBuilder + heightIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'height', + )); + }); + } + + QueryBuilder heightEqualTo( + int? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'height', + value: value, + )); + }); + } + + QueryBuilder + heightGreaterThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'height', + value: value, + )); + }); + } + + QueryBuilder heightLessThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'height', + value: value, + )); + }); + } + + QueryBuilder heightBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'height', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder idEqualTo( + Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idGreaterThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idLessThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + inputsLengthEqualTo(int length) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'inputs', + length, + true, + length, + true, + ); + }); + } + + QueryBuilder + inputsIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'inputs', + 0, + true, + 0, + true, + ); + }); + } + + QueryBuilder + inputsIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'inputs', + 0, + false, + 999999, + true, + ); + }); + } + + QueryBuilder + inputsLengthLessThan( + int length, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'inputs', + 0, + true, + length, + include, + ); + }); + } + + QueryBuilder + inputsLengthGreaterThan( + int length, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'inputs', + length, + include, + 999999, + true, + ); + }); + } + + QueryBuilder + inputsLengthBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'inputs', + lower, + includeLower, + upper, + includeUpper, + ); + }); + } + + QueryBuilder + isCancelledEqualTo(bool value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'isCancelled', + value: value, + )); + }); + } + + QueryBuilder + isLelantusIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'isLelantus', + )); + }); + } + + QueryBuilder + isLelantusIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'isLelantus', + )); + }); + } + + QueryBuilder + isLelantusEqualTo(bool? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'isLelantus', + value: value, + )); + }); + } + + QueryBuilder nonceIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'nonce', + )); + }); + } + + QueryBuilder + nonceIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'nonce', + )); + }); + } + + QueryBuilder nonceEqualTo( + int? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'nonce', + value: value, + )); + }); + } + + QueryBuilder + nonceGreaterThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'nonce', + value: value, + )); + }); + } + + QueryBuilder nonceLessThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'nonce', + value: value, + )); + }); + } + + QueryBuilder nonceBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'nonce', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + otherDataIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'otherData', + )); + }); + } + + QueryBuilder + otherDataIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'otherData', + )); + }); + } + + QueryBuilder + otherDataEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'otherData', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + otherDataGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'otherData', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + otherDataLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'otherData', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + otherDataBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'otherData', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + otherDataStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'otherData', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + otherDataEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'otherData', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + otherDataContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'otherData', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + otherDataMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'otherData', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + otherDataIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'otherData', + value: '', + )); + }); + } + + QueryBuilder + otherDataIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'otherData', + value: '', + )); + }); + } + + QueryBuilder + outputsLengthEqualTo(int length) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'outputs', + length, + true, + length, + true, + ); + }); + } + + QueryBuilder + outputsIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'outputs', + 0, + true, + 0, + true, + ); + }); + } + + QueryBuilder + outputsIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'outputs', + 0, + false, + 999999, + true, + ); + }); + } + + QueryBuilder + outputsLengthLessThan( + int length, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'outputs', + 0, + true, + length, + include, + ); + }); + } + + QueryBuilder + outputsLengthGreaterThan( + int length, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'outputs', + length, + include, + 999999, + true, + ); + }); + } + + QueryBuilder + outputsLengthBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'outputs', + lower, + includeLower, + upper, + includeUpper, + ); + }); + } + + QueryBuilder + slateIdIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'slateId', + )); + }); + } + + QueryBuilder + slateIdIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'slateId', + )); + }); + } + + QueryBuilder slateIdEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'slateId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + slateIdGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'slateId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder slateIdLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'slateId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder slateIdBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'slateId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + slateIdStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'slateId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder slateIdEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'slateId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder slateIdContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'slateId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder slateIdMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'slateId', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + slateIdIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'slateId', + value: '', + )); + }); + } + + QueryBuilder + slateIdIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'slateId', + value: '', + )); + }); + } + + QueryBuilder subTypeEqualTo( + TransactionSubType value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'subType', + value: value, + )); + }); + } + + QueryBuilder + subTypeGreaterThan( + TransactionSubType value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'subType', + value: value, + )); + }); + } + + QueryBuilder subTypeLessThan( + TransactionSubType value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'subType', + value: value, + )); + }); + } + + QueryBuilder subTypeBetween( + TransactionSubType lower, + TransactionSubType upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'subType', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + timestampEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'timestamp', + value: value, + )); + }); + } + + QueryBuilder + timestampGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'timestamp', + value: value, + )); + }); + } + + QueryBuilder + timestampLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'timestamp', + value: value, + )); + }); + } + + QueryBuilder + timestampBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'timestamp', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder txidEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'txid', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder txidGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'txid', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder txidLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'txid', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder txidBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'txid', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder txidStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'txid', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder txidEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'txid', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder txidContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'txid', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder txidMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'txid', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder txidIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'txid', + value: '', + )); + }); + } + + QueryBuilder + txidIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'txid', + value: '', + )); + }); + } + + QueryBuilder typeEqualTo( + TransactionType value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'type', + value: value, + )); + }); + } + + QueryBuilder typeGreaterThan( + TransactionType value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'type', + value: value, + )); + }); + } + + QueryBuilder typeLessThan( + TransactionType value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'type', + value: value, + )); + }); + } + + QueryBuilder typeBetween( + TransactionType lower, + TransactionType upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'type', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder walletIdEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + walletIdGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + walletIdLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder walletIdBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'walletId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + walletIdStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + walletIdEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + walletIdContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder walletIdMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'walletId', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + walletIdIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'walletId', + value: '', + )); + }); + } + + QueryBuilder + walletIdIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'walletId', + value: '', + )); + }); + } +} + +extension TransactionQueryObject + on QueryBuilder { + QueryBuilder inputsElement( + FilterQuery q) { + return QueryBuilder.apply(this, (query) { + return query.object(q, r'inputs'); + }); + } + + QueryBuilder outputsElement( + FilterQuery q) { + return QueryBuilder.apply(this, (query) { + return query.object(q, r'outputs'); + }); + } +} + +extension TransactionQueryLinks + on QueryBuilder { + QueryBuilder address( + FilterQuery
q) { + return QueryBuilder.apply(this, (query) { + return query.link(q, r'address'); + }); + } + + QueryBuilder + addressIsNull() { + return QueryBuilder.apply(this, (query) { + return query.linkLength(r'address', 0, true, 0, true); + }); + } +} + +extension TransactionQuerySortBy + on QueryBuilder { + QueryBuilder sortByAmount() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'amount', Sort.asc); + }); + } + + QueryBuilder sortByAmountDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'amount', Sort.desc); + }); + } + + QueryBuilder sortByAmountString() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'amountString', Sort.asc); + }); + } + + QueryBuilder + sortByAmountStringDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'amountString', Sort.desc); + }); + } + + QueryBuilder sortByFee() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'fee', Sort.asc); + }); + } + + QueryBuilder sortByFeeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'fee', Sort.desc); + }); + } + + QueryBuilder sortByHeight() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'height', Sort.asc); + }); + } + + QueryBuilder sortByHeightDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'height', Sort.desc); + }); + } + + QueryBuilder sortByIsCancelled() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isCancelled', Sort.asc); + }); + } + + QueryBuilder sortByIsCancelledDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isCancelled', Sort.desc); + }); + } + + QueryBuilder sortByIsLelantus() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isLelantus', Sort.asc); + }); + } + + QueryBuilder sortByIsLelantusDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isLelantus', Sort.desc); + }); + } + + QueryBuilder sortByNonce() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'nonce', Sort.asc); + }); + } + + QueryBuilder sortByNonceDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'nonce', Sort.desc); + }); + } + + QueryBuilder sortByOtherData() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'otherData', Sort.asc); + }); + } + + QueryBuilder sortByOtherDataDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'otherData', Sort.desc); + }); + } + + QueryBuilder sortBySlateId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'slateId', Sort.asc); + }); + } + + QueryBuilder sortBySlateIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'slateId', Sort.desc); + }); + } + + QueryBuilder sortBySubType() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'subType', Sort.asc); + }); + } + + QueryBuilder sortBySubTypeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'subType', Sort.desc); + }); + } + + QueryBuilder sortByTimestamp() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'timestamp', Sort.asc); + }); + } + + QueryBuilder sortByTimestampDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'timestamp', Sort.desc); + }); + } + + QueryBuilder sortByTxid() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'txid', Sort.asc); + }); + } + + QueryBuilder sortByTxidDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'txid', Sort.desc); + }); + } + + QueryBuilder sortByType() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'type', Sort.asc); + }); + } + + QueryBuilder sortByTypeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'type', Sort.desc); + }); + } + + QueryBuilder sortByWalletId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'walletId', Sort.asc); + }); + } + + QueryBuilder sortByWalletIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'walletId', Sort.desc); + }); + } +} + +extension TransactionQuerySortThenBy + on QueryBuilder { + QueryBuilder thenByAmount() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'amount', Sort.asc); + }); + } + + QueryBuilder thenByAmountDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'amount', Sort.desc); + }); + } + + QueryBuilder thenByAmountString() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'amountString', Sort.asc); + }); + } + + QueryBuilder + thenByAmountStringDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'amountString', Sort.desc); + }); + } + + QueryBuilder thenByFee() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'fee', Sort.asc); + }); + } + + QueryBuilder thenByFeeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'fee', Sort.desc); + }); + } + + QueryBuilder thenByHeight() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'height', Sort.asc); + }); + } + + QueryBuilder thenByHeightDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'height', Sort.desc); + }); + } + + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder thenByIsCancelled() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isCancelled', Sort.asc); + }); + } + + QueryBuilder thenByIsCancelledDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isCancelled', Sort.desc); + }); + } + + QueryBuilder thenByIsLelantus() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isLelantus', Sort.asc); + }); + } + + QueryBuilder thenByIsLelantusDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isLelantus', Sort.desc); + }); + } + + QueryBuilder thenByNonce() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'nonce', Sort.asc); + }); + } + + QueryBuilder thenByNonceDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'nonce', Sort.desc); + }); + } + + QueryBuilder thenByOtherData() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'otherData', Sort.asc); + }); + } + + QueryBuilder thenByOtherDataDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'otherData', Sort.desc); + }); + } + + QueryBuilder thenBySlateId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'slateId', Sort.asc); + }); + } + + QueryBuilder thenBySlateIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'slateId', Sort.desc); + }); + } + + QueryBuilder thenBySubType() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'subType', Sort.asc); + }); + } + + QueryBuilder thenBySubTypeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'subType', Sort.desc); + }); + } + + QueryBuilder thenByTimestamp() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'timestamp', Sort.asc); + }); + } + + QueryBuilder thenByTimestampDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'timestamp', Sort.desc); + }); + } + + QueryBuilder thenByTxid() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'txid', Sort.asc); + }); + } + + QueryBuilder thenByTxidDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'txid', Sort.desc); + }); + } + + QueryBuilder thenByType() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'type', Sort.asc); + }); + } + + QueryBuilder thenByTypeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'type', Sort.desc); + }); + } + + QueryBuilder thenByWalletId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'walletId', Sort.asc); + }); + } + + QueryBuilder thenByWalletIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'walletId', Sort.desc); + }); + } +} + +extension TransactionQueryWhereDistinct + on QueryBuilder { + QueryBuilder distinctByAmount() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'amount'); + }); + } + + QueryBuilder distinctByAmountString( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'amountString', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByFee() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'fee'); + }); + } + + QueryBuilder distinctByHeight() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'height'); + }); + } + + QueryBuilder distinctByIsCancelled() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'isCancelled'); + }); + } + + QueryBuilder distinctByIsLelantus() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'isLelantus'); + }); + } + + QueryBuilder distinctByNonce() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'nonce'); + }); + } + + QueryBuilder distinctByOtherData( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'otherData', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctBySlateId( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'slateId', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctBySubType() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'subType'); + }); + } + + QueryBuilder distinctByTimestamp() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'timestamp'); + }); + } + + QueryBuilder distinctByTxid( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'txid', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByType() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'type'); + }); + } + + QueryBuilder distinctByWalletId( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'walletId', caseSensitive: caseSensitive); + }); + } +} + +extension TransactionQueryProperty + on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder amountProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'amount'); + }); + } + + QueryBuilder amountStringProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'amountString'); + }); + } + + QueryBuilder feeProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'fee'); + }); + } + + QueryBuilder heightProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'height'); + }); + } + + QueryBuilder, QQueryOperations> inputsProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'inputs'); + }); + } + + QueryBuilder isCancelledProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isCancelled'); + }); + } + + QueryBuilder isLelantusProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isLelantus'); + }); + } + + QueryBuilder nonceProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'nonce'); + }); + } + + QueryBuilder otherDataProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'otherData'); + }); + } + + QueryBuilder, QQueryOperations> outputsProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'outputs'); + }); + } + + QueryBuilder slateIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'slateId'); + }); + } + + QueryBuilder + subTypeProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'subType'); + }); + } + + QueryBuilder timestampProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'timestamp'); + }); + } + + QueryBuilder txidProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'txid'); + }); + } + + QueryBuilder typeProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'type'); + }); + } + + QueryBuilder walletIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'walletId'); + }); + } +} diff --git a/lib/models/isar/models/blockchain_data/utxo.dart b/lib/models/isar/models/blockchain_data/utxo.dart new file mode 100644 index 000000000..ba618b0c1 --- /dev/null +++ b/lib/models/isar/models/blockchain_data/utxo.dart @@ -0,0 +1,137 @@ +import 'dart:math'; + +import 'package:isar/isar.dart'; + +part 'utxo.g.dart'; + +@Collection(accessor: "utxos", inheritance: false) +class UTXO { + UTXO({ + required this.walletId, + required this.txid, + required this.vout, + required this.value, + required this.name, + required this.isBlocked, + required this.blockedReason, + required this.isCoinbase, + required this.blockHash, + required this.blockHeight, + required this.blockTime, + this.address, + this.used, + this.otherData, + }); + + Id id = Isar.autoIncrement; + + @Index() + late final String walletId; + + @Index(unique: true, replace: true, composite: [ + CompositeIndex("walletId"), + CompositeIndex("vout"), + ]) + late final String txid; + + late final int vout; + + late final int value; + + late final String name; + + @Index() + late final bool isBlocked; + + late final String? blockedReason; + + late final bool isCoinbase; + + late final String? blockHash; + + late final int? blockHeight; + + late final int? blockTime; + + late final String? address; + + late final bool? used; + + late final String? otherData; + + int getConfirmations(int currentChainHeight) { + if (blockTime == null || blockHash == null) return 0; + if (blockHeight == null || blockHeight! <= 0) return 0; + return max(0, currentChainHeight - (blockHeight! - 1)); + } + + bool isConfirmed(int currentChainHeight, int minimumConfirms) { + final confirmations = getConfirmations(currentChainHeight); + return confirmations >= minimumConfirms; + } + + UTXO copyWith({ + Id? id, + String? walletId, + String? txid, + int? vout, + int? value, + String? name, + bool? isBlocked, + String? blockedReason, + bool? isCoinbase, + String? blockHash, + int? blockHeight, + int? blockTime, + String? address, + bool? used, + String? otherData, + }) => + UTXO( + walletId: walletId ?? this.walletId, + txid: txid ?? this.txid, + vout: vout ?? this.vout, + value: value ?? this.value, + name: name ?? this.name, + isBlocked: isBlocked ?? this.isBlocked, + blockedReason: blockedReason ?? this.blockedReason, + isCoinbase: isCoinbase ?? this.isCoinbase, + blockHash: blockHash ?? this.blockHash, + blockHeight: blockHeight ?? this.blockHeight, + blockTime: blockTime ?? this.blockTime, + address: address ?? this.address, + used: used ?? this.used, + otherData: otherData ?? this.otherData, + )..id = id ?? this.id; + + @override + String toString() => "{ " + "id: $id, " + "walletId: $walletId, " + "txid: $txid, " + "vout: $vout, " + "value: $value, " + "name: $name, " + "isBlocked: $isBlocked, " + "blockedReason: $blockedReason, " + "isCoinbase: $isCoinbase, " + "blockHash: $blockHash, " + "blockHeight: $blockHeight, " + "blockTime: $blockTime, " + "address: $address, " + "used: $used, " + "otherData: $otherData, " + "}"; + + @override + bool operator ==(Object other) { + return other is UTXO && + other.walletId == walletId && + other.txid == txid && + other.vout == vout; + } + + @override + @ignore + int get hashCode => Object.hashAll([walletId, txid, vout]); +} diff --git a/lib/models/isar/models/blockchain_data/utxo.g.dart b/lib/models/isar/models/blockchain_data/utxo.g.dart new file mode 100644 index 000000000..b12e9b470 --- /dev/null +++ b/lib/models/isar/models/blockchain_data/utxo.g.dart @@ -0,0 +1,2584 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'utxo.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters + +extension GetUTXOCollection on Isar { + IsarCollection get utxos => this.collection(); +} + +const UTXOSchema = CollectionSchema( + name: r'UTXO', + id: 5934032492047519621, + properties: { + r'address': PropertySchema( + id: 0, + name: r'address', + type: IsarType.string, + ), + r'blockHash': PropertySchema( + id: 1, + name: r'blockHash', + type: IsarType.string, + ), + r'blockHeight': PropertySchema( + id: 2, + name: r'blockHeight', + type: IsarType.long, + ), + r'blockTime': PropertySchema( + id: 3, + name: r'blockTime', + type: IsarType.long, + ), + r'blockedReason': PropertySchema( + id: 4, + name: r'blockedReason', + type: IsarType.string, + ), + r'isBlocked': PropertySchema( + id: 5, + name: r'isBlocked', + type: IsarType.bool, + ), + r'isCoinbase': PropertySchema( + id: 6, + name: r'isCoinbase', + type: IsarType.bool, + ), + r'name': PropertySchema( + id: 7, + name: r'name', + type: IsarType.string, + ), + r'otherData': PropertySchema( + id: 8, + name: r'otherData', + type: IsarType.string, + ), + r'txid': PropertySchema( + id: 9, + name: r'txid', + type: IsarType.string, + ), + r'used': PropertySchema( + id: 10, + name: r'used', + type: IsarType.bool, + ), + r'value': PropertySchema( + id: 11, + name: r'value', + type: IsarType.long, + ), + r'vout': PropertySchema( + id: 12, + name: r'vout', + type: IsarType.long, + ), + r'walletId': PropertySchema( + id: 13, + name: r'walletId', + type: IsarType.string, + ) + }, + estimateSize: _uTXOEstimateSize, + serialize: _uTXOSerialize, + deserialize: _uTXODeserialize, + deserializeProp: _uTXODeserializeProp, + idName: r'id', + indexes: { + r'walletId': IndexSchema( + id: -1783113319798776304, + name: r'walletId', + unique: false, + replace: false, + properties: [ + IndexPropertySchema( + name: r'walletId', + type: IndexType.hash, + caseSensitive: true, + ) + ], + ), + r'txid_walletId_vout': IndexSchema( + id: -2984264099359759359, + name: r'txid_walletId_vout', + unique: true, + replace: true, + properties: [ + IndexPropertySchema( + name: r'txid', + type: IndexType.hash, + caseSensitive: true, + ), + IndexPropertySchema( + name: r'walletId', + type: IndexType.hash, + caseSensitive: true, + ), + IndexPropertySchema( + name: r'vout', + type: IndexType.value, + caseSensitive: false, + ) + ], + ), + r'isBlocked': IndexSchema( + id: 4270553749242334751, + name: r'isBlocked', + unique: false, + replace: false, + properties: [ + IndexPropertySchema( + name: r'isBlocked', + type: IndexType.value, + caseSensitive: false, + ) + ], + ) + }, + links: {}, + embeddedSchemas: {}, + getId: _uTXOGetId, + getLinks: _uTXOGetLinks, + attach: _uTXOAttach, + version: '3.0.5', +); + +int _uTXOEstimateSize( + UTXO object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + { + final value = object.address; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.blockHash; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.blockedReason; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + bytesCount += 3 + object.name.length * 3; + { + final value = object.otherData; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + bytesCount += 3 + object.txid.length * 3; + bytesCount += 3 + object.walletId.length * 3; + return bytesCount; +} + +void _uTXOSerialize( + UTXO object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeString(offsets[0], object.address); + writer.writeString(offsets[1], object.blockHash); + writer.writeLong(offsets[2], object.blockHeight); + writer.writeLong(offsets[3], object.blockTime); + writer.writeString(offsets[4], object.blockedReason); + writer.writeBool(offsets[5], object.isBlocked); + writer.writeBool(offsets[6], object.isCoinbase); + writer.writeString(offsets[7], object.name); + writer.writeString(offsets[8], object.otherData); + writer.writeString(offsets[9], object.txid); + writer.writeBool(offsets[10], object.used); + writer.writeLong(offsets[11], object.value); + writer.writeLong(offsets[12], object.vout); + writer.writeString(offsets[13], object.walletId); +} + +UTXO _uTXODeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = UTXO( + address: reader.readStringOrNull(offsets[0]), + blockHash: reader.readStringOrNull(offsets[1]), + blockHeight: reader.readLongOrNull(offsets[2]), + blockTime: reader.readLongOrNull(offsets[3]), + blockedReason: reader.readStringOrNull(offsets[4]), + isBlocked: reader.readBool(offsets[5]), + isCoinbase: reader.readBool(offsets[6]), + name: reader.readString(offsets[7]), + otherData: reader.readStringOrNull(offsets[8]), + txid: reader.readString(offsets[9]), + used: reader.readBoolOrNull(offsets[10]), + value: reader.readLong(offsets[11]), + vout: reader.readLong(offsets[12]), + walletId: reader.readString(offsets[13]), + ); + object.id = id; + return object; +} + +P _uTXODeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readStringOrNull(offset)) as P; + case 1: + return (reader.readStringOrNull(offset)) as P; + case 2: + return (reader.readLongOrNull(offset)) as P; + case 3: + return (reader.readLongOrNull(offset)) as P; + case 4: + return (reader.readStringOrNull(offset)) as P; + case 5: + return (reader.readBool(offset)) as P; + case 6: + return (reader.readBool(offset)) as P; + case 7: + return (reader.readString(offset)) as P; + case 8: + return (reader.readStringOrNull(offset)) as P; + case 9: + return (reader.readString(offset)) as P; + case 10: + return (reader.readBoolOrNull(offset)) as P; + case 11: + return (reader.readLong(offset)) as P; + case 12: + return (reader.readLong(offset)) as P; + case 13: + return (reader.readString(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +Id _uTXOGetId(UTXO object) { + return object.id; +} + +List> _uTXOGetLinks(UTXO object) { + return []; +} + +void _uTXOAttach(IsarCollection col, Id id, UTXO object) { + object.id = id; +} + +extension UTXOByIndex on IsarCollection { + Future getByTxidWalletIdVout(String txid, String walletId, int vout) { + return getByIndex(r'txid_walletId_vout', [txid, walletId, vout]); + } + + UTXO? getByTxidWalletIdVoutSync(String txid, String walletId, int vout) { + return getByIndexSync(r'txid_walletId_vout', [txid, walletId, vout]); + } + + Future deleteByTxidWalletIdVout( + String txid, String walletId, int vout) { + return deleteByIndex(r'txid_walletId_vout', [txid, walletId, vout]); + } + + bool deleteByTxidWalletIdVoutSync(String txid, String walletId, int vout) { + return deleteByIndexSync(r'txid_walletId_vout', [txid, walletId, vout]); + } + + Future> getAllByTxidWalletIdVout(List txidValues, + List walletIdValues, List voutValues) { + final len = txidValues.length; + assert(walletIdValues.length == len && voutValues.length == len, + 'All index values must have the same length'); + final values = >[]; + for (var i = 0; i < len; i++) { + values.add([txidValues[i], walletIdValues[i], voutValues[i]]); + } + + return getAllByIndex(r'txid_walletId_vout', values); + } + + List getAllByTxidWalletIdVoutSync(List txidValues, + List walletIdValues, List voutValues) { + final len = txidValues.length; + assert(walletIdValues.length == len && voutValues.length == len, + 'All index values must have the same length'); + final values = >[]; + for (var i = 0; i < len; i++) { + values.add([txidValues[i], walletIdValues[i], voutValues[i]]); + } + + return getAllByIndexSync(r'txid_walletId_vout', values); + } + + Future deleteAllByTxidWalletIdVout(List txidValues, + List walletIdValues, List voutValues) { + final len = txidValues.length; + assert(walletIdValues.length == len && voutValues.length == len, + 'All index values must have the same length'); + final values = >[]; + for (var i = 0; i < len; i++) { + values.add([txidValues[i], walletIdValues[i], voutValues[i]]); + } + + return deleteAllByIndex(r'txid_walletId_vout', values); + } + + int deleteAllByTxidWalletIdVoutSync(List txidValues, + List walletIdValues, List voutValues) { + final len = txidValues.length; + assert(walletIdValues.length == len && voutValues.length == len, + 'All index values must have the same length'); + final values = >[]; + for (var i = 0; i < len; i++) { + values.add([txidValues[i], walletIdValues[i], voutValues[i]]); + } + + return deleteAllByIndexSync(r'txid_walletId_vout', values); + } + + Future putByTxidWalletIdVout(UTXO object) { + return putByIndex(r'txid_walletId_vout', object); + } + + Id putByTxidWalletIdVoutSync(UTXO object, {bool saveLinks = true}) { + return putByIndexSync(r'txid_walletId_vout', object, saveLinks: saveLinks); + } + + Future> putAllByTxidWalletIdVout(List objects) { + return putAllByIndex(r'txid_walletId_vout', objects); + } + + List putAllByTxidWalletIdVoutSync(List objects, + {bool saveLinks = true}) { + return putAllByIndexSync(r'txid_walletId_vout', objects, + saveLinks: saveLinks); + } +} + +extension UTXOQueryWhereSort on QueryBuilder { + QueryBuilder anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } + + QueryBuilder anyIsBlocked() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + const IndexWhereClause.any(indexName: r'isBlocked'), + ); + }); + } +} + +extension UTXOQueryWhere on QueryBuilder { + QueryBuilder idEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: id, + upper: id, + )); + }); + } + + QueryBuilder idNotEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder idGreaterThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder idLessThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder walletIdEqualTo(String walletId) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'walletId', + value: [walletId], + )); + }); + } + + QueryBuilder walletIdNotEqualTo( + String walletId) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'walletId', + lower: [], + upper: [walletId], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'walletId', + lower: [walletId], + includeLower: false, + upper: [], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'walletId', + lower: [walletId], + includeLower: false, + upper: [], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'walletId', + lower: [], + upper: [walletId], + includeUpper: false, + )); + } + }); + } + + QueryBuilder txidEqualToAnyWalletIdVout( + String txid) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'txid_walletId_vout', + value: [txid], + )); + }); + } + + QueryBuilder txidNotEqualToAnyWalletIdVout( + String txid) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId_vout', + lower: [], + upper: [txid], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId_vout', + lower: [txid], + includeLower: false, + upper: [], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId_vout', + lower: [txid], + includeLower: false, + upper: [], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId_vout', + lower: [], + upper: [txid], + includeUpper: false, + )); + } + }); + } + + QueryBuilder txidWalletIdEqualToAnyVout( + String txid, String walletId) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'txid_walletId_vout', + value: [txid, walletId], + )); + }); + } + + QueryBuilder + txidEqualToWalletIdNotEqualToAnyVout(String txid, String walletId) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId_vout', + lower: [txid], + upper: [txid, walletId], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId_vout', + lower: [txid, walletId], + includeLower: false, + upper: [txid], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId_vout', + lower: [txid, walletId], + includeLower: false, + upper: [txid], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId_vout', + lower: [txid], + upper: [txid, walletId], + includeUpper: false, + )); + } + }); + } + + QueryBuilder txidWalletIdVoutEqualTo( + String txid, String walletId, int vout) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'txid_walletId_vout', + value: [txid, walletId, vout], + )); + }); + } + + QueryBuilder txidWalletIdEqualToVoutNotEqualTo( + String txid, String walletId, int vout) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId_vout', + lower: [txid, walletId], + upper: [txid, walletId, vout], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId_vout', + lower: [txid, walletId, vout], + includeLower: false, + upper: [txid, walletId], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId_vout', + lower: [txid, walletId, vout], + includeLower: false, + upper: [txid, walletId], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId_vout', + lower: [txid, walletId], + upper: [txid, walletId, vout], + includeUpper: false, + )); + } + }); + } + + QueryBuilder + txidWalletIdEqualToVoutGreaterThan( + String txid, + String walletId, + int vout, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId_vout', + lower: [txid, walletId, vout], + includeLower: include, + upper: [txid, walletId], + )); + }); + } + + QueryBuilder txidWalletIdEqualToVoutLessThan( + String txid, + String walletId, + int vout, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId_vout', + lower: [txid, walletId], + upper: [txid, walletId, vout], + includeUpper: include, + )); + }); + } + + QueryBuilder txidWalletIdEqualToVoutBetween( + String txid, + String walletId, + int lowerVout, + int upperVout, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId_vout', + lower: [txid, walletId, lowerVout], + includeLower: includeLower, + upper: [txid, walletId, upperVout], + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder isBlockedEqualTo(bool isBlocked) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'isBlocked', + value: [isBlocked], + )); + }); + } + + QueryBuilder isBlockedNotEqualTo( + bool isBlocked) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'isBlocked', + lower: [], + upper: [isBlocked], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'isBlocked', + lower: [isBlocked], + includeLower: false, + upper: [], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'isBlocked', + lower: [isBlocked], + includeLower: false, + upper: [], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'isBlocked', + lower: [], + upper: [isBlocked], + includeUpper: false, + )); + } + }); + } +} + +extension UTXOQueryFilter on QueryBuilder { + QueryBuilder addressIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'address', + )); + }); + } + + QueryBuilder addressIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'address', + )); + }); + } + + QueryBuilder addressEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'address', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder addressGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'address', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder addressLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'address', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder addressBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'address', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder addressStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'address', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder addressEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'address', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder addressContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'address', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder addressMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'address', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder addressIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'address', + value: '', + )); + }); + } + + QueryBuilder addressIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'address', + value: '', + )); + }); + } + + QueryBuilder blockHashIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'blockHash', + )); + }); + } + + QueryBuilder blockHashIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'blockHash', + )); + }); + } + + QueryBuilder blockHashEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'blockHash', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder blockHashGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'blockHash', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder blockHashLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'blockHash', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder blockHashBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'blockHash', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder blockHashStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'blockHash', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder blockHashEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'blockHash', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder blockHashContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'blockHash', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder blockHashMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'blockHash', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder blockHashIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'blockHash', + value: '', + )); + }); + } + + QueryBuilder blockHashIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'blockHash', + value: '', + )); + }); + } + + QueryBuilder blockHeightIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'blockHeight', + )); + }); + } + + QueryBuilder blockHeightIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'blockHeight', + )); + }); + } + + QueryBuilder blockHeightEqualTo( + int? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'blockHeight', + value: value, + )); + }); + } + + QueryBuilder blockHeightGreaterThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'blockHeight', + value: value, + )); + }); + } + + QueryBuilder blockHeightLessThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'blockHeight', + value: value, + )); + }); + } + + QueryBuilder blockHeightBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'blockHeight', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder blockTimeIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'blockTime', + )); + }); + } + + QueryBuilder blockTimeIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'blockTime', + )); + }); + } + + QueryBuilder blockTimeEqualTo(int? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'blockTime', + value: value, + )); + }); + } + + QueryBuilder blockTimeGreaterThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'blockTime', + value: value, + )); + }); + } + + QueryBuilder blockTimeLessThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'blockTime', + value: value, + )); + }); + } + + QueryBuilder blockTimeBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'blockTime', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder blockedReasonIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'blockedReason', + )); + }); + } + + QueryBuilder blockedReasonIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'blockedReason', + )); + }); + } + + QueryBuilder blockedReasonEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'blockedReason', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder blockedReasonGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'blockedReason', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder blockedReasonLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'blockedReason', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder blockedReasonBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'blockedReason', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder blockedReasonStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'blockedReason', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder blockedReasonEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'blockedReason', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder blockedReasonContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'blockedReason', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder blockedReasonMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'blockedReason', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder blockedReasonIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'blockedReason', + value: '', + )); + }); + } + + QueryBuilder blockedReasonIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'blockedReason', + value: '', + )); + }); + } + + QueryBuilder idEqualTo(Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idGreaterThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idLessThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder isBlockedEqualTo(bool value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'isBlocked', + value: value, + )); + }); + } + + QueryBuilder isCoinbaseEqualTo( + bool value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'isCoinbase', + value: value, + )); + }); + } + + QueryBuilder nameEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'name', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'name', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'name', + value: '', + )); + }); + } + + QueryBuilder nameIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'name', + value: '', + )); + }); + } + + QueryBuilder otherDataIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'otherData', + )); + }); + } + + QueryBuilder otherDataIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'otherData', + )); + }); + } + + QueryBuilder otherDataEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'otherData', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder otherDataGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'otherData', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder otherDataLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'otherData', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder otherDataBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'otherData', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder otherDataStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'otherData', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder otherDataEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'otherData', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder otherDataContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'otherData', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder otherDataMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'otherData', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder otherDataIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'otherData', + value: '', + )); + }); + } + + QueryBuilder otherDataIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'otherData', + value: '', + )); + }); + } + + QueryBuilder txidEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'txid', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder txidGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'txid', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder txidLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'txid', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder txidBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'txid', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder txidStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'txid', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder txidEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'txid', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder txidContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'txid', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder txidMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'txid', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder txidIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'txid', + value: '', + )); + }); + } + + QueryBuilder txidIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'txid', + value: '', + )); + }); + } + + QueryBuilder usedIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'used', + )); + }); + } + + QueryBuilder usedIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'used', + )); + }); + } + + QueryBuilder usedEqualTo(bool? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'used', + value: value, + )); + }); + } + + QueryBuilder valueEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'value', + value: value, + )); + }); + } + + QueryBuilder valueGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'value', + value: value, + )); + }); + } + + QueryBuilder valueLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'value', + value: value, + )); + }); + } + + QueryBuilder valueBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'value', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder voutEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'vout', + value: value, + )); + }); + } + + QueryBuilder voutGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'vout', + value: value, + )); + }); + } + + QueryBuilder voutLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'vout', + value: value, + )); + }); + } + + QueryBuilder voutBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'vout', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder walletIdEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder walletIdGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder walletIdLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder walletIdBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'walletId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder walletIdStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder walletIdEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder walletIdContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder walletIdMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'walletId', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder walletIdIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'walletId', + value: '', + )); + }); + } + + QueryBuilder walletIdIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'walletId', + value: '', + )); + }); + } +} + +extension UTXOQueryObject on QueryBuilder {} + +extension UTXOQueryLinks on QueryBuilder {} + +extension UTXOQuerySortBy on QueryBuilder { + QueryBuilder sortByAddress() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'address', Sort.asc); + }); + } + + QueryBuilder sortByAddressDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'address', Sort.desc); + }); + } + + QueryBuilder sortByBlockHash() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'blockHash', Sort.asc); + }); + } + + QueryBuilder sortByBlockHashDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'blockHash', Sort.desc); + }); + } + + QueryBuilder sortByBlockHeight() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'blockHeight', Sort.asc); + }); + } + + QueryBuilder sortByBlockHeightDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'blockHeight', Sort.desc); + }); + } + + QueryBuilder sortByBlockTime() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'blockTime', Sort.asc); + }); + } + + QueryBuilder sortByBlockTimeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'blockTime', Sort.desc); + }); + } + + QueryBuilder sortByBlockedReason() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'blockedReason', Sort.asc); + }); + } + + QueryBuilder sortByBlockedReasonDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'blockedReason', Sort.desc); + }); + } + + QueryBuilder sortByIsBlocked() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isBlocked', Sort.asc); + }); + } + + QueryBuilder sortByIsBlockedDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isBlocked', Sort.desc); + }); + } + + QueryBuilder sortByIsCoinbase() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isCoinbase', Sort.asc); + }); + } + + QueryBuilder sortByIsCoinbaseDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isCoinbase', Sort.desc); + }); + } + + QueryBuilder sortByName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.asc); + }); + } + + QueryBuilder sortByNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.desc); + }); + } + + QueryBuilder sortByOtherData() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'otherData', Sort.asc); + }); + } + + QueryBuilder sortByOtherDataDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'otherData', Sort.desc); + }); + } + + QueryBuilder sortByTxid() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'txid', Sort.asc); + }); + } + + QueryBuilder sortByTxidDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'txid', Sort.desc); + }); + } + + QueryBuilder sortByUsed() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'used', Sort.asc); + }); + } + + QueryBuilder sortByUsedDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'used', Sort.desc); + }); + } + + QueryBuilder sortByValue() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'value', Sort.asc); + }); + } + + QueryBuilder sortByValueDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'value', Sort.desc); + }); + } + + QueryBuilder sortByVout() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'vout', Sort.asc); + }); + } + + QueryBuilder sortByVoutDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'vout', Sort.desc); + }); + } + + QueryBuilder sortByWalletId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'walletId', Sort.asc); + }); + } + + QueryBuilder sortByWalletIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'walletId', Sort.desc); + }); + } +} + +extension UTXOQuerySortThenBy on QueryBuilder { + QueryBuilder thenByAddress() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'address', Sort.asc); + }); + } + + QueryBuilder thenByAddressDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'address', Sort.desc); + }); + } + + QueryBuilder thenByBlockHash() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'blockHash', Sort.asc); + }); + } + + QueryBuilder thenByBlockHashDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'blockHash', Sort.desc); + }); + } + + QueryBuilder thenByBlockHeight() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'blockHeight', Sort.asc); + }); + } + + QueryBuilder thenByBlockHeightDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'blockHeight', Sort.desc); + }); + } + + QueryBuilder thenByBlockTime() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'blockTime', Sort.asc); + }); + } + + QueryBuilder thenByBlockTimeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'blockTime', Sort.desc); + }); + } + + QueryBuilder thenByBlockedReason() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'blockedReason', Sort.asc); + }); + } + + QueryBuilder thenByBlockedReasonDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'blockedReason', Sort.desc); + }); + } + + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder thenByIsBlocked() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isBlocked', Sort.asc); + }); + } + + QueryBuilder thenByIsBlockedDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isBlocked', Sort.desc); + }); + } + + QueryBuilder thenByIsCoinbase() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isCoinbase', Sort.asc); + }); + } + + QueryBuilder thenByIsCoinbaseDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isCoinbase', Sort.desc); + }); + } + + QueryBuilder thenByName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.asc); + }); + } + + QueryBuilder thenByNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.desc); + }); + } + + QueryBuilder thenByOtherData() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'otherData', Sort.asc); + }); + } + + QueryBuilder thenByOtherDataDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'otherData', Sort.desc); + }); + } + + QueryBuilder thenByTxid() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'txid', Sort.asc); + }); + } + + QueryBuilder thenByTxidDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'txid', Sort.desc); + }); + } + + QueryBuilder thenByUsed() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'used', Sort.asc); + }); + } + + QueryBuilder thenByUsedDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'used', Sort.desc); + }); + } + + QueryBuilder thenByValue() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'value', Sort.asc); + }); + } + + QueryBuilder thenByValueDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'value', Sort.desc); + }); + } + + QueryBuilder thenByVout() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'vout', Sort.asc); + }); + } + + QueryBuilder thenByVoutDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'vout', Sort.desc); + }); + } + + QueryBuilder thenByWalletId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'walletId', Sort.asc); + }); + } + + QueryBuilder thenByWalletIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'walletId', Sort.desc); + }); + } +} + +extension UTXOQueryWhereDistinct on QueryBuilder { + QueryBuilder distinctByAddress( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'address', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByBlockHash( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'blockHash', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByBlockHeight() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'blockHeight'); + }); + } + + QueryBuilder distinctByBlockTime() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'blockTime'); + }); + } + + QueryBuilder distinctByBlockedReason( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'blockedReason', + caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByIsBlocked() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'isBlocked'); + }); + } + + QueryBuilder distinctByIsCoinbase() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'isCoinbase'); + }); + } + + QueryBuilder distinctByName( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'name', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByOtherData( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'otherData', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByTxid( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'txid', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByUsed() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'used'); + }); + } + + QueryBuilder distinctByValue() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'value'); + }); + } + + QueryBuilder distinctByVout() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'vout'); + }); + } + + QueryBuilder distinctByWalletId( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'walletId', caseSensitive: caseSensitive); + }); + } +} + +extension UTXOQueryProperty on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder addressProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'address'); + }); + } + + QueryBuilder blockHashProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'blockHash'); + }); + } + + QueryBuilder blockHeightProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'blockHeight'); + }); + } + + QueryBuilder blockTimeProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'blockTime'); + }); + } + + QueryBuilder blockedReasonProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'blockedReason'); + }); + } + + QueryBuilder isBlockedProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isBlocked'); + }); + } + + QueryBuilder isCoinbaseProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isCoinbase'); + }); + } + + QueryBuilder nameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'name'); + }); + } + + QueryBuilder otherDataProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'otherData'); + }); + } + + QueryBuilder txidProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'txid'); + }); + } + + QueryBuilder usedProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'used'); + }); + } + + QueryBuilder valueProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'value'); + }); + } + + QueryBuilder voutProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'vout'); + }); + } + + QueryBuilder walletIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'walletId'); + }); + } +} diff --git a/lib/models/isar/models/contact_entry.dart b/lib/models/isar/models/contact_entry.dart new file mode 100644 index 000000000..676b33d23 --- /dev/null +++ b/lib/models/isar/models/contact_entry.dart @@ -0,0 +1,108 @@ +import 'package:isar/isar.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; + +part 'contact_entry.g.dart'; + +@collection +class ContactEntry { + ContactEntry({ + this.emojiChar, + required this.name, + required this.addresses, + required this.isFavorite, + required this.customId, + }); + + Id id = Isar.autoIncrement; + + late final String? emojiChar; + late final String name; + late final List addresses; + late final bool isFavorite; + + @Index(unique: true, replace: true) + late final String customId; + + ContactEntry copyWith({ + bool shouldCopyEmojiWithNull = false, + String? emojiChar, + String? name, + List? addresses, + bool? isFavorite, + }) { + List _addresses = []; + if (addresses == null) { + for (var e in this.addresses) { + _addresses.add(e.copyWith()); + } + } else { + for (var e in addresses) { + _addresses.add(e.copyWith()); + } + } + String? newEmoji; + if (shouldCopyEmojiWithNull) { + newEmoji = emojiChar; + } else { + newEmoji = emojiChar ?? this.emojiChar; + } + + return ContactEntry( + emojiChar: newEmoji, + name: name ?? this.name, + addresses: _addresses, + isFavorite: isFavorite ?? this.isFavorite, + customId: customId, + ); + } + + Map toMap() { + return { + "emoji": emojiChar, + "name": name, + "addresses": addresses.map((e) => e.toMap()).toList(), + "id": customId, + "isFavorite": isFavorite, + }; + } +} + +@embedded +class ContactAddressEntry { + late final String coinName; + late final String address; + late final String label; + late final String? other; + + @ignore + Coin get coin => Coin.values.byName(coinName); + + ContactAddressEntry(); + + ContactAddressEntry copyWith({ + Coin? coin, + String? address, + String? label, + String? other, + }) { + return ContactAddressEntry() + ..coinName = coin?.name ?? coinName + ..address = address ?? this.address + ..label = label ?? this.label + ..other = other ?? this.other; + } + + Map toMap() { + return { + "label": label, + "address": address, + "coin": coin.name, + "other": other ?? "", + }; + } + + @override + String toString() { + return "AddressBookEntry: ${toMap()}"; + } +} diff --git a/lib/models/isar/models/contact_entry.g.dart b/lib/models/isar/models/contact_entry.g.dart new file mode 100644 index 000000000..dc1eb2b61 --- /dev/null +++ b/lib/models/isar/models/contact_entry.g.dart @@ -0,0 +1,1808 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'contact_entry.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters + +extension GetContactEntryCollection on Isar { + IsarCollection get contactEntrys => this.collection(); +} + +const ContactEntrySchema = CollectionSchema( + name: r'ContactEntry', + id: -3248212280610531288, + properties: { + r'addresses': PropertySchema( + id: 0, + name: r'addresses', + type: IsarType.objectList, + target: r'ContactAddressEntry', + ), + r'customId': PropertySchema( + id: 1, + name: r'customId', + type: IsarType.string, + ), + r'emojiChar': PropertySchema( + id: 2, + name: r'emojiChar', + type: IsarType.string, + ), + r'isFavorite': PropertySchema( + id: 3, + name: r'isFavorite', + type: IsarType.bool, + ), + r'name': PropertySchema( + id: 4, + name: r'name', + type: IsarType.string, + ) + }, + estimateSize: _contactEntryEstimateSize, + serialize: _contactEntrySerialize, + deserialize: _contactEntryDeserialize, + deserializeProp: _contactEntryDeserializeProp, + idName: r'id', + indexes: { + r'customId': IndexSchema( + id: -7523974382886476007, + name: r'customId', + unique: true, + replace: true, + properties: [ + IndexPropertySchema( + name: r'customId', + type: IndexType.hash, + caseSensitive: true, + ) + ], + ) + }, + links: {}, + embeddedSchemas: {r'ContactAddressEntry': ContactAddressEntrySchema}, + getId: _contactEntryGetId, + getLinks: _contactEntryGetLinks, + attach: _contactEntryAttach, + version: '3.0.5', +); + +int _contactEntryEstimateSize( + ContactEntry object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + bytesCount += 3 + object.addresses.length * 3; + { + final offsets = allOffsets[ContactAddressEntry]!; + for (var i = 0; i < object.addresses.length; i++) { + final value = object.addresses[i]; + bytesCount += + ContactAddressEntrySchema.estimateSize(value, offsets, allOffsets); + } + } + bytesCount += 3 + object.customId.length * 3; + { + final value = object.emojiChar; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + bytesCount += 3 + object.name.length * 3; + return bytesCount; +} + +void _contactEntrySerialize( + ContactEntry object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeObjectList( + offsets[0], + allOffsets, + ContactAddressEntrySchema.serialize, + object.addresses, + ); + writer.writeString(offsets[1], object.customId); + writer.writeString(offsets[2], object.emojiChar); + writer.writeBool(offsets[3], object.isFavorite); + writer.writeString(offsets[4], object.name); +} + +ContactEntry _contactEntryDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = ContactEntry( + addresses: reader.readObjectList( + offsets[0], + ContactAddressEntrySchema.deserialize, + allOffsets, + ContactAddressEntry(), + ) ?? + [], + customId: reader.readString(offsets[1]), + emojiChar: reader.readStringOrNull(offsets[2]), + isFavorite: reader.readBool(offsets[3]), + name: reader.readString(offsets[4]), + ); + object.id = id; + return object; +} + +P _contactEntryDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readObjectList( + offset, + ContactAddressEntrySchema.deserialize, + allOffsets, + ContactAddressEntry(), + ) ?? + []) as P; + case 1: + return (reader.readString(offset)) as P; + case 2: + return (reader.readStringOrNull(offset)) as P; + case 3: + return (reader.readBool(offset)) as P; + case 4: + return (reader.readString(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +Id _contactEntryGetId(ContactEntry object) { + return object.id; +} + +List> _contactEntryGetLinks(ContactEntry object) { + return []; +} + +void _contactEntryAttach( + IsarCollection col, Id id, ContactEntry object) { + object.id = id; +} + +extension ContactEntryByIndex on IsarCollection { + Future getByCustomId(String customId) { + return getByIndex(r'customId', [customId]); + } + + ContactEntry? getByCustomIdSync(String customId) { + return getByIndexSync(r'customId', [customId]); + } + + Future deleteByCustomId(String customId) { + return deleteByIndex(r'customId', [customId]); + } + + bool deleteByCustomIdSync(String customId) { + return deleteByIndexSync(r'customId', [customId]); + } + + Future> getAllByCustomId(List customIdValues) { + final values = customIdValues.map((e) => [e]).toList(); + return getAllByIndex(r'customId', values); + } + + List getAllByCustomIdSync(List customIdValues) { + final values = customIdValues.map((e) => [e]).toList(); + return getAllByIndexSync(r'customId', values); + } + + Future deleteAllByCustomId(List customIdValues) { + final values = customIdValues.map((e) => [e]).toList(); + return deleteAllByIndex(r'customId', values); + } + + int deleteAllByCustomIdSync(List customIdValues) { + final values = customIdValues.map((e) => [e]).toList(); + return deleteAllByIndexSync(r'customId', values); + } + + Future putByCustomId(ContactEntry object) { + return putByIndex(r'customId', object); + } + + Id putByCustomIdSync(ContactEntry object, {bool saveLinks = true}) { + return putByIndexSync(r'customId', object, saveLinks: saveLinks); + } + + Future> putAllByCustomId(List objects) { + return putAllByIndex(r'customId', objects); + } + + List putAllByCustomIdSync(List objects, + {bool saveLinks = true}) { + return putAllByIndexSync(r'customId', objects, saveLinks: saveLinks); + } +} + +extension ContactEntryQueryWhereSort + on QueryBuilder { + QueryBuilder anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension ContactEntryQueryWhere + on QueryBuilder { + QueryBuilder idEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: id, + upper: id, + )); + }); + } + + QueryBuilder idNotEqualTo( + Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder idGreaterThan( + Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder idLessThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder customIdEqualTo( + String customId) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'customId', + value: [customId], + )); + }); + } + + QueryBuilder + customIdNotEqualTo(String customId) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'customId', + lower: [], + upper: [customId], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'customId', + lower: [customId], + includeLower: false, + upper: [], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'customId', + lower: [customId], + includeLower: false, + upper: [], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'customId', + lower: [], + upper: [customId], + includeUpper: false, + )); + } + }); + } +} + +extension ContactEntryQueryFilter + on QueryBuilder { + QueryBuilder + addressesLengthEqualTo(int length) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'addresses', + length, + true, + length, + true, + ); + }); + } + + QueryBuilder + addressesIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'addresses', + 0, + true, + 0, + true, + ); + }); + } + + QueryBuilder + addressesIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'addresses', + 0, + false, + 999999, + true, + ); + }); + } + + QueryBuilder + addressesLengthLessThan( + int length, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'addresses', + 0, + true, + length, + include, + ); + }); + } + + QueryBuilder + addressesLengthGreaterThan( + int length, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'addresses', + length, + include, + 999999, + true, + ); + }); + } + + QueryBuilder + addressesLengthBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.listLength( + r'addresses', + lower, + includeLower, + upper, + includeUpper, + ); + }); + } + + QueryBuilder + customIdEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'customId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + customIdGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'customId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + customIdLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'customId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + customIdBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'customId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + customIdStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'customId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + customIdEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'customId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + customIdContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'customId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + customIdMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'customId', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + customIdIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'customId', + value: '', + )); + }); + } + + QueryBuilder + customIdIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'customId', + value: '', + )); + }); + } + + QueryBuilder + emojiCharIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'emojiChar', + )); + }); + } + + QueryBuilder + emojiCharIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'emojiChar', + )); + }); + } + + QueryBuilder + emojiCharEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'emojiChar', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + emojiCharGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'emojiChar', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + emojiCharLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'emojiChar', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + emojiCharBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'emojiChar', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + emojiCharStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'emojiChar', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + emojiCharEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'emojiChar', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + emojiCharContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'emojiChar', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + emojiCharMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'emojiChar', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + emojiCharIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'emojiChar', + value: '', + )); + }); + } + + QueryBuilder + emojiCharIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'emojiChar', + value: '', + )); + }); + } + + QueryBuilder idEqualTo( + Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idGreaterThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idLessThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + isFavoriteEqualTo(bool value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'isFavorite', + value: value, + )); + }); + } + + QueryBuilder nameEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + nameGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'name', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + nameStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'name', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + nameIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'name', + value: '', + )); + }); + } + + QueryBuilder + nameIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'name', + value: '', + )); + }); + } +} + +extension ContactEntryQueryObject + on QueryBuilder { + QueryBuilder + addressesElement(FilterQuery q) { + return QueryBuilder.apply(this, (query) { + return query.object(q, r'addresses'); + }); + } +} + +extension ContactEntryQueryLinks + on QueryBuilder {} + +extension ContactEntryQuerySortBy + on QueryBuilder { + QueryBuilder sortByCustomId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'customId', Sort.asc); + }); + } + + QueryBuilder sortByCustomIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'customId', Sort.desc); + }); + } + + QueryBuilder sortByEmojiChar() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'emojiChar', Sort.asc); + }); + } + + QueryBuilder sortByEmojiCharDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'emojiChar', Sort.desc); + }); + } + + QueryBuilder sortByIsFavorite() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isFavorite', Sort.asc); + }); + } + + QueryBuilder + sortByIsFavoriteDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isFavorite', Sort.desc); + }); + } + + QueryBuilder sortByName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.asc); + }); + } + + QueryBuilder sortByNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.desc); + }); + } +} + +extension ContactEntryQuerySortThenBy + on QueryBuilder { + QueryBuilder thenByCustomId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'customId', Sort.asc); + }); + } + + QueryBuilder thenByCustomIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'customId', Sort.desc); + }); + } + + QueryBuilder thenByEmojiChar() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'emojiChar', Sort.asc); + }); + } + + QueryBuilder thenByEmojiCharDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'emojiChar', Sort.desc); + }); + } + + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder thenByIsFavorite() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isFavorite', Sort.asc); + }); + } + + QueryBuilder + thenByIsFavoriteDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'isFavorite', Sort.desc); + }); + } + + QueryBuilder thenByName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.asc); + }); + } + + QueryBuilder thenByNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.desc); + }); + } +} + +extension ContactEntryQueryWhereDistinct + on QueryBuilder { + QueryBuilder distinctByCustomId( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'customId', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByEmojiChar( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'emojiChar', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByIsFavorite() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'isFavorite'); + }); + } + + QueryBuilder distinctByName( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'name', caseSensitive: caseSensitive); + }); + } +} + +extension ContactEntryQueryProperty + on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder, QQueryOperations> + addressesProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'addresses'); + }); + } + + QueryBuilder customIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'customId'); + }); + } + + QueryBuilder emojiCharProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'emojiChar'); + }); + } + + QueryBuilder isFavoriteProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'isFavorite'); + }); + } + + QueryBuilder nameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'name'); + }); + } +} + +// ************************************************************************** +// IsarEmbeddedGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters + +const ContactAddressEntrySchema = Schema( + name: r'ContactAddressEntry', + id: 2556413586404997281, + properties: { + r'address': PropertySchema( + id: 0, + name: r'address', + type: IsarType.string, + ), + r'coinName': PropertySchema( + id: 1, + name: r'coinName', + type: IsarType.string, + ), + r'label': PropertySchema( + id: 2, + name: r'label', + type: IsarType.string, + ), + r'other': PropertySchema( + id: 3, + name: r'other', + type: IsarType.string, + ) + }, + estimateSize: _contactAddressEntryEstimateSize, + serialize: _contactAddressEntrySerialize, + deserialize: _contactAddressEntryDeserialize, + deserializeProp: _contactAddressEntryDeserializeProp, +); + +int _contactAddressEntryEstimateSize( + ContactAddressEntry object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + bytesCount += 3 + object.address.length * 3; + bytesCount += 3 + object.coinName.length * 3; + bytesCount += 3 + object.label.length * 3; + { + final value = object.other; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + return bytesCount; +} + +void _contactAddressEntrySerialize( + ContactAddressEntry object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeString(offsets[0], object.address); + writer.writeString(offsets[1], object.coinName); + writer.writeString(offsets[2], object.label); + writer.writeString(offsets[3], object.other); +} + +ContactAddressEntry _contactAddressEntryDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = ContactAddressEntry(); + object.address = reader.readString(offsets[0]); + object.coinName = reader.readString(offsets[1]); + object.label = reader.readString(offsets[2]); + object.other = reader.readStringOrNull(offsets[3]); + return object; +} + +P _contactAddressEntryDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readString(offset)) as P; + case 1: + return (reader.readString(offset)) as P; + case 2: + return (reader.readString(offset)) as P; + case 3: + return (reader.readStringOrNull(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +extension ContactAddressEntryQueryFilter on QueryBuilder { + QueryBuilder + addressEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'address', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + addressGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'address', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + addressLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'address', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + addressBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'address', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + addressStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'address', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + addressEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'address', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + addressContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'address', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + addressMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'address', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + addressIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'address', + value: '', + )); + }); + } + + QueryBuilder + addressIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'address', + value: '', + )); + }); + } + + QueryBuilder + coinNameEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'coinName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinNameGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'coinName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinNameLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'coinName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinNameBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'coinName', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinNameStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'coinName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinNameEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'coinName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinNameContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'coinName', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinNameMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'coinName', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinNameIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'coinName', + value: '', + )); + }); + } + + QueryBuilder + coinNameIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'coinName', + value: '', + )); + }); + } + + QueryBuilder + labelEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'label', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + labelGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'label', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + labelLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'label', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + labelBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'label', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + labelStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'label', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + labelEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'label', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + labelContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'label', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + labelMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'label', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + labelIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'label', + value: '', + )); + }); + } + + QueryBuilder + labelIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'label', + value: '', + )); + }); + } + + QueryBuilder + otherIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'other', + )); + }); + } + + QueryBuilder + otherIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'other', + )); + }); + } + + QueryBuilder + otherEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'other', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + otherGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'other', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + otherLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'other', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + otherBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'other', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + otherStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'other', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + otherEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'other', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + otherContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'other', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + otherMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'other', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + otherIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'other', + value: '', + )); + }); + } + + QueryBuilder + otherIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'other', + value: '', + )); + }); + } +} + +extension ContactAddressEntryQueryObject on QueryBuilder {} diff --git a/lib/models/isar/models/contract.dart b/lib/models/isar/models/contract.dart new file mode 100644 index 000000000..88e2a454c --- /dev/null +++ b/lib/models/isar/models/contract.dart @@ -0,0 +1,3 @@ +abstract class Contract { + // for possible future use +} diff --git a/lib/models/isar/models/encrypted_string_value.g.dart b/lib/models/isar/models/encrypted_string_value.g.dart index 2315c5d85..fdfe5527a 100644 --- a/lib/models/isar/models/encrypted_string_value.g.dart +++ b/lib/models/isar/models/encrypted_string_value.g.dart @@ -7,7 +7,7 @@ part of 'encrypted_string_value.dart'; // ************************************************************************** // coverage:ignore-file -// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, join_return_with_assignment, avoid_js_rounded_ints, prefer_final_locals +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters extension GetEncryptedStringValueCollection on Isar { IsarCollection get encryptedStringValues => @@ -30,12 +30,9 @@ const EncryptedStringValueSchema = CollectionSchema( ) }, estimateSize: _encryptedStringValueEstimateSize, - serializeNative: _encryptedStringValueSerializeNative, - deserializeNative: _encryptedStringValueDeserializeNative, - deserializePropNative: _encryptedStringValueDeserializePropNative, - serializeWeb: _encryptedStringValueSerializeWeb, - deserializeWeb: _encryptedStringValueDeserializeWeb, - deserializePropWeb: _encryptedStringValueDeserializePropWeb, + serialize: _encryptedStringValueSerialize, + deserialize: _encryptedStringValueDeserialize, + deserializeProp: _encryptedStringValueDeserializeProp, idName: r'id', indexes: { r'key': IndexSchema( @@ -57,7 +54,7 @@ const EncryptedStringValueSchema = CollectionSchema( getId: _encryptedStringValueGetId, getLinks: _encryptedStringValueGetLinks, attach: _encryptedStringValueAttach, - version: 5, + version: '3.0.5', ); int _encryptedStringValueEstimateSize( @@ -71,20 +68,19 @@ int _encryptedStringValueEstimateSize( return bytesCount; } -int _encryptedStringValueSerializeNative( +void _encryptedStringValueSerialize( EncryptedStringValue object, - IsarBinaryWriter writer, + IsarWriter writer, List offsets, Map> allOffsets, ) { writer.writeString(offsets[0], object.key); writer.writeString(offsets[1], object.value); - return writer.usedBytes; } -EncryptedStringValue _encryptedStringValueDeserializeNative( - int id, - IsarBinaryReader reader, +EncryptedStringValue _encryptedStringValueDeserialize( + Id id, + IsarReader reader, List offsets, Map> allOffsets, ) { @@ -95,9 +91,8 @@ EncryptedStringValue _encryptedStringValueDeserializeNative( return object; } -P _encryptedStringValueDeserializePropNative

( - Id id, - IsarBinaryReader reader, +P _encryptedStringValueDeserializeProp

( + IsarReader reader, int propertyId, int offset, Map> allOffsets, @@ -112,33 +107,8 @@ P _encryptedStringValueDeserializePropNative

( } } -Object _encryptedStringValueSerializeWeb( - IsarCollection collection, - EncryptedStringValue object) { - /*final jsObj = IsarNative.newJsObject();*/ throw UnimplementedError(); -} - -EncryptedStringValue _encryptedStringValueDeserializeWeb( - IsarCollection collection, Object jsObj) { - /*final object = EncryptedStringValue();object.id = IsarNative.jsObjectGet(jsObj, r'id') ?? (double.negativeInfinity as int);object.key = IsarNative.jsObjectGet(jsObj, r'key') ?? '';object.value = IsarNative.jsObjectGet(jsObj, r'value') ?? '';*/ - //return object; - throw UnimplementedError(); -} - -P _encryptedStringValueDeserializePropWeb

( - Object jsObj, String propertyName) { - switch (propertyName) { - default: - throw IsarError('Illegal propertyName'); - } -} - -int? _encryptedStringValueGetId(EncryptedStringValue object) { - if (object.id == Isar.autoIncrement) { - return null; - } else { - return object.id; - } +Id _encryptedStringValueGetId(EncryptedStringValue object) { + return object.id; } List> _encryptedStringValueGetLinks( @@ -188,19 +158,19 @@ extension EncryptedStringValueByIndex on IsarCollection { return deleteAllByIndexSync(r'key', values); } - Future putByKey(EncryptedStringValue object) { + Future putByKey(EncryptedStringValue object) { return putByIndex(r'key', object); } - int putByKeySync(EncryptedStringValue object, {bool saveLinks = true}) { + Id putByKeySync(EncryptedStringValue object, {bool saveLinks = true}) { return putByIndexSync(r'key', object, saveLinks: saveLinks); } - Future> putAllByKey(List objects) { + Future> putAllByKey(List objects) { return putAllByIndex(r'key', objects); } - List putAllByKeySync(List objects, + List putAllByKeySync(List objects, {bool saveLinks = true}) { return putAllByIndexSync(r'key', objects, saveLinks: saveLinks); } @@ -219,7 +189,7 @@ extension EncryptedStringValueQueryWhereSort extension EncryptedStringValueQueryWhere on QueryBuilder { QueryBuilder - idEqualTo(int id) { + idEqualTo(Id id) { return QueryBuilder.apply(this, (query) { return query.addWhereClause(IdWhereClause.between( lower: id, @@ -229,7 +199,7 @@ extension EncryptedStringValueQueryWhere } QueryBuilder - idNotEqualTo(int id) { + idNotEqualTo(Id id) { return QueryBuilder.apply(this, (query) { if (query.whereSort == Sort.asc) { return query @@ -252,7 +222,7 @@ extension EncryptedStringValueQueryWhere } QueryBuilder - idGreaterThan(int id, {bool include = false}) { + idGreaterThan(Id id, {bool include = false}) { return QueryBuilder.apply(this, (query) { return query.addWhereClause( IdWhereClause.greaterThan(lower: id, includeLower: include), @@ -261,7 +231,7 @@ extension EncryptedStringValueQueryWhere } QueryBuilder - idLessThan(int id, {bool include = false}) { + idLessThan(Id id, {bool include = false}) { return QueryBuilder.apply(this, (query) { return query.addWhereClause( IdWhereClause.lessThan(upper: id, includeUpper: include), @@ -271,8 +241,8 @@ extension EncryptedStringValueQueryWhere QueryBuilder idBetween( - int lowerId, - int upperId, { + Id lowerId, + Id upperId, { bool includeLower = true, bool includeUpper = true, }) { @@ -335,7 +305,7 @@ extension EncryptedStringValueQueryWhere extension EncryptedStringValueQueryFilter on QueryBuilder { QueryBuilder idEqualTo(int value) { + QAfterFilterCondition> idEqualTo(Id value) { return QueryBuilder.apply(this, (query) { return query.addFilterCondition(FilterCondition.equalTo( property: r'id', @@ -346,7 +316,7 @@ extension EncryptedStringValueQueryFilter on QueryBuilder idGreaterThan( - int value, { + Id value, { bool include = false, }) { return QueryBuilder.apply(this, (query) { @@ -360,7 +330,7 @@ extension EncryptedStringValueQueryFilter on QueryBuilder idLessThan( - int value, { + Id value, { bool include = false, }) { return QueryBuilder.apply(this, (query) { @@ -374,8 +344,8 @@ extension EncryptedStringValueQueryFilter on QueryBuilder idBetween( - int lower, - int upper, { + Id lower, + Id upper, { bool includeLower = true, bool includeUpper = true, }) { @@ -508,6 +478,26 @@ extension EncryptedStringValueQueryFilter on QueryBuilder keyIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'key', + value: '', + )); + }); + } + + QueryBuilder keyIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'key', + value: '', + )); + }); + } + QueryBuilder valueEqualTo( String value, { @@ -625,6 +615,26 @@ extension EncryptedStringValueQueryFilter on QueryBuilder valueIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'value', + value: '', + )); + }); + } + + QueryBuilder valueIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'value', + value: '', + )); + }); + } } extension EncryptedStringValueQueryObject on QueryBuilder? walletIds, + String? abi, + String? otherData, + }) => + EthContract( + address: address ?? this.address, + name: name ?? this.name, + symbol: symbol ?? this.symbol, + decimals: decimals ?? this.decimals, + type: type ?? this.type, + abi: abi ?? this.abi, + )..id = id ?? this.id; +} + +// Used in Isar db and stored there as int indexes so adding/removing values +// in this definition should be done extremely carefully in production +enum EthContractType { + unknown, + erc20, + erc721; +} diff --git a/lib/models/isar/models/ethereum/eth_contract.g.dart b/lib/models/isar/models/ethereum/eth_contract.g.dart new file mode 100644 index 000000000..bc9548e8d --- /dev/null +++ b/lib/models/isar/models/ethereum/eth_contract.g.dart @@ -0,0 +1,1322 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'eth_contract.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters + +extension GetEthContractCollection on Isar { + IsarCollection get ethContracts => this.collection(); +} + +const EthContractSchema = CollectionSchema( + name: r'EthContract', + id: 3784021410994159165, + properties: { + r'abi': PropertySchema( + id: 0, + name: r'abi', + type: IsarType.string, + ), + r'address': PropertySchema( + id: 1, + name: r'address', + type: IsarType.string, + ), + r'decimals': PropertySchema( + id: 2, + name: r'decimals', + type: IsarType.long, + ), + r'name': PropertySchema( + id: 3, + name: r'name', + type: IsarType.string, + ), + r'symbol': PropertySchema( + id: 4, + name: r'symbol', + type: IsarType.string, + ), + r'type': PropertySchema( + id: 5, + name: r'type', + type: IsarType.byte, + enumMap: _EthContracttypeEnumValueMap, + ) + }, + estimateSize: _ethContractEstimateSize, + serialize: _ethContractSerialize, + deserialize: _ethContractDeserialize, + deserializeProp: _ethContractDeserializeProp, + idName: r'id', + indexes: { + r'address': IndexSchema( + id: -259407546592846288, + name: r'address', + unique: true, + replace: true, + properties: [ + IndexPropertySchema( + name: r'address', + type: IndexType.hash, + caseSensitive: true, + ) + ], + ) + }, + links: {}, + embeddedSchemas: {}, + getId: _ethContractGetId, + getLinks: _ethContractGetLinks, + attach: _ethContractAttach, + version: '3.0.5', +); + +int _ethContractEstimateSize( + EthContract object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + { + final value = object.abi; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + bytesCount += 3 + object.address.length * 3; + bytesCount += 3 + object.name.length * 3; + bytesCount += 3 + object.symbol.length * 3; + return bytesCount; +} + +void _ethContractSerialize( + EthContract object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeString(offsets[0], object.abi); + writer.writeString(offsets[1], object.address); + writer.writeLong(offsets[2], object.decimals); + writer.writeString(offsets[3], object.name); + writer.writeString(offsets[4], object.symbol); + writer.writeByte(offsets[5], object.type.index); +} + +EthContract _ethContractDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = EthContract( + abi: reader.readStringOrNull(offsets[0]), + address: reader.readString(offsets[1]), + decimals: reader.readLong(offsets[2]), + name: reader.readString(offsets[3]), + symbol: reader.readString(offsets[4]), + type: _EthContracttypeValueEnumMap[reader.readByteOrNull(offsets[5])] ?? + EthContractType.unknown, + ); + object.id = id; + return object; +} + +P _ethContractDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readStringOrNull(offset)) as P; + case 1: + return (reader.readString(offset)) as P; + case 2: + return (reader.readLong(offset)) as P; + case 3: + return (reader.readString(offset)) as P; + case 4: + return (reader.readString(offset)) as P; + case 5: + return (_EthContracttypeValueEnumMap[reader.readByteOrNull(offset)] ?? + EthContractType.unknown) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +const _EthContracttypeEnumValueMap = { + 'unknown': 0, + 'erc20': 1, + 'erc721': 2, +}; +const _EthContracttypeValueEnumMap = { + 0: EthContractType.unknown, + 1: EthContractType.erc20, + 2: EthContractType.erc721, +}; + +Id _ethContractGetId(EthContract object) { + return object.id; +} + +List> _ethContractGetLinks(EthContract object) { + return []; +} + +void _ethContractAttach( + IsarCollection col, Id id, EthContract object) { + object.id = id; +} + +extension EthContractByIndex on IsarCollection { + Future getByAddress(String address) { + return getByIndex(r'address', [address]); + } + + EthContract? getByAddressSync(String address) { + return getByIndexSync(r'address', [address]); + } + + Future deleteByAddress(String address) { + return deleteByIndex(r'address', [address]); + } + + bool deleteByAddressSync(String address) { + return deleteByIndexSync(r'address', [address]); + } + + Future> getAllByAddress(List addressValues) { + final values = addressValues.map((e) => [e]).toList(); + return getAllByIndex(r'address', values); + } + + List getAllByAddressSync(List addressValues) { + final values = addressValues.map((e) => [e]).toList(); + return getAllByIndexSync(r'address', values); + } + + Future deleteAllByAddress(List addressValues) { + final values = addressValues.map((e) => [e]).toList(); + return deleteAllByIndex(r'address', values); + } + + int deleteAllByAddressSync(List addressValues) { + final values = addressValues.map((e) => [e]).toList(); + return deleteAllByIndexSync(r'address', values); + } + + Future putByAddress(EthContract object) { + return putByIndex(r'address', object); + } + + Id putByAddressSync(EthContract object, {bool saveLinks = true}) { + return putByIndexSync(r'address', object, saveLinks: saveLinks); + } + + Future> putAllByAddress(List objects) { + return putAllByIndex(r'address', objects); + } + + List putAllByAddressSync(List objects, + {bool saveLinks = true}) { + return putAllByIndexSync(r'address', objects, saveLinks: saveLinks); + } +} + +extension EthContractQueryWhereSort + on QueryBuilder { + QueryBuilder anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension EthContractQueryWhere + on QueryBuilder { + QueryBuilder idEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: id, + upper: id, + )); + }); + } + + QueryBuilder idNotEqualTo( + Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder idGreaterThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder idLessThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder addressEqualTo( + String address) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'address', + value: [address], + )); + }); + } + + QueryBuilder addressNotEqualTo( + String address) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'address', + lower: [], + upper: [address], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'address', + lower: [address], + includeLower: false, + upper: [], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'address', + lower: [address], + includeLower: false, + upper: [], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'address', + lower: [], + upper: [address], + includeUpper: false, + )); + } + }); + } +} + +extension EthContractQueryFilter + on QueryBuilder { + QueryBuilder abiIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'abi', + )); + }); + } + + QueryBuilder abiIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'abi', + )); + }); + } + + QueryBuilder abiEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'abi', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder abiGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'abi', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder abiLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'abi', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder abiBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'abi', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder abiStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'abi', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder abiEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'abi', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder abiContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'abi', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder abiMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'abi', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder abiIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'abi', + value: '', + )); + }); + } + + QueryBuilder + abiIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'abi', + value: '', + )); + }); + } + + QueryBuilder addressEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'address', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + addressGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'address', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder addressLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'address', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder addressBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'address', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + addressStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'address', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder addressEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'address', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder addressContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'address', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder addressMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'address', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + addressIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'address', + value: '', + )); + }); + } + + QueryBuilder + addressIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'address', + value: '', + )); + }); + } + + QueryBuilder decimalsEqualTo( + int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'decimals', + value: value, + )); + }); + } + + QueryBuilder + decimalsGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'decimals', + value: value, + )); + }); + } + + QueryBuilder + decimalsLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'decimals', + value: value, + )); + }); + } + + QueryBuilder decimalsBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'decimals', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder idEqualTo( + Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idGreaterThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idLessThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder nameEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'name', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'name', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'name', + value: '', + )); + }); + } + + QueryBuilder + nameIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'name', + value: '', + )); + }); + } + + QueryBuilder symbolEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'symbol', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + symbolGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'symbol', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder symbolLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'symbol', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder symbolBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'symbol', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + symbolStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'symbol', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder symbolEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'symbol', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder symbolContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'symbol', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder symbolMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'symbol', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + symbolIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'symbol', + value: '', + )); + }); + } + + QueryBuilder + symbolIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'symbol', + value: '', + )); + }); + } + + QueryBuilder typeEqualTo( + EthContractType value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'type', + value: value, + )); + }); + } + + QueryBuilder typeGreaterThan( + EthContractType value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'type', + value: value, + )); + }); + } + + QueryBuilder typeLessThan( + EthContractType value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'type', + value: value, + )); + }); + } + + QueryBuilder typeBetween( + EthContractType lower, + EthContractType upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'type', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } +} + +extension EthContractQueryObject + on QueryBuilder {} + +extension EthContractQueryLinks + on QueryBuilder {} + +extension EthContractQuerySortBy + on QueryBuilder { + QueryBuilder sortByAbi() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'abi', Sort.asc); + }); + } + + QueryBuilder sortByAbiDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'abi', Sort.desc); + }); + } + + QueryBuilder sortByAddress() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'address', Sort.asc); + }); + } + + QueryBuilder sortByAddressDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'address', Sort.desc); + }); + } + + QueryBuilder sortByDecimals() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'decimals', Sort.asc); + }); + } + + QueryBuilder sortByDecimalsDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'decimals', Sort.desc); + }); + } + + QueryBuilder sortByName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.asc); + }); + } + + QueryBuilder sortByNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.desc); + }); + } + + QueryBuilder sortBySymbol() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'symbol', Sort.asc); + }); + } + + QueryBuilder sortBySymbolDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'symbol', Sort.desc); + }); + } + + QueryBuilder sortByType() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'type', Sort.asc); + }); + } + + QueryBuilder sortByTypeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'type', Sort.desc); + }); + } +} + +extension EthContractQuerySortThenBy + on QueryBuilder { + QueryBuilder thenByAbi() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'abi', Sort.asc); + }); + } + + QueryBuilder thenByAbiDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'abi', Sort.desc); + }); + } + + QueryBuilder thenByAddress() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'address', Sort.asc); + }); + } + + QueryBuilder thenByAddressDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'address', Sort.desc); + }); + } + + QueryBuilder thenByDecimals() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'decimals', Sort.asc); + }); + } + + QueryBuilder thenByDecimalsDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'decimals', Sort.desc); + }); + } + + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder thenByName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.asc); + }); + } + + QueryBuilder thenByNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.desc); + }); + } + + QueryBuilder thenBySymbol() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'symbol', Sort.asc); + }); + } + + QueryBuilder thenBySymbolDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'symbol', Sort.desc); + }); + } + + QueryBuilder thenByType() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'type', Sort.asc); + }); + } + + QueryBuilder thenByTypeDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'type', Sort.desc); + }); + } +} + +extension EthContractQueryWhereDistinct + on QueryBuilder { + QueryBuilder distinctByAbi( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'abi', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByAddress( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'address', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByDecimals() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'decimals'); + }); + } + + QueryBuilder distinctByName( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'name', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctBySymbol( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'symbol', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByType() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'type'); + }); + } +} + +extension EthContractQueryProperty + on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder abiProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'abi'); + }); + } + + QueryBuilder addressProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'address'); + }); + } + + QueryBuilder decimalsProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'decimals'); + }); + } + + QueryBuilder nameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'name'); + }); + } + + QueryBuilder symbolProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'symbol'); + }); + } + + QueryBuilder typeProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'type'); + }); + } +} diff --git a/lib/models/isar/models/isar_models.dart b/lib/models/isar/models/isar_models.dart new file mode 100644 index 000000000..6b244ee48 --- /dev/null +++ b/lib/models/isar/models/isar_models.dart @@ -0,0 +1,9 @@ +export 'address_label.dart'; +export 'blockchain_data/address.dart'; +export 'blockchain_data/input.dart'; +export 'blockchain_data/output.dart'; +export 'blockchain_data/transaction.dart'; +export 'blockchain_data/utxo.dart'; +export 'ethereum/eth_contract.dart'; +export 'log.dart'; +export 'transaction_note.dart'; diff --git a/lib/models/isar/models/log.dart b/lib/models/isar/models/log.dart index 69945f1c6..f89fba253 100644 --- a/lib/models/isar/models/log.dart +++ b/lib/models/isar/models/log.dart @@ -13,6 +13,7 @@ class Log { @Index() late int timestampInMillisUTC; + @Enumerated(EnumType.name) late LogLevel logLevel; @override diff --git a/lib/models/isar/models/log.g.dart b/lib/models/isar/models/log.g.dart index d1f28c75c..332e91d3c 100644 --- a/lib/models/isar/models/log.g.dart +++ b/lib/models/isar/models/log.g.dart @@ -7,7 +7,7 @@ part of 'log.dart'; // ************************************************************************** // coverage:ignore-file -// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, join_return_with_assignment, avoid_js_rounded_ints, prefer_final_locals +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters extension GetLogCollection on Isar { IsarCollection get logs => this.collection(); @@ -21,6 +21,7 @@ const LogSchema = CollectionSchema( id: 0, name: r'logLevel', type: IsarType.string, + enumMap: _LoglogLevelEnumValueMap, ), r'message': PropertySchema( id: 1, @@ -34,12 +35,9 @@ const LogSchema = CollectionSchema( ) }, estimateSize: _logEstimateSize, - serializeNative: _logSerializeNative, - deserializeNative: _logDeserializeNative, - deserializePropNative: _logDeserializePropNative, - serializeWeb: _logSerializeWeb, - deserializeWeb: _logDeserializeWeb, - deserializePropWeb: _logDeserializePropWeb, + serialize: _logSerialize, + deserialize: _logDeserialize, + deserializeProp: _logDeserializeProp, idName: r'id', indexes: { r'timestampInMillisUTC': IndexSchema( @@ -61,7 +59,7 @@ const LogSchema = CollectionSchema( getId: _logGetId, getLinks: _logGetLinks, attach: _logAttach, - version: 5, + version: '3.0.5', ); int _logEstimateSize( @@ -70,49 +68,48 @@ int _logEstimateSize( Map> allOffsets, ) { var bytesCount = offsets.last; - bytesCount += 3 + object.logLevel.value.length * 3; + bytesCount += 3 + object.logLevel.name.length * 3; bytesCount += 3 + object.message.length * 3; return bytesCount; } -int _logSerializeNative( +void _logSerialize( Log object, - IsarBinaryWriter writer, + IsarWriter writer, List offsets, Map> allOffsets, ) { - writer.writeString(offsets[0], object.logLevel.value); + writer.writeString(offsets[0], object.logLevel.name); writer.writeString(offsets[1], object.message); writer.writeLong(offsets[2], object.timestampInMillisUTC); - return writer.usedBytes; } -Log _logDeserializeNative( - int id, - IsarBinaryReader reader, +Log _logDeserialize( + Id id, + IsarReader reader, List offsets, Map> allOffsets, ) { final object = Log(); object.id = id; object.logLevel = - _LogLogLevelMap[reader.readStringOrNull(offsets[0])] ?? LogLevel.Info; + _LoglogLevelValueEnumMap[reader.readStringOrNull(offsets[0])] ?? + LogLevel.Info; object.message = reader.readString(offsets[1]); object.timestampInMillisUTC = reader.readLong(offsets[2]); return object; } -P _logDeserializePropNative

( - Id id, - IsarBinaryReader reader, +P _logDeserializeProp

( + IsarReader reader, int propertyId, int offset, Map> allOffsets, ) { switch (propertyId) { case 0: - return (_LogLogLevelMap[reader.readStringOrNull(offset)] ?? LogLevel.Info) - as P; + return (_LoglogLevelValueEnumMap[reader.readStringOrNull(offset)] ?? + LogLevel.Info) as P; case 1: return (reader.readString(offset)) as P; case 2: @@ -122,36 +119,21 @@ P _logDeserializePropNative

( } } -Object _logSerializeWeb(IsarCollection collection, Log object) { - /*final jsObj = IsarNative.newJsObject();*/ throw UnimplementedError(); -} - -Log _logDeserializeWeb(IsarCollection collection, Object jsObj) { - /*final object = Log();object.id = IsarNative.jsObjectGet(jsObj, r'id') ?? (double.negativeInfinity as int);object.logLevel = IsarNative.jsObjectGet(jsObj, r'logLevel') ?? LogLevel.Info;object.message = IsarNative.jsObjectGet(jsObj, r'message') ?? '';object.timestampInMillisUTC = IsarNative.jsObjectGet(jsObj, r'timestampInMillisUTC') ?? (double.negativeInfinity as int);*/ - //return object; - throw UnimplementedError(); -} - -P _logDeserializePropWeb

(Object jsObj, String propertyName) { - switch (propertyName) { - default: - throw IsarError('Illegal propertyName'); - } -} - -final _LogLogLevelMap = { - LogLevel.Info.value: LogLevel.Info, - LogLevel.Warning.value: LogLevel.Warning, - LogLevel.Error.value: LogLevel.Error, - LogLevel.Fatal.value: LogLevel.Fatal, +const _LoglogLevelEnumValueMap = { + r'Info': r'Info', + r'Warning': r'Warning', + r'Error': r'Error', + r'Fatal': r'Fatal', +}; +const _LoglogLevelValueEnumMap = { + r'Info': LogLevel.Info, + r'Warning': LogLevel.Warning, + r'Error': LogLevel.Error, + r'Fatal': LogLevel.Fatal, }; -int? _logGetId(Log object) { - if (object.id == Isar.autoIncrement) { - return null; - } else { - return object.id; - } +Id _logGetId(Log object) { + return object.id; } List> _logGetLinks(Log object) { @@ -179,7 +161,7 @@ extension LogQueryWhereSort on QueryBuilder { } extension LogQueryWhere on QueryBuilder { - QueryBuilder idEqualTo(int id) { + QueryBuilder idEqualTo(Id id) { return QueryBuilder.apply(this, (query) { return query.addWhereClause(IdWhereClause.between( lower: id, @@ -188,7 +170,7 @@ extension LogQueryWhere on QueryBuilder { }); } - QueryBuilder idNotEqualTo(int id) { + QueryBuilder idNotEqualTo(Id id) { return QueryBuilder.apply(this, (query) { if (query.whereSort == Sort.asc) { return query @@ -210,7 +192,7 @@ extension LogQueryWhere on QueryBuilder { }); } - QueryBuilder idGreaterThan(int id, + QueryBuilder idGreaterThan(Id id, {bool include = false}) { return QueryBuilder.apply(this, (query) { return query.addWhereClause( @@ -219,7 +201,7 @@ extension LogQueryWhere on QueryBuilder { }); } - QueryBuilder idLessThan(int id, + QueryBuilder idLessThan(Id id, {bool include = false}) { return QueryBuilder.apply(this, (query) { return query.addWhereClause( @@ -229,8 +211,8 @@ extension LogQueryWhere on QueryBuilder { } QueryBuilder idBetween( - int lowerId, - int upperId, { + Id lowerId, + Id upperId, { bool includeLower = true, bool includeUpper = true, }) { @@ -336,7 +318,7 @@ extension LogQueryWhere on QueryBuilder { } extension LogQueryFilter on QueryBuilder { - QueryBuilder idEqualTo(int value) { + QueryBuilder idEqualTo(Id value) { return QueryBuilder.apply(this, (query) { return query.addFilterCondition(FilterCondition.equalTo( property: r'id', @@ -346,7 +328,7 @@ extension LogQueryFilter on QueryBuilder { } QueryBuilder idGreaterThan( - int value, { + Id value, { bool include = false, }) { return QueryBuilder.apply(this, (query) { @@ -359,7 +341,7 @@ extension LogQueryFilter on QueryBuilder { } QueryBuilder idLessThan( - int value, { + Id value, { bool include = false, }) { return QueryBuilder.apply(this, (query) { @@ -372,8 +354,8 @@ extension LogQueryFilter on QueryBuilder { } QueryBuilder idBetween( - int lower, - int upper, { + Id lower, + Id upper, { bool includeLower = true, bool includeUpper = true, }) { @@ -498,6 +480,24 @@ extension LogQueryFilter on QueryBuilder { }); } + QueryBuilder logLevelIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'logLevel', + value: '', + )); + }); + } + + QueryBuilder logLevelIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'logLevel', + value: '', + )); + }); + } + QueryBuilder messageEqualTo( String value, { bool caseSensitive = true, @@ -608,6 +608,24 @@ extension LogQueryFilter on QueryBuilder { }); } + QueryBuilder messageIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'message', + value: '', + )); + }); + } + + QueryBuilder messageIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'message', + value: '', + )); + }); + } + QueryBuilder timestampInMillisUTCEqualTo( int value) { return QueryBuilder.apply(this, (query) { diff --git a/lib/models/isar/models/transaction_note.dart b/lib/models/isar/models/transaction_note.dart new file mode 100644 index 000000000..a1e9e8d3f --- /dev/null +++ b/lib/models/isar/models/transaction_note.dart @@ -0,0 +1,22 @@ +import 'package:isar/isar.dart'; + +part 'transaction_note.g.dart'; + +@Collection() +class TransactionNote { + TransactionNote({ + required this.walletId, + required this.txid, + required this.value, + }); + + Id id = Isar.autoIncrement; + + @Index() + late String walletId; + + @Index(unique: true, composite: [CompositeIndex("walletId")]) + late String txid; + + late String value; +} diff --git a/lib/models/isar/models/transaction_note.g.dart b/lib/models/isar/models/transaction_note.g.dart new file mode 100644 index 000000000..7bfe7d639 --- /dev/null +++ b/lib/models/isar/models/transaction_note.g.dart @@ -0,0 +1,1073 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'transaction_note.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters + +extension GetTransactionNoteCollection on Isar { + IsarCollection get transactionNotes => this.collection(); +} + +const TransactionNoteSchema = CollectionSchema( + name: r'TransactionNote', + id: 5128348263878806765, + properties: { + r'txid': PropertySchema( + id: 0, + name: r'txid', + type: IsarType.string, + ), + r'value': PropertySchema( + id: 1, + name: r'value', + type: IsarType.string, + ), + r'walletId': PropertySchema( + id: 2, + name: r'walletId', + type: IsarType.string, + ) + }, + estimateSize: _transactionNoteEstimateSize, + serialize: _transactionNoteSerialize, + deserialize: _transactionNoteDeserialize, + deserializeProp: _transactionNoteDeserializeProp, + idName: r'id', + indexes: { + r'walletId': IndexSchema( + id: -1783113319798776304, + name: r'walletId', + unique: false, + replace: false, + properties: [ + IndexPropertySchema( + name: r'walletId', + type: IndexType.hash, + caseSensitive: true, + ) + ], + ), + r'txid_walletId': IndexSchema( + id: -2771771174176035985, + name: r'txid_walletId', + unique: true, + replace: false, + properties: [ + IndexPropertySchema( + name: r'txid', + type: IndexType.hash, + caseSensitive: true, + ), + IndexPropertySchema( + name: r'walletId', + type: IndexType.hash, + caseSensitive: true, + ) + ], + ) + }, + links: {}, + embeddedSchemas: {}, + getId: _transactionNoteGetId, + getLinks: _transactionNoteGetLinks, + attach: _transactionNoteAttach, + version: '3.0.5', +); + +int _transactionNoteEstimateSize( + TransactionNote object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + bytesCount += 3 + object.txid.length * 3; + bytesCount += 3 + object.value.length * 3; + bytesCount += 3 + object.walletId.length * 3; + return bytesCount; +} + +void _transactionNoteSerialize( + TransactionNote object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeString(offsets[0], object.txid); + writer.writeString(offsets[1], object.value); + writer.writeString(offsets[2], object.walletId); +} + +TransactionNote _transactionNoteDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = TransactionNote( + txid: reader.readString(offsets[0]), + value: reader.readString(offsets[1]), + walletId: reader.readString(offsets[2]), + ); + object.id = id; + return object; +} + +P _transactionNoteDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readString(offset)) as P; + case 1: + return (reader.readString(offset)) as P; + case 2: + return (reader.readString(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +Id _transactionNoteGetId(TransactionNote object) { + return object.id; +} + +List> _transactionNoteGetLinks(TransactionNote object) { + return []; +} + +void _transactionNoteAttach( + IsarCollection col, Id id, TransactionNote object) { + object.id = id; +} + +extension TransactionNoteByIndex on IsarCollection { + Future getByTxidWalletId(String txid, String walletId) { + return getByIndex(r'txid_walletId', [txid, walletId]); + } + + TransactionNote? getByTxidWalletIdSync(String txid, String walletId) { + return getByIndexSync(r'txid_walletId', [txid, walletId]); + } + + Future deleteByTxidWalletId(String txid, String walletId) { + return deleteByIndex(r'txid_walletId', [txid, walletId]); + } + + bool deleteByTxidWalletIdSync(String txid, String walletId) { + return deleteByIndexSync(r'txid_walletId', [txid, walletId]); + } + + Future> getAllByTxidWalletId( + List txidValues, List walletIdValues) { + final len = txidValues.length; + assert(walletIdValues.length == len, + 'All index values must have the same length'); + final values = >[]; + for (var i = 0; i < len; i++) { + values.add([txidValues[i], walletIdValues[i]]); + } + + return getAllByIndex(r'txid_walletId', values); + } + + List getAllByTxidWalletIdSync( + List txidValues, List walletIdValues) { + final len = txidValues.length; + assert(walletIdValues.length == len, + 'All index values must have the same length'); + final values = >[]; + for (var i = 0; i < len; i++) { + values.add([txidValues[i], walletIdValues[i]]); + } + + return getAllByIndexSync(r'txid_walletId', values); + } + + Future deleteAllByTxidWalletId( + List txidValues, List walletIdValues) { + final len = txidValues.length; + assert(walletIdValues.length == len, + 'All index values must have the same length'); + final values = >[]; + for (var i = 0; i < len; i++) { + values.add([txidValues[i], walletIdValues[i]]); + } + + return deleteAllByIndex(r'txid_walletId', values); + } + + int deleteAllByTxidWalletIdSync( + List txidValues, List walletIdValues) { + final len = txidValues.length; + assert(walletIdValues.length == len, + 'All index values must have the same length'); + final values = >[]; + for (var i = 0; i < len; i++) { + values.add([txidValues[i], walletIdValues[i]]); + } + + return deleteAllByIndexSync(r'txid_walletId', values); + } + + Future putByTxidWalletId(TransactionNote object) { + return putByIndex(r'txid_walletId', object); + } + + Id putByTxidWalletIdSync(TransactionNote object, {bool saveLinks = true}) { + return putByIndexSync(r'txid_walletId', object, saveLinks: saveLinks); + } + + Future> putAllByTxidWalletId(List objects) { + return putAllByIndex(r'txid_walletId', objects); + } + + List putAllByTxidWalletIdSync(List objects, + {bool saveLinks = true}) { + return putAllByIndexSync(r'txid_walletId', objects, saveLinks: saveLinks); + } +} + +extension TransactionNoteQueryWhereSort + on QueryBuilder { + QueryBuilder anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension TransactionNoteQueryWhere + on QueryBuilder { + QueryBuilder idEqualTo( + Id id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: id, + upper: id, + )); + }); + } + + QueryBuilder + idNotEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder + idGreaterThan(Id id, {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder idLessThan( + Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + walletIdEqualTo(String walletId) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'walletId', + value: [walletId], + )); + }); + } + + QueryBuilder + walletIdNotEqualTo(String walletId) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'walletId', + lower: [], + upper: [walletId], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'walletId', + lower: [walletId], + includeLower: false, + upper: [], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'walletId', + lower: [walletId], + includeLower: false, + upper: [], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'walletId', + lower: [], + upper: [walletId], + includeUpper: false, + )); + } + }); + } + + QueryBuilder + txidEqualToAnyWalletId(String txid) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'txid_walletId', + value: [txid], + )); + }); + } + + QueryBuilder + txidNotEqualToAnyWalletId(String txid) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId', + lower: [], + upper: [txid], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId', + lower: [txid], + includeLower: false, + upper: [], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId', + lower: [txid], + includeLower: false, + upper: [], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId', + lower: [], + upper: [txid], + includeUpper: false, + )); + } + }); + } + + QueryBuilder + txidWalletIdEqualTo(String txid, String walletId) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'txid_walletId', + value: [txid, walletId], + )); + }); + } + + QueryBuilder + txidEqualToWalletIdNotEqualTo(String txid, String walletId) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId', + lower: [txid], + upper: [txid, walletId], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId', + lower: [txid, walletId], + includeLower: false, + upper: [txid], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId', + lower: [txid, walletId], + includeLower: false, + upper: [txid], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'txid_walletId', + lower: [txid], + upper: [txid, walletId], + includeUpper: false, + )); + } + }); + } +} + +extension TransactionNoteQueryFilter + on QueryBuilder { + QueryBuilder + idEqualTo(Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'id', + value: value, + )); + }); + } + + QueryBuilder + idGreaterThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder + idLessThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder + idBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + txidEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'txid', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txidGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'txid', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txidLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'txid', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txidBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'txid', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txidStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'txid', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txidEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'txid', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txidContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'txid', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txidMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'txid', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txidIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'txid', + value: '', + )); + }); + } + + QueryBuilder + txidIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'txid', + value: '', + )); + }); + } + + QueryBuilder + valueEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + valueGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + valueLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + valueBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'value', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + valueStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + valueEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + valueContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'value', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + valueMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'value', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + valueIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'value', + value: '', + )); + }); + } + + QueryBuilder + valueIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'value', + value: '', + )); + }); + } + + QueryBuilder + walletIdEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + walletIdGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + walletIdLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + walletIdBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'walletId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + walletIdStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + walletIdEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + walletIdContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'walletId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + walletIdMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'walletId', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + walletIdIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'walletId', + value: '', + )); + }); + } + + QueryBuilder + walletIdIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'walletId', + value: '', + )); + }); + } +} + +extension TransactionNoteQueryObject + on QueryBuilder {} + +extension TransactionNoteQueryLinks + on QueryBuilder {} + +extension TransactionNoteQuerySortBy + on QueryBuilder { + QueryBuilder sortByTxid() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'txid', Sort.asc); + }); + } + + QueryBuilder + sortByTxidDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'txid', Sort.desc); + }); + } + + QueryBuilder sortByValue() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'value', Sort.asc); + }); + } + + QueryBuilder + sortByValueDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'value', Sort.desc); + }); + } + + QueryBuilder + sortByWalletId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'walletId', Sort.asc); + }); + } + + QueryBuilder + sortByWalletIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'walletId', Sort.desc); + }); + } +} + +extension TransactionNoteQuerySortThenBy + on QueryBuilder { + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder thenByTxid() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'txid', Sort.asc); + }); + } + + QueryBuilder + thenByTxidDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'txid', Sort.desc); + }); + } + + QueryBuilder thenByValue() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'value', Sort.asc); + }); + } + + QueryBuilder + thenByValueDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'value', Sort.desc); + }); + } + + QueryBuilder + thenByWalletId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'walletId', Sort.asc); + }); + } + + QueryBuilder + thenByWalletIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'walletId', Sort.desc); + }); + } +} + +extension TransactionNoteQueryWhereDistinct + on QueryBuilder { + QueryBuilder distinctByTxid( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'txid', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByValue( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'value', caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByWalletId( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'walletId', caseSensitive: caseSensitive); + }); + } +} + +extension TransactionNoteQueryProperty + on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder txidProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'txid'); + }); + } + + QueryBuilder valueProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'value'); + }); + } + + QueryBuilder walletIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'walletId'); + }); + } +} diff --git a/lib/models/isar/stack_theme.dart b/lib/models/isar/stack_theme.dart new file mode 100644 index 000000000..5809e4443 --- /dev/null +++ b/lib/models/isar/stack_theme.dart @@ -0,0 +1,2239 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/themes/color_theme.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/extensions/impl/box_shadow.dart'; +import 'package:stackwallet/utilities/extensions/impl/gradient.dart'; +import 'package:stackwallet/utilities/extensions/impl/string.dart'; + +part 'stack_theme.g.dart'; + +@Collection(inheritance: false) +class StackTheme { + Id id = Isar.autoIncrement; + + /// id of theme on themes server + @Index(unique: true, replace: true) + late final String themeId; + + /// the theme name that will be displayed in app + late final String name; + + // system brightness + late final String brightnessString; + + /// convenience enum conversion for stored [brightnessString] + @ignore + Brightness get brightness { + switch (brightnessString) { + case "light": + return Brightness.light; + case "dark": + return Brightness.dark; + default: + // just return light instead of a possible crash causing error + return Brightness.light; + } + } + +// ==== background ===================================================== + + @ignore + Color get background => _background ??= Color(backgroundInt); + @ignore + Color? _background; + late final int backgroundInt; + + // ==== backgroundAppBar ===================================================== + + @ignore + Color get backgroundAppBar => + _backgroundAppBar ??= Color(backgroundAppBarInt); + @ignore + Color? _backgroundAppBar; + late final int backgroundAppBarInt; + + // ==== gradientBackground ===================================================== + + @ignore + Gradient? get gradientBackground { + if (gradientBackgroundString == null) { + _gradientBackground = null; + } else { + _gradientBackground ??= GradientExt.fromJson( + Map.from( + jsonDecode(gradientBackgroundString!) as Map, + ), + ); + } + return _gradientBackground; + } + + @ignore + Gradient? _gradientBackground; + late final String? gradientBackgroundString; + + // ==== boxShadows ===================================================== + + @ignore + BoxShadow get standardBoxShadow => + _standardBoxShadow ??= BoxShadowExt.fromJson( + Map.from( + jsonDecode(standardBoxShadowString) as Map, + ), + ); + @ignore + BoxShadow? _standardBoxShadow; + late final String standardBoxShadowString; + + @ignore + BoxShadow? get homeViewButtonBarBoxShadow { + if (homeViewButtonBarBoxShadowString == null) { + _homeViewButtonBarBoxShadow = null; + } else { + _homeViewButtonBarBoxShadow ??= BoxShadowExt.fromJson( + Map.from( + jsonDecode(homeViewButtonBarBoxShadowString!) as Map, + ), + ); + } + + return _homeViewButtonBarBoxShadow; + } + + @ignore + BoxShadow? _homeViewButtonBarBoxShadow; + late final String? homeViewButtonBarBoxShadowString; + + // ==== overlay ===================================================== + + @ignore + Color get overlay => _overlay ??= Color(overlayInt); + @ignore + Color? _overlay; + late final int overlayInt; + + // ==== accentColorBlue ===================================================== + + @ignore + Color get accentColorBlue => _accentColorBlue ??= Color( + accentColorBlueInt, + ); + @ignore + Color? _accentColorBlue; + late final int accentColorBlueInt; + + // ==== accentColorGreen ===================================================== + + @ignore + Color get accentColorGreen => _accentColorGreen ??= Color( + accentColorGreenInt, + ); + @ignore + Color? _accentColorGreen; + late final int accentColorGreenInt; + + // ==== accentColorYellow ===================================================== + + @ignore + Color get accentColorYellow => _accentColorYellow ??= Color( + accentColorYellowInt, + ); + @ignore + Color? _accentColorYellow; + late final int accentColorYellowInt; + + // ==== accentColorRed ===================================================== + + @ignore + Color get accentColorRed => _accentColorRed ??= Color( + accentColorRedInt, + ); + @ignore + Color? _accentColorRed; + late final int accentColorRedInt; + + // ==== accentColorOrange ===================================================== + + @ignore + Color get accentColorOrange => _accentColorOrange ??= Color( + accentColorOrangeInt, + ); + @ignore + Color? _accentColorOrange; + late final int accentColorOrangeInt; + + // ==== accentColorDark ===================================================== + + @ignore + Color get accentColorDark => _accentColorDark ??= Color( + accentColorDarkInt, + ); + @ignore + Color? _accentColorDark; + late final int accentColorDarkInt; + + // ==== shadow ===================================================== + + @ignore + Color get shadow => _shadow ??= Color( + shadowInt, + ); + @ignore + Color? _shadow; + late final int shadowInt; + + // ==== textDark ===================================================== + + @ignore + Color get textDark => _textDark ??= Color( + textDarkInt, + ); + @ignore + Color? _textDark; + late final int textDarkInt; + + // ==== textDark2 ===================================================== + + @ignore + Color get textDark2 => _textDark2 ??= Color( + textDark2Int, + ); + @ignore + Color? _textDark2; + late final int textDark2Int; + + // ==== textDark3 ===================================================== + + @ignore + Color get textDark3 => _textDark3 ??= Color( + textDark3Int, + ); + @ignore + Color? _textDark3; + late final int textDark3Int; + + // ==== textSubtitle1 ===================================================== + + @ignore + Color get textSubtitle1 => _textSubtitle1 ??= Color( + textSubtitle1Int, + ); + @ignore + Color? _textSubtitle1; + late final int textSubtitle1Int; + + // ==== textSubtitle2 ===================================================== + + @ignore + Color get textSubtitle2 => _textSubtitle2 ??= Color( + textSubtitle2Int, + ); + @ignore + Color? _textSubtitle2; + late final int textSubtitle2Int; + + // ==== textSubtitle3 ===================================================== + + @ignore + Color get textSubtitle3 => _textSubtitle3 ??= Color( + textSubtitle3Int, + ); + @ignore + Color? _textSubtitle3; + late final int textSubtitle3Int; + + // ==== textSubtitle4 ===================================================== + + @ignore + Color get textSubtitle4 => _textSubtitle4 ??= Color( + textSubtitle4Int, + ); + @ignore + Color? _textSubtitle4; + late final int textSubtitle4Int; + + // ==== textSubtitle5 ===================================================== + + @ignore + Color get textSubtitle5 => _textSubtitle5 ??= Color( + textSubtitle5Int, + ); + @ignore + Color? _textSubtitle5; + late final int textSubtitle5Int; + + // ==== textSubtitle6 ===================================================== + + @ignore + Color get textSubtitle6 => _textSubtitle6 ??= Color( + textSubtitle6Int, + ); + @ignore + Color? _textSubtitle6; + late final int textSubtitle6Int; + + // ==== textWhite ===================================================== + + @ignore + Color get textWhite => _textWhite ??= Color( + textWhiteInt, + ); + @ignore + Color? _textWhite; + late final int textWhiteInt; + + // ==== textFavoriteCard ===================================================== + + @ignore + Color get textFavoriteCard => _textFavoriteCard ??= Color( + textFavoriteCardInt, + ); + @ignore + Color? _textFavoriteCard; + late final int textFavoriteCardInt; + + // ==== textError ===================================================== + + @ignore + Color get textError => _textError ??= Color( + textErrorInt, + ); + @ignore + Color? _textError; + late final int textErrorInt; + + // ==== textRestore ===================================================== + + @ignore + Color get textRestore => _textRestore ??= Color( + textRestoreInt, + ); + @ignore + Color? _textRestore; + late final int textRestoreInt; + + // ==== buttonBackPrimary ===================================================== + + @ignore + Color get buttonBackPrimary => _buttonBackPrimary ??= Color( + buttonBackPrimaryInt, + ); + @ignore + Color? _buttonBackPrimary; + late final int buttonBackPrimaryInt; + + // ==== buttonBackSecondary ===================================================== + + @ignore + Color get buttonBackSecondary => _buttonBackSecondary ??= Color( + buttonBackSecondaryInt, + ); + @ignore + Color? _buttonBackSecondary; + late final int buttonBackSecondaryInt; + + // ==== buttonBackPrimaryDisabled ===================================================== + + @ignore + Color get buttonBackPrimaryDisabled => _buttonBackPrimaryDisabled ??= Color( + buttonBackPrimaryDisabledInt, + ); + @ignore + Color? _buttonBackPrimaryDisabled; + late final int buttonBackPrimaryDisabledInt; + + // ==== buttonBackSecondaryDisabled ===================================================== + + @ignore + Color get buttonBackSecondaryDisabled => + _buttonBackSecondaryDisabled ??= Color( + buttonBackSecondaryDisabledInt, + ); + @ignore + Color? _buttonBackSecondaryDisabled; + late final int buttonBackSecondaryDisabledInt; + + // ==== buttonBackBorder ===================================================== + + @ignore + Color get buttonBackBorder => _buttonBackBorder ??= Color( + buttonBackBorderInt, + ); + @ignore + Color? _buttonBackBorder; + late final int buttonBackBorderInt; + + // ==== buttonBackBorderDisabled ===================================================== + + @ignore + Color get buttonBackBorderDisabled => _buttonBackBorderDisabled ??= Color( + buttonBackBorderDisabledInt, + ); + @ignore + Color? _buttonBackBorderDisabled; + late final int buttonBackBorderDisabledInt; + + // ==== buttonBackBorderSecondary ===================================================== + + @ignore + Color get buttonBackBorderSecondary => _buttonBackBorderSecondary ??= Color( + buttonBackBorderSecondaryInt, + ); + @ignore + Color? _buttonBackBorderSecondary; + late final int buttonBackBorderSecondaryInt; + + // ==== buttonBackBorderSecondaryDisabled ===================================================== + + @ignore + Color get buttonBackBorderSecondaryDisabled => + _buttonBackBorderSecondaryDisabled ??= Color( + buttonBackBorderSecondaryDisabledInt, + ); + @ignore + Color? _buttonBackBorderSecondaryDisabled; + late final int buttonBackBorderSecondaryDisabledInt; + + // ==== numberBackDefault ===================================================== + + @ignore + Color get numberBackDefault => _numberBackDefault ??= Color( + numberBackDefaultInt, + ); + @ignore + Color? _numberBackDefault; + late final int numberBackDefaultInt; + + // ==== numpadBackDefault ===================================================== + + @ignore + Color get numpadBackDefault => _numpadBackDefault ??= Color( + numpadBackDefaultInt, + ); + @ignore + Color? _numpadBackDefault; + late final int numpadBackDefaultInt; + + // ==== bottomNavBack ===================================================== + + @ignore + Color get bottomNavBack => _bottomNavBack ??= Color( + bottomNavBackInt, + ); + @ignore + Color? _bottomNavBack; + late final int bottomNavBackInt; + + // ==== buttonTextPrimary ===================================================== + + @ignore + Color get buttonTextPrimary => _buttonTextPrimary ??= Color( + buttonTextPrimaryInt, + ); + @ignore + Color? _buttonTextPrimary; + late final int buttonTextPrimaryInt; + + // ==== buttonTextSecondary ===================================================== + + @ignore + Color get buttonTextSecondary => _buttonTextSecondary ??= Color( + buttonTextSecondaryInt, + ); + @ignore + Color? _buttonTextSecondary; + late final int buttonTextSecondaryInt; + + // ==== buttonTextPrimaryDisabled ===================================================== + + @ignore + Color get buttonTextPrimaryDisabled => _buttonTextPrimaryDisabled ??= Color( + buttonTextPrimaryDisabledInt, + ); + @ignore + Color? _buttonTextPrimaryDisabled; + late final int buttonTextPrimaryDisabledInt; + + // ==== buttonTextSecondaryDisabled ===================================================== + + @ignore + Color get buttonTextSecondaryDisabled => + _buttonTextSecondaryDisabled ??= Color(buttonTextSecondaryDisabledInt); + @ignore + Color? _buttonTextSecondaryDisabled; + late final int buttonTextSecondaryDisabledInt; + + // ==== buttonTextBorder ===================================================== + + @ignore + Color get buttonTextBorder => + _buttonTextBorder ??= Color(buttonTextBorderInt); + @ignore + Color? _buttonTextBorder; + late final int buttonTextBorderInt; + + // ==== buttonTextDisabled ===================================================== + + @ignore + Color get buttonTextDisabled => + _buttonTextDisabled ??= Color(buttonTextDisabledInt); + @ignore + Color? _buttonTextDisabled; + late final int buttonTextDisabledInt; + + // ==== buttonTextBorderless ===================================================== + + @ignore + Color get buttonTextBorderless => + _buttonTextBorderless ??= Color(buttonTextBorderlessInt); + @ignore + Color? _buttonTextBorderless; + late final int buttonTextBorderlessInt; + + // ==== buttonTextBorderlessDisabled ===================================================== + + @ignore + Color get buttonTextBorderlessDisabled => + _buttonTextBorderlessDisabled ??= Color(buttonTextBorderlessDisabledInt); + @ignore + Color? _buttonTextBorderlessDisabled; + late final int buttonTextBorderlessDisabledInt; + + // ==== numberTextDefault ===================================================== + + @ignore + Color get numberTextDefault => + _numberTextDefault ??= Color(numberTextDefaultInt); + @ignore + Color? _numberTextDefault; + late final int numberTextDefaultInt; + + // ==== numpadTextDefault ===================================================== + + @ignore + Color get numpadTextDefault => + _numpadTextDefault ??= Color(numpadTextDefaultInt); + @ignore + Color? _numpadTextDefault; + late final int numpadTextDefaultInt; + + // ==== bottomNavText ===================================================== + + @ignore + Color get bottomNavText => _bottomNavText ??= Color(bottomNavTextInt); + @ignore + Color? _bottomNavText; + late final int bottomNavTextInt; + + // ==== customTextButtonEnabledText ===================================================== + + @ignore + Color get customTextButtonEnabledText => + _customTextButtonEnabledText ??= Color(customTextButtonEnabledTextInt); + @ignore + Color? _customTextButtonEnabledText; + late final int customTextButtonEnabledTextInt; + + // ==== customTextButtonDisabledText ===================================================== + + @ignore + Color get customTextButtonDisabledText => + _customTextButtonDisabledText ??= Color(customTextButtonDisabledTextInt); + @ignore + Color? _customTextButtonDisabledText; + late final int customTextButtonDisabledTextInt; + + // ==== switchBGOn ===================================================== + + @ignore + Color get switchBGOn => _switchBGOn ??= Color(switchBGOnInt); + @ignore + Color? _switchBGOn; + late final int switchBGOnInt; + + // ==== switchBGOff ===================================================== + + @ignore + Color get switchBGOff => _switchBGOff ??= Color(switchBGOffInt); + @ignore + Color? _switchBGOff; + late final int switchBGOffInt; + + // ==== switchBGDisabled ===================================================== + + @ignore + Color get switchBGDisabled => + _switchBGDisabled ??= Color(switchBGDisabledInt); + @ignore + Color? _switchBGDisabled; + late final int switchBGDisabledInt; + + // ==== switchCircleOn ===================================================== + + @ignore + Color get switchCircleOn => _switchCircleOn ??= Color(switchCircleOnInt); + @ignore + Color? _switchCircleOn; + late final int switchCircleOnInt; + + // ==== switchCircleOff ===================================================== + + @ignore + Color get switchCircleOff => _switchCircleOff ??= Color(switchCircleOffInt); + @ignore + Color? _switchCircleOff; + late final int switchCircleOffInt; + + // ==== switchCircleDisabled ===================================================== + + @ignore + Color get switchCircleDisabled => + _switchCircleDisabled ??= Color(switchCircleDisabledInt); + @ignore + Color? _switchCircleDisabled; + late final int switchCircleDisabledInt; + + // ==== stepIndicatorBGCheck ===================================================== + + @ignore + Color get stepIndicatorBGCheck => + _stepIndicatorBGCheck ??= Color(stepIndicatorBGCheckInt); + @ignore + Color? _stepIndicatorBGCheck; + late final int stepIndicatorBGCheckInt; + + // ==== stepIndicatorBGNumber ===================================================== + + @ignore + Color get stepIndicatorBGNumber => + _stepIndicatorBGNumber ??= Color(stepIndicatorBGNumberInt); + @ignore + Color? _stepIndicatorBGNumber; + late final int stepIndicatorBGNumberInt; + + // ==== stepIndicatorBGInactive ===================================================== + + @ignore + Color get stepIndicatorBGInactive => + _stepIndicatorBGInactive ??= Color(stepIndicatorBGInactiveInt); + @ignore + Color? _stepIndicatorBGInactive; + late final int stepIndicatorBGInactiveInt; + + // ==== stepIndicatorBGLines ===================================================== + + @ignore + Color get stepIndicatorBGLines => + _stepIndicatorBGLines ??= Color(stepIndicatorBGLinesInt); + @ignore + Color? _stepIndicatorBGLines; + late final int stepIndicatorBGLinesInt; + + // ==== stepIndicatorBGLinesInactive ===================================================== + + @ignore + Color get stepIndicatorBGLinesInactive => + _stepIndicatorBGLinesInactive ??= Color(stepIndicatorBGLinesInactiveInt); + @ignore + Color? _stepIndicatorBGLinesInactive; + late final int stepIndicatorBGLinesInactiveInt; + + // ==== stepIndicatorIconText ===================================================== + + @ignore + Color get stepIndicatorIconText => + _stepIndicatorIconText ??= Color(stepIndicatorIconTextInt); + @ignore + Color? _stepIndicatorIconText; + late final int stepIndicatorIconTextInt; + + // ==== stepIndicatorIconNumber ===================================================== + + @ignore + Color get stepIndicatorIconNumber => + _stepIndicatorIconNumber ??= Color(stepIndicatorIconNumberInt); + @ignore + Color? _stepIndicatorIconNumber; + late final int stepIndicatorIconNumberInt; + + // ==== stepIndicatorIconInactive ===================================================== + + @ignore + Color get stepIndicatorIconInactive => + _stepIndicatorIconInactive ??= Color(stepIndicatorIconInactiveInt); + @ignore + Color? _stepIndicatorIconInactive; + late final int stepIndicatorIconInactiveInt; + + // ==== checkboxBGChecked ===================================================== + + @ignore + Color get checkboxBGChecked => + _checkboxBGChecked ??= Color(checkboxBGCheckedInt); + @ignore + Color? _checkboxBGChecked; + late final int checkboxBGCheckedInt; + + // ==== checkboxBorderEmpty ===================================================== + + @ignore + Color get checkboxBorderEmpty => + _checkboxBorderEmpty ??= Color(checkboxBorderEmptyInt); + @ignore + Color? _checkboxBorderEmpty; + late final int checkboxBorderEmptyInt; + + // ==== checkboxBGDisabled ===================================================== + + @ignore + Color get checkboxBGDisabled => + _checkboxBGDisabled ??= Color(checkboxBGDisabledInt); + @ignore + Color? _checkboxBGDisabled; + late final int checkboxBGDisabledInt; + + // ==== checkboxIconChecked ===================================================== + + @ignore + Color get checkboxIconChecked => + _checkboxIconChecked ??= Color(checkboxIconCheckedInt); + @ignore + Color? _checkboxIconChecked; + late final int checkboxIconCheckedInt; + + // ==== checkboxIconDisabled ===================================================== + + @ignore + Color get checkboxIconDisabled => + _checkboxIconDisabled ??= Color(checkboxIconDisabledInt); + @ignore + Color? _checkboxIconDisabled; + late final int checkboxIconDisabledInt; + + // ==== checkboxTextLabel ===================================================== + + @ignore + Color get checkboxTextLabel => + _checkboxTextLabel ??= Color(checkboxTextLabelInt); + @ignore + Color? _checkboxTextLabel; + late final int checkboxTextLabelInt; + + // ==== snackBarBackSuccess ===================================================== + + @ignore + Color get snackBarBackSuccess => + _snackBarBackSuccess ??= Color(snackBarBackSuccessInt); + @ignore + Color? _snackBarBackSuccess; + late final int snackBarBackSuccessInt; + + // ==== snackBarBackError ===================================================== + + @ignore + Color get snackBarBackError => + _snackBarBackError ??= Color(snackBarBackErrorInt); + @ignore + Color? _snackBarBackError; + late final int snackBarBackErrorInt; + + // ==== snackBarBackInfo ===================================================== + + @ignore + Color get snackBarBackInfo => + _snackBarBackInfo ??= Color(snackBarBackInfoInt); + @ignore + Color? _snackBarBackInfo; + late final int snackBarBackInfoInt; + + // ==== snackBarTextSuccess ===================================================== + + @ignore + Color get snackBarTextSuccess => + _snackBarTextSuccess ??= Color(snackBarTextSuccessInt); + @ignore + Color? _snackBarTextSuccess; + late final int snackBarTextSuccessInt; + + // ==== snackBarTextError ===================================================== + + @ignore + Color get snackBarTextError => + _snackBarTextError ??= Color(snackBarTextErrorInt); + @ignore + Color? _snackBarTextError; + late final int snackBarTextErrorInt; + + // ==== snackBarTextInfo ===================================================== + + @ignore + Color get snackBarTextInfo => + _snackBarTextInfo ??= Color(snackBarTextInfoInt); + @ignore + Color? _snackBarTextInfo; + late final int snackBarTextInfoInt; + + // ==== bottomNavIconBack ===================================================== + + @ignore + Color get bottomNavIconBack => + _bottomNavIconBack ??= Color(bottomNavIconBackInt); + @ignore + Color? _bottomNavIconBack; + late final int bottomNavIconBackInt; + + // ==== bottomNavIconIcon ===================================================== + + @ignore + Color get bottomNavIconIcon => + _bottomNavIconIcon ??= Color(bottomNavIconIconInt); + @ignore + Color? _bottomNavIconIcon; + late final int bottomNavIconIconInt; + + // ==== bottomNavIconIcon highlighted ===================================================== + + @ignore + Color get bottomNavIconIconHighlighted => + _bottomNavIconIconHighlighted ??= Color(bottomNavIconIconHighlightedInt); + @ignore + Color? _bottomNavIconIconHighlighted; + late final int bottomNavIconIconHighlightedInt; + + // ==== topNavIconPrimary ===================================================== + + @ignore + Color get topNavIconPrimary => + _topNavIconPrimary ??= Color(topNavIconPrimaryInt); + @ignore + Color? _topNavIconPrimary; + late final int topNavIconPrimaryInt; + + // ==== topNavIconGreen ===================================================== + + @ignore + Color get topNavIconGreen => _topNavIconGreen ??= Color(topNavIconGreenInt); + @ignore + Color? _topNavIconGreen; + late final int topNavIconGreenInt; + + // ==== topNavIconYellow ===================================================== + + @ignore + Color get topNavIconYellow => + _topNavIconYellow ??= Color(topNavIconYellowInt); + @ignore + Color? _topNavIconYellow; + late final int topNavIconYellowInt; + + // ==== topNavIconRed ===================================================== + + @ignore + Color get topNavIconRed => _topNavIconRed ??= Color(topNavIconRedInt); + @ignore + Color? _topNavIconRed; + late final int topNavIconRedInt; + + // ==== settingsIconBack ===================================================== + + @ignore + Color get settingsIconBack => + _settingsIconBack ??= Color(settingsIconBackInt); + @ignore + Color? _settingsIconBack; + late final int settingsIconBackInt; + + // ==== settingsIconIcon ===================================================== + + @ignore + Color get settingsIconIcon => + _settingsIconIcon ??= Color(settingsIconIconInt); + @ignore + Color? _settingsIconIcon; + late final int settingsIconIconInt; + + // ==== settingsIconBack2 ===================================================== + + @ignore + Color get settingsIconBack2 => + _settingsIconBack2 ??= Color(settingsIconBack2Int); + @ignore + Color? _settingsIconBack2; + late final int settingsIconBack2Int; + + // ==== settingsIconElement ===================================================== + + @ignore + Color get settingsIconElement => + _settingsIconElement ??= Color(settingsIconElementInt); + @ignore + Color? _settingsIconElement; + late final int settingsIconElementInt; + + // ==== textFieldActiveBG ===================================================== + + @ignore + Color get textFieldActiveBG => + _textFieldActiveBG ??= Color(textFieldActiveBGInt); + @ignore + Color? _textFieldActiveBG; + late final int textFieldActiveBGInt; + + // ==== textFieldDefaultBG ===================================================== + + @ignore + Color get textFieldDefaultBG => + _textFieldDefaultBG ??= Color(textFieldDefaultBGInt); + @ignore + Color? _textFieldDefaultBG; + late final int textFieldDefaultBGInt; + + // ==== textFieldErrorBG ===================================================== + + @ignore + Color get textFieldErrorBG => + _textFieldErrorBG ??= Color(textFieldErrorBGInt); + @ignore + Color? _textFieldErrorBG; + late final int textFieldErrorBGInt; + + // ==== textFieldSuccessBG ===================================================== + + @ignore + Color get textFieldSuccessBG => + _textFieldSuccessBG ??= Color(textFieldSuccessBGInt); + @ignore + Color? _textFieldSuccessBG; + late final int textFieldSuccessBGInt; + + // ==== textFieldErrorBorder ===================================================== + + @ignore + Color get textFieldErrorBorder => + _textFieldErrorBorder ??= Color(textFieldErrorBorderInt); + @ignore + Color? _textFieldErrorBorder; + late final int textFieldErrorBorderInt; + + // ==== textFieldSuccessBorder ===================================================== + + @ignore + Color get textFieldSuccessBorder => + _textFieldSuccessBorder ??= Color(textFieldSuccessBorderInt); + @ignore + Color? _textFieldSuccessBorder; + late final int textFieldSuccessBorderInt; + + // ==== textFieldActiveSearchIconLeft ===================================================== + + @ignore + Color get textFieldActiveSearchIconLeft => _textFieldActiveSearchIconLeft ??= + Color(textFieldActiveSearchIconLeftInt); + @ignore + Color? _textFieldActiveSearchIconLeft; + late final int textFieldActiveSearchIconLeftInt; + + // ==== textFieldDefaultSearchIconLeft ===================================================== + + @ignore + Color get textFieldDefaultSearchIconLeft => + _textFieldDefaultSearchIconLeft ??= + Color(textFieldDefaultSearchIconLeftInt); + @ignore + Color? _textFieldDefaultSearchIconLeft; + late final int textFieldDefaultSearchIconLeftInt; + + // ==== textFieldErrorSearchIconLeft ===================================================== + + @ignore + Color get textFieldErrorSearchIconLeft => + _textFieldErrorSearchIconLeft ??= Color(textFieldErrorSearchIconLeftInt); + @ignore + Color? _textFieldErrorSearchIconLeft; + late final int textFieldErrorSearchIconLeftInt; + + // ==== textFieldSuccessSearchIconLeft ===================================================== + + @ignore + Color get textFieldSuccessSearchIconLeft => + _textFieldSuccessSearchIconLeft ??= + Color(textFieldSuccessSearchIconLeftInt); + @ignore + Color? _textFieldSuccessSearchIconLeft; + late final int textFieldSuccessSearchIconLeftInt; + + // ==== textFieldActiveText ===================================================== + + @ignore + Color get textFieldActiveText => + _textFieldActiveText ??= Color(textFieldActiveTextInt); + @ignore + Color? _textFieldActiveText; + late final int textFieldActiveTextInt; + + // ==== textFieldDefaultText ===================================================== + + @ignore + Color get textFieldDefaultText => + _textFieldDefaultText ??= Color(textFieldDefaultTextInt); + @ignore + Color? _textFieldDefaultText; + late final int textFieldDefaultTextInt; + + // ==== textFieldErrorText ===================================================== + + @ignore + Color get textFieldErrorText => + _textFieldErrorText ??= Color(textFieldErrorTextInt); + @ignore + Color? _textFieldErrorText; + late final int textFieldErrorTextInt; + + // ==== textFieldSuccessText ===================================================== + + @ignore + Color get textFieldSuccessText => + _textFieldSuccessText ??= Color(textFieldSuccessTextInt); + @ignore + Color? _textFieldSuccessText; + late final int textFieldSuccessTextInt; + + // ==== textFieldActiveLabel ===================================================== + + @ignore + Color get textFieldActiveLabel => + _textFieldActiveLabel ??= Color(textFieldActiveLabelInt); + @ignore + Color? _textFieldActiveLabel; + late final int textFieldActiveLabelInt; + + // ==== textFieldErrorLabel ===================================================== + + @ignore + Color get textFieldErrorLabel => + _textFieldErrorLabel ??= Color(textFieldErrorLabelInt); + @ignore + Color? _textFieldErrorLabel; + late final int textFieldErrorLabelInt; + + // ==== textFieldSuccessLabel ===================================================== + + @ignore + Color get textFieldSuccessLabel => + _textFieldSuccessLabel ??= Color(textFieldSuccessLabelInt); + @ignore + Color? _textFieldSuccessLabel; + late final int textFieldSuccessLabelInt; + + // ==== textFieldActiveSearchIconRight ===================================================== + + @ignore + Color get textFieldActiveSearchIconRight => + _textFieldActiveSearchIconRight ??= + Color(textFieldActiveSearchIconRightInt); + @ignore + Color? _textFieldActiveSearchIconRight; + late final int textFieldActiveSearchIconRightInt; + + // ==== textFieldDefaultSearchIconRight ===================================================== + + @ignore + Color get textFieldDefaultSearchIconRight => + _textFieldDefaultSearchIconRight ??= + Color(textFieldDefaultSearchIconRightInt); + @ignore + Color? _textFieldDefaultSearchIconRight; + late final int textFieldDefaultSearchIconRightInt; + + // ==== textFieldErrorSearchIconRight ===================================================== + + @ignore + Color get textFieldErrorSearchIconRight => _textFieldErrorSearchIconRight ??= + Color(textFieldErrorSearchIconRightInt); + @ignore + Color? _textFieldErrorSearchIconRight; + late final int textFieldErrorSearchIconRightInt; + + // ==== textFieldSuccessSearchIconRight ===================================================== + + @ignore + Color get textFieldSuccessSearchIconRight => + _textFieldSuccessSearchIconRight ??= + Color(textFieldSuccessSearchIconRightInt); + @ignore + Color? _textFieldSuccessSearchIconRight; + late final int textFieldSuccessSearchIconRightInt; + + // ==== settingsItem2ActiveBG ===================================================== + + @ignore + Color get settingsItem2ActiveBG => + _settingsItem2ActiveBG ??= Color(settingsItem2ActiveBGInt); + @ignore + Color? _settingsItem2ActiveBG; + late final int settingsItem2ActiveBGInt; + + // ==== settingsItem2ActiveText ===================================================== + + @ignore + Color get settingsItem2ActiveText => + _settingsItem2ActiveText ??= Color(settingsItem2ActiveTextInt); + @ignore + Color? _settingsItem2ActiveText; + late final int settingsItem2ActiveTextInt; + + // ==== settingsItem2ActiveSub ===================================================== + + @ignore + Color get settingsItem2ActiveSub => + _settingsItem2ActiveSub ??= Color(settingsItem2ActiveSubInt); + @ignore + Color? _settingsItem2ActiveSub; + late final int settingsItem2ActiveSubInt; + + // ==== radioButtonIconBorder ===================================================== + + @ignore + Color get radioButtonIconBorder => + _radioButtonIconBorder ??= Color(radioButtonIconBorderInt); + @ignore + Color? _radioButtonIconBorder; + late final int radioButtonIconBorderInt; + + // ==== radioButtonIconBorderDisabled ===================================================== + + @ignore + Color get radioButtonIconBorderDisabled => _radioButtonIconBorderDisabled ??= + Color(radioButtonIconBorderDisabledInt); + @ignore + Color? _radioButtonIconBorderDisabled; + late final int radioButtonIconBorderDisabledInt; + + // ==== radioButtonBorderEnabled ===================================================== + + @ignore + Color get radioButtonBorderEnabled => + _radioButtonBorderEnabled ??= Color(radioButtonBorderEnabledInt); + @ignore + Color? _radioButtonBorderEnabled; + late final int radioButtonBorderEnabledInt; + + // ==== radioButtonBorderDisabled ===================================================== + + @ignore + Color get radioButtonBorderDisabled => + _radioButtonBorderDisabled ??= Color(radioButtonBorderDisabledInt); + @ignore + Color? _radioButtonBorderDisabled; + late final int radioButtonBorderDisabledInt; + + // ==== radioButtonIconCircle ===================================================== + + @ignore + Color get radioButtonIconCircle => + _radioButtonIconCircle ??= Color(radioButtonIconCircleInt); + @ignore + Color? _radioButtonIconCircle; + late final int radioButtonIconCircleInt; + + // ==== radioButtonIconEnabled ===================================================== + + @ignore + Color get radioButtonIconEnabled => + _radioButtonIconEnabled ??= Color(radioButtonIconEnabledInt); + @ignore + Color? _radioButtonIconEnabled; + late final int radioButtonIconEnabledInt; + + // ==== radioButtonTextEnabled ===================================================== + + @ignore + Color get radioButtonTextEnabled => + _radioButtonTextEnabled ??= Color(radioButtonTextEnabledInt); + @ignore + Color? _radioButtonTextEnabled; + late final int radioButtonTextEnabledInt; + + // ==== radioButtonTextDisabled ===================================================== + + @ignore + Color get radioButtonTextDisabled => + _radioButtonTextDisabled ??= Color(radioButtonTextDisabledInt); + @ignore + Color? _radioButtonTextDisabled; + late final int radioButtonTextDisabledInt; + + // ==== radioButtonLabelEnabled ===================================================== + + @ignore + Color get radioButtonLabelEnabled => + _radioButtonLabelEnabled ??= Color(radioButtonLabelEnabledInt); + @ignore + Color? _radioButtonLabelEnabled; + late final int radioButtonLabelEnabledInt; + + // ==== radioButtonLabelDisabled ===================================================== + + @ignore + Color get radioButtonLabelDisabled => + _radioButtonLabelDisabled ??= Color(radioButtonLabelDisabledInt); + @ignore + Color? _radioButtonLabelDisabled; + late final int radioButtonLabelDisabledInt; + + // ==== infoItemBG ===================================================== + + @ignore + Color get infoItemBG => _infoItemBG ??= Color(infoItemBGInt); + @ignore + Color? _infoItemBG; + late final int infoItemBGInt; + + // ==== infoItemLabel ===================================================== + + @ignore + Color get infoItemLabel => _infoItemLabel ??= Color(infoItemLabelInt); + @ignore + Color? _infoItemLabel; + late final int infoItemLabelInt; + + // ==== infoItemText ===================================================== + + @ignore + Color get infoItemText => _infoItemText ??= Color(infoItemTextInt); + @ignore + Color? _infoItemText; + late final int infoItemTextInt; + + // ==== infoItemIcons ===================================================== + + @ignore + Color get infoItemIcons => _infoItemIcons ??= Color(infoItemIconsInt); + @ignore + Color? _infoItemIcons; + late final int infoItemIconsInt; + + // ==== popupBG ===================================================== + + @ignore + Color get popupBG => _popupBG ??= Color(popupBGInt); + @ignore + Color? _popupBG; + late final int popupBGInt; + + // ==== currencyListItemBG ===================================================== + + @ignore + Color get currencyListItemBG => + _currencyListItemBG ??= Color(currencyListItemBGInt); + @ignore + Color? _currencyListItemBG; + late final int currencyListItemBGInt; + + // ==== stackWalletBG ===================================================== + + @ignore + Color get stackWalletBG => _stackWalletBG ??= Color(stackWalletBGInt); + @ignore + Color? _stackWalletBG; + late final int stackWalletBGInt; + + // ==== stackWalletMid ===================================================== + + @ignore + Color get stackWalletMid => _stackWalletMid ??= Color(stackWalletMidInt); + @ignore + Color? _stackWalletMid; + late final int stackWalletMidInt; + + // ==== stackWalletBottom ===================================================== + + @ignore + Color get stackWalletBottom => + _stackWalletBottom ??= Color(stackWalletBottomInt); + @ignore + Color? _stackWalletBottom; + late final int stackWalletBottomInt; + + // ==== bottomNavShadow ===================================================== + + @ignore + Color get bottomNavShadow => _bottomNavShadow ??= Color(bottomNavShadowInt); + @ignore + Color? _bottomNavShadow; + late final int bottomNavShadowInt; + + // ==== favoriteStarActive ===================================================== + + @ignore + Color get favoriteStarActive => + _favoriteStarActive ??= Color(favoriteStarActiveInt); + @ignore + Color? _favoriteStarActive; + late final int favoriteStarActiveInt; + + // ==== favoriteStarInactive ===================================================== + + @ignore + Color get favoriteStarInactive => + _favoriteStarInactive ??= Color(favoriteStarInactiveInt); + @ignore + Color? _favoriteStarInactive; + late final int favoriteStarInactiveInt; + + // ==== splash ===================================================== + + @ignore + Color get splash => _splash ??= Color(splashInt); + @ignore + Color? _splash; + late final int splashInt; + + // ==== highlight ===================================================== + + @ignore + Color get highlight => _highlight ??= Color(highlightInt); + @ignore + Color? _highlight; + late final int highlightInt; + + // ==== warningForeground ===================================================== + + @ignore + Color get warningForeground => + _warningForeground ??= Color(warningForegroundInt); + @ignore + Color? _warningForeground; + late final int warningForegroundInt; + + // ==== warningBackground ===================================================== + + @ignore + Color get warningBackground => + _warningBackground ??= Color(warningBackgroundInt); + @ignore + Color? _warningBackground; + late final int warningBackgroundInt; + + // ==== loadingOverlayTextColor ===================================================== + + @ignore + Color get loadingOverlayTextColor => + _loadingOverlayTextColor ??= Color(loadingOverlayTextColorInt); + @ignore + Color? _loadingOverlayTextColor; + late final int loadingOverlayTextColorInt; + + // ==== myStackContactIconBG ===================================================== + + @ignore + Color get myStackContactIconBG => + _myStackContactIconBG ??= Color(myStackContactIconBGInt); + @ignore + Color? _myStackContactIconBG; + late final int myStackContactIconBGInt; + + // ==== textConfirmTotalAmount ===================================================== + + @ignore + Color get textConfirmTotalAmount => + _textConfirmTotalAmount ??= Color(textConfirmTotalAmountInt); + @ignore + Color? _textConfirmTotalAmount; + late final int textConfirmTotalAmountInt; + + // ==== textSelectedWordTableItem ===================================================== + + @ignore + Color get textSelectedWordTableItem => + _textSelectedWordTableItem ??= Color(textSelectedWordTableItemInt); + @ignore + Color? _textSelectedWordTableItem; + late final int textSelectedWordTableItemInt; + + // ==== rateTypeToggleColorOn ===================================================== + + @ignore + Color get rateTypeToggleColorOn => + _rateTypeToggleColorOn ??= Color(rateTypeToggleColorOnInt); + @ignore + Color? _rateTypeToggleColorOn; + late final int rateTypeToggleColorOnInt; + + // ==== rateTypeToggleColorOff ===================================================== + + @ignore + Color get rateTypeToggleColorOff => + _rateTypeToggleColorOff ??= Color(rateTypeToggleColorOffInt); + @ignore + Color? _rateTypeToggleColorOff; + late final int rateTypeToggleColorOffInt; + + // ==== rateTypeToggleDesktopColorOn ===================================================== + + @ignore + Color get rateTypeToggleDesktopColorOn => + _rateTypeToggleDesktopColorOn ??= Color(rateTypeToggleDesktopColorOnInt); + @ignore + Color? _rateTypeToggleDesktopColorOn; + late final int rateTypeToggleDesktopColorOnInt; + + // ==== rateTypeToggleDesktopColorOff ===================================================== + + @ignore + Color get rateTypeToggleDesktopColorOff => _rateTypeToggleDesktopColorOff ??= + Color(rateTypeToggleDesktopColorOffInt); + @ignore + Color? _rateTypeToggleDesktopColorOff; + late final int rateTypeToggleDesktopColorOffInt; + + // ==== ethTagText ===================================================== + + @ignore + Color get ethTagText => _ethTagText ??= Color(ethTagTextInt); + @ignore + Color? _ethTagText; + late final int ethTagTextInt; + + // ==== ethTagBG ===================================================== + + @ignore + Color get ethTagBG => _ethTagBG ??= Color(ethTagBGInt); + @ignore + Color? _ethTagBG; + late final int ethTagBGInt; + + // ==== ethWalletTagText ===================================================== + + @ignore + Color get ethWalletTagText => + _ethWalletTagText ??= Color(ethWalletTagTextInt); + @ignore + Color? _ethWalletTagText; + late final int ethWalletTagTextInt; + + // ==== ethWalletTagBG ===================================================== + + @ignore + Color get ethWalletTagBG => _ethWalletTagBG ??= Color(ethWalletTagBGInt); + @ignore + Color? _ethWalletTagBG; + late final int ethWalletTagBGInt; + + // ==== tokenSummaryTextPrimary ===================================================== + + @ignore + Color get tokenSummaryTextPrimary => + _tokenSummaryTextPrimary ??= Color(tokenSummaryTextPrimaryInt); + @ignore + Color? _tokenSummaryTextPrimary; + late final int tokenSummaryTextPrimaryInt; + + // ==== tokenSummaryTextSecondary ===================================================== + + @ignore + Color get tokenSummaryTextSecondary => + _tokenSummaryTextSecondary ??= Color(tokenSummaryTextSecondaryInt); + @ignore + Color? _tokenSummaryTextSecondary; + late final int tokenSummaryTextSecondaryInt; + + // ==== tokenSummaryBG ===================================================== + + @ignore + Color get tokenSummaryBG => _tokenSummaryBG ??= Color(tokenSummaryBGInt); + @ignore + Color? _tokenSummaryBG; + late final int tokenSummaryBGInt; + + // ==== tokenSummaryButtonBG ===================================================== + + @ignore + Color get tokenSummaryButtonBG => + _tokenSummaryButtonBG ??= Color(tokenSummaryButtonBGInt); + @ignore + Color? _tokenSummaryButtonBG; + late final int tokenSummaryButtonBGInt; + + // ==== tokenSummaryIcon ===================================================== + + @ignore + Color get tokenSummaryIcon => + _tokenSummaryIcon ??= Color(tokenSummaryIconInt); + @ignore + Color? _tokenSummaryIcon; + late final int tokenSummaryIconInt; + + // ==== coinColors ===================================================== + + @ignore + Map get coinColors => + _coinColors ??= parseCoinColors(coinColorsJsonString); + @ignore + Map? _coinColors; + late final String coinColorsJsonString; + + // ==== assets ===================================================== + + @Name("assets") // legacy "column" name + late final ThemeAssets? assetsV1; + + late final ThemeAssetsV2? assetsV2; + + @ignore + IThemeAssets get assets => assetsV2 ?? assetsV1!; + + // =========================================================================== + + late final int? version; + + StackTheme(); + + factory StackTheme.fromJson({ + required Map json, + required String applicationThemesDirectoryPath, + }) { + final version = json["version"] as int? ?? 1; + + return StackTheme() + ..version = version + ..assetsV1 = version == 1 + ? ThemeAssets.fromJson( + json: Map.from(json["assets"] as Map), + applicationThemesDirectoryPath: applicationThemesDirectoryPath, + themeId: json["id"] as String, + ) + : null + ..assetsV2 = version == 2 + ? ThemeAssetsV2.fromJson( + json: Map.from(json["assets"] as Map), + applicationThemesDirectoryPath: applicationThemesDirectoryPath, + themeId: json["id"] as String, + ) + : null + ..themeId = json["id"] as String + ..name = json["name"] as String + ..brightnessString = json["brightness"] as String + ..backgroundInt = parseColor(json["colors"]["background"] as String) + ..backgroundAppBarInt = + parseColor(json["colors"]["background_app_bar"] as String) + ..gradientBackgroundString = json["colors"]["gradients"] != null + ? jsonEncode(json["colors"]["gradients"]) + : null + ..standardBoxShadowString = + jsonEncode(json["colors"]["box_shadows"]["standard"] as Map) + ..homeViewButtonBarBoxShadowString = + json["colors"]["box_shadows"]["home_view_button_bar"] == null + ? null + : jsonEncode( + json["colors"]["box_shadows"]["home_view_button_bar"] as Map) + ..coinColorsJsonString = jsonEncode(json["colors"]['coin'] as Map) + ..overlayInt = parseColor(json["colors"]["overlay"] as String) + ..accentColorBlueInt = + parseColor(json["colors"]["accent_color_blue"] as String) + ..accentColorGreenInt = + parseColor(json["colors"]["accent_color_green"] as String) + ..accentColorYellowInt = + parseColor(json["colors"]["accent_color_yellow"] as String) + ..accentColorRedInt = + parseColor(json["colors"]["accent_color_red"] as String) + ..accentColorOrangeInt = + parseColor(json["colors"]["accent_color_orange"] as String) + ..accentColorDarkInt = + parseColor(json["colors"]["accent_color_dark"] as String) + ..shadowInt = parseColor(json["colors"]["shadow"] as String) + ..textDarkInt = parseColor(json["colors"]["text_dark_one"] as String) + ..textDark2Int = parseColor(json["colors"]["text_dark_two"] as String) + ..textDark3Int = parseColor(json["colors"]["text_dark_three"] as String) + ..textWhiteInt = parseColor(json["colors"]["text_white"] as String) + ..textFavoriteCardInt = + parseColor(json["colors"]["text_favorite"] as String) + ..textErrorInt = parseColor(json["colors"]["text_error"] as String) + ..textRestoreInt = parseColor(json["colors"]["text_restore"] as String) + ..buttonBackPrimaryInt = + parseColor(json["colors"]["button_back_primary"] as String) + ..buttonBackSecondaryInt = + parseColor(json["colors"]["button_back_secondary"] as String) + ..buttonBackPrimaryDisabledInt = + parseColor(json["colors"]["button_back_primary_disabled"] as String) + ..buttonBackSecondaryDisabledInt = + parseColor(json["colors"]["button_back_secondary_disabled"] as String) + ..buttonBackBorderInt = + parseColor(json["colors"]["button_back_border"] as String) + ..buttonBackBorderDisabledInt = + parseColor(json["colors"]["button_back_border_disabled"] as String) + ..buttonBackBorderSecondaryInt = + parseColor(json["colors"]["button_back_border_secondary"] as String) + ..buttonBackBorderSecondaryDisabledInt = parseColor( + json["colors"]["button_back_border_secondary_disabled"] as String) + ..numberBackDefaultInt = + parseColor(json["colors"]["number_back_default"] as String) + ..numpadBackDefaultInt = + parseColor(json["colors"]["numpad_back_default"] as String) + ..bottomNavBackInt = + parseColor(json["colors"]["bottom_nav_back"] as String) + ..textSubtitle1Int = + parseColor(json["colors"]["text_subtitle_one"] as String) + ..textSubtitle2Int = + parseColor(json["colors"]["text_subtitle_two"] as String) + ..textSubtitle3Int = + parseColor(json["colors"]["text_subtitle_three"] as String) + ..textSubtitle4Int = + parseColor(json["colors"]["text_subtitle_four"] as String) + ..textSubtitle5Int = + parseColor(json["colors"]["text_subtitle_five"] as String) + ..textSubtitle6Int = + parseColor(json["colors"]["text_subtitle_six"] as String) + ..buttonTextPrimaryInt = + parseColor(json["colors"]["button_text_primary"] as String) + ..buttonTextSecondaryInt = + parseColor(json["colors"]["button_text_secondary"] as String) + ..buttonTextPrimaryDisabledInt = + parseColor(json["colors"]["button_text_primary_disabled"] as String) + ..buttonTextSecondaryDisabledInt = + parseColor(json["colors"]["button_text_secondary_disabled"] as String) + ..buttonTextBorderInt = + parseColor(json["colors"]["button_text_border"] as String) + ..buttonTextDisabledInt = + parseColor(json["colors"]["button_text_disabled"] as String) + ..buttonTextBorderlessInt = + parseColor(json["colors"]["button_text_borderless"] as String) + ..buttonTextBorderlessDisabledInt = parseColor( + json["colors"]["button_text_borderless_disabled"] as String) + ..numberTextDefaultInt = + parseColor(json["colors"]["number_text_default"] as String) + ..numpadTextDefaultInt = + parseColor(json["colors"]["numpad_text_default"] as String) + ..bottomNavTextInt = + parseColor(json["colors"]["bottom_nav_text"] as String) + ..customTextButtonEnabledTextInt = parseColor( + json["colors"]["custom_text_button_enabled_text"] as String) + ..customTextButtonDisabledTextInt = parseColor( + json["colors"]["custom_text_button_disabled_text"] as String) + ..switchBGOnInt = parseColor(json["colors"]["switch_bg_on"] as String) + ..switchBGOffInt = parseColor(json["colors"]["switch_bg_off"] as String) + ..switchBGDisabledInt = + parseColor(json["colors"]["switch_bg_disabled"] as String) + ..switchCircleOnInt = + parseColor(json["colors"]["switch_circle_on"] as String) + ..switchCircleOffInt = + parseColor(json["colors"]["switch_circle_off"] as String) + ..switchCircleDisabledInt = + parseColor(json["colors"]["switch_circle_disabled"] as String) + ..stepIndicatorBGCheckInt = + parseColor(json["colors"]["step_indicator_bg_check"] as String) + ..stepIndicatorBGNumberInt = + parseColor(json["colors"]["step_indicator_bg_number"] as String) + ..stepIndicatorBGInactiveInt = + parseColor(json["colors"]["step_indicator_bg_inactive"] as String) + ..stepIndicatorBGLinesInt = + parseColor(json["colors"]["step_indicator_bg_lines"] as String) + ..stepIndicatorBGLinesInactiveInt = parseColor( + json["colors"]["step_indicator_bg_lines_inactive"] as String) + ..stepIndicatorIconTextInt = + parseColor(json["colors"]["step_indicator_icon_text"] as String) + ..stepIndicatorIconNumberInt = + parseColor(json["colors"]["step_indicator_icon_number"] as String) + ..stepIndicatorIconInactiveInt = + parseColor(json["colors"]["step_indicator_icon_inactive"] as String) + ..checkboxBGCheckedInt = + parseColor(json["colors"]["checkbox_bg_checked"] as String) + ..checkboxBorderEmptyInt = + parseColor(json["colors"]["checkbox_border_empty"] as String) + ..checkboxBGDisabledInt = + parseColor(json["colors"]["checkbox_bg_disabled"] as String) + ..checkboxIconCheckedInt = + parseColor(json["colors"]["checkbox_icon_checked"] as String) + ..checkboxIconDisabledInt = + parseColor(json["colors"]["checkbox_icon_disabled"] as String) + ..checkboxTextLabelInt = + parseColor(json["colors"]["checkbox_text_label"] as String) + ..snackBarBackSuccessInt = + parseColor(json["colors"]["snack_bar_back_success"] as String) + ..snackBarBackErrorInt = + parseColor(json["colors"]["snack_bar_back_error"] as String) + ..snackBarBackInfoInt = + parseColor(json["colors"]["snack_bar_back_info"] as String) + ..snackBarTextSuccessInt = + parseColor(json["colors"]["snack_bar_text_success"] as String) + ..snackBarTextErrorInt = + parseColor(json["colors"]["snack_bar_text_error"] as String) + ..snackBarTextInfoInt = + parseColor(json["colors"]["snack_bar_text_info"] as String) + ..bottomNavIconBackInt = + parseColor(json["colors"]["bottom_nav_icon_back"] as String) + ..bottomNavIconIconInt = + parseColor(json["colors"]["bottom_nav_icon_icon"] as String) + ..bottomNavIconIconHighlightedInt = parseColor( + json["colors"]["bottom_nav_icon_icon_highlighted"] as String) + ..topNavIconPrimaryInt = + parseColor(json["colors"]["top_nav_icon_primary"] as String) + ..topNavIconGreenInt = + parseColor(json["colors"]["top_nav_icon_green"] as String) + ..topNavIconYellowInt = + parseColor(json["colors"]["top_nav_icon_yellow"] as String) + ..topNavIconRedInt = + parseColor(json["colors"]["top_nav_icon_red"] as String) + ..settingsIconBackInt = + parseColor(json["colors"]["settings_icon_back"] as String) + ..settingsIconIconInt = + parseColor(json["colors"]["settings_icon_icon"] as String) + ..settingsIconBack2Int = + parseColor(json["colors"]["settings_icon_back_two"] as String) + ..settingsIconElementInt = + parseColor(json["colors"]["settings_icon_element"] as String) + ..textFieldActiveBGInt = + parseColor(json["colors"]["text_field_active_bg"] as String) + ..textFieldDefaultBGInt = + parseColor(json["colors"]["text_field_default_bg"] as String) + ..textFieldErrorBGInt = + parseColor(json["colors"]["text_field_error_bg"] as String) + ..textFieldSuccessBGInt = + parseColor(json["colors"]["text_field_success_bg"] as String) + ..textFieldErrorBorderInt = + parseColor(json["colors"]["text_field_error_border"] as String) + ..textFieldSuccessBorderInt = + parseColor(json["colors"]["text_field_success_border"] as String) + ..textFieldActiveSearchIconLeftInt = parseColor( + json["colors"]["text_field_active_search_icon_left"] as String) + ..textFieldDefaultSearchIconLeftInt = parseColor( + json["colors"]["text_field_default_search_icon_left"] as String) + ..textFieldErrorSearchIconLeftInt = parseColor( + json["colors"]["text_field_error_search_icon_left"] as String) + ..textFieldSuccessSearchIconLeftInt = parseColor( + json["colors"]["text_field_success_search_icon_left"] as String) + ..textFieldActiveTextInt = + parseColor(json["colors"]["text_field_active_text"] as String) + ..textFieldDefaultTextInt = + parseColor(json["colors"]["text_field_default_text"] as String) + ..textFieldErrorTextInt = + parseColor(json["colors"]["text_field_error_text"] as String) + ..textFieldSuccessTextInt = + parseColor(json["colors"]["text_field_success_text"] as String) + ..textFieldActiveLabelInt = + parseColor(json["colors"]["text_field_active_label"] as String) + ..textFieldErrorLabelInt = + parseColor(json["colors"]["text_field_error_label"] as String) + ..textFieldSuccessLabelInt = + parseColor(json["colors"]["text_field_success_label"] as String) + ..textFieldActiveSearchIconRightInt = parseColor( + json["colors"]["text_field_active_search_icon_right"] as String) + ..textFieldDefaultSearchIconRightInt = parseColor( + json["colors"]["text_field_default_search_icon_right"] as String) + ..textFieldErrorSearchIconRightInt = parseColor( + json["colors"]["text_field_error_search_icon_right"] as String) + ..textFieldSuccessSearchIconRightInt = parseColor( + json["colors"]["text_field_success_search_icon_right"] as String) + ..settingsItem2ActiveBGInt = parseColor( + json["colors"]["settings_item_level_two_active_bg"] as String) + ..settingsItem2ActiveTextInt = parseColor( + json["colors"]["settings_item_level_two_active_text"] as String) + ..settingsItem2ActiveSubInt = parseColor( + json["colors"]["settings_item_level_two_active_sub"] as String) + ..radioButtonIconBorderInt = + parseColor(json["colors"]["radio_button_icon_border"] as String) + ..radioButtonIconBorderDisabledInt = parseColor( + json["colors"]["radio_button_icon_border_disabled"] as String) + ..radioButtonBorderEnabledInt = + parseColor(json["colors"]["radio_button_border_enabled"] as String) + ..radioButtonBorderDisabledInt = + parseColor(json["colors"]["radio_button_border_disabled"] as String) + ..radioButtonIconCircleInt = + parseColor(json["colors"]["radio_button_icon_circle"] as String) + ..radioButtonIconEnabledInt = + parseColor(json["colors"]["radio_button_icon_enabled"] as String) + ..radioButtonTextEnabledInt = + parseColor(json["colors"]["radio_button_text_enabled"] as String) + ..radioButtonTextDisabledInt = + parseColor(json["colors"]["radio_button_text_disabled"] as String) + ..radioButtonLabelEnabledInt = + parseColor(json["colors"]["radio_button_label_enabled"] as String) + ..radioButtonLabelDisabledInt = + parseColor(json["colors"]["radio_button_label_disabled"] as String) + ..infoItemBGInt = parseColor(json["colors"]["info_item_bg"] as String) + ..infoItemLabelInt = + parseColor(json["colors"]["info_item_label"] as String) + ..infoItemTextInt = parseColor(json["colors"]["info_item_text"] as String) + ..infoItemIconsInt = + parseColor(json["colors"]["info_item_icons"] as String) + ..popupBGInt = parseColor(json["colors"]["popup_bg"] as String) + ..currencyListItemBGInt = + parseColor(json["colors"]["currency_list_item_bg"] as String) + ..stackWalletBGInt = parseColor(json["colors"]["sw_bg"] as String) + ..stackWalletMidInt = parseColor(json["colors"]["sw_mid"] as String) + ..stackWalletBottomInt = parseColor(json["colors"]["sw_bottom"] as String) + ..bottomNavShadowInt = + parseColor(json["colors"]["bottom_nav_shadow"] as String) + ..splashInt = parseColor(json["colors"]["splash"] as String) + ..highlightInt = parseColor(json["colors"]["highlight"] as String) + ..warningForegroundInt = + parseColor(json["colors"]["warning_foreground"] as String) + ..warningBackgroundInt = + parseColor(json["colors"]["warning_background"] as String) + ..loadingOverlayTextColorInt = + parseColor(json["colors"]["loading_overlay_text_color"] as String) + ..myStackContactIconBGInt = + parseColor(json["colors"]["my_stack_contact_icon_bg"] as String) + ..textConfirmTotalAmountInt = + parseColor(json["colors"]["text_confirm_total_amount"] as String) + ..textSelectedWordTableItemInt = + parseColor(json["colors"]["text_selected_word_table_iterm"] as String) + ..favoriteStarActiveInt = + parseColor(json["colors"]["favorite_star_active"] as String) + ..favoriteStarInactiveInt = + parseColor(json["colors"]["favorite_star_inactive"] as String) + ..rateTypeToggleColorOnInt = + parseColor(json["colors"]["rate_type_toggle_color_on"] as String) + ..rateTypeToggleColorOffInt = + parseColor(json["colors"]["rate_type_toggle_color_off"] as String) + ..rateTypeToggleDesktopColorOnInt = parseColor( + json["colors"]["rate_type_toggle_desktop_color_on"] as String) + ..rateTypeToggleDesktopColorOffInt = parseColor( + json["colors"]["rate_type_toggle_desktop_color_off"] as String) + ..ethTagTextInt = parseColor(json["colors"]["eth_tag_text"] as String) + ..ethTagBGInt = parseColor(json["colors"]["eth_tag_bg"] as String) + ..ethWalletTagTextInt = + parseColor(json["colors"]["eth_wallet_tag_text"] as String) + ..ethWalletTagBGInt = + parseColor(json["colors"]["eth_wallet_tag_bg"] as String) + ..tokenSummaryTextPrimaryInt = + parseColor(json["colors"]["token_summary_text_primary"] as String) + ..tokenSummaryTextSecondaryInt = + parseColor(json["colors"]["token_summary_text_secondary"] as String) + ..tokenSummaryBGInt = + parseColor(json["colors"]["token_summary_bg"] as String) + ..tokenSummaryButtonBGInt = + parseColor(json["colors"]["token_summary_button_bg"] as String) + ..tokenSummaryIconInt = + parseColor(json["colors"]["token_summary_icon"] as String); + } + + /// Grab the int value of the hex color string. + /// 8 char string value expected where the first 2 are opacity + static int parseColor(String colorHex) { + try { + final int colorValue = colorHex.toBigIntFromHex.toInt(); + if (colorValue >= 0 && colorValue <= 0xFFFFFFFF) { + return colorValue; + } else { + throw ArgumentError( + '"$colorHex" and corresponding int ' + 'value "$colorValue" is not a valid color.', + ); + } + } catch (_) { + throw ArgumentError( + '"$colorHex" is not a valid hex number', + ); + } + } + + /// parse coin colors json and fetch color or use default + static Map parseCoinColors(String jsonString) { + final json = jsonDecode(jsonString) as Map; + final map = Map.from(json); + + final Map result = {}; + + for (final coin in Coin.values) { + if (map[coin.name] is String) { + result[coin] = Color( + (map[coin.name] as String).toBigIntFromHex.toInt(), + ); + } else { + result[coin] = kCoinThemeColorDefaults.forCoin(coin); + } + } + + return result; + } +} + +@Embedded(inheritance: false) +class ThemeAssets implements IThemeAssets { + @override + late final String bellNew; + @override + late final String buy; + @override + late final String exchange; + @override + late final String personaIncognito; + @override + late final String personaEasy; + @override + late final String stack; + @override + late final String stackIcon; + @override + late final String receive; + @override + late final String receivePending; + @override + late final String receiveCancelled; + @override + late final String send; + @override + late final String sendPending; + @override + late final String sendCancelled; + @override + late final String themeSelector; + @override + late final String themePreview; + @override + late final String txExchange; + @override + late final String txExchangePending; + @override + late final String txExchangeFailed; + + late final String bitcoin; + late final String litecoin; + late final String bitcoincash; + late final String dogecoin; + late final String epicCash; + late final String ethereum; + late final String firo; + late final String monero; + late final String wownero; + late final String namecoin; + late final String particl; + late final String bitcoinImage; + late final String bitcoincashImage; + late final String dogecoinImage; + late final String epicCashImage; + late final String ethereumImage; + late final String firoImage; + late final String litecoinImage; + late final String moneroImage; + late final String wowneroImage; + late final String namecoinImage; + late final String particlImage; + late final String bitcoinImageSecondary; + late final String bitcoincashImageSecondary; + late final String dogecoinImageSecondary; + late final String epicCashImageSecondary; + late final String ethereumImageSecondary; + late final String firoImageSecondary; + late final String litecoinImageSecondary; + late final String moneroImageSecondary; + late final String wowneroImageSecondary; + late final String namecoinImageSecondary; + late final String particlImageSecondary; + @override + late final String? loadingGif; + @override + late final String? background; + + // todo: add all assets expected in json + + ThemeAssets(); + + factory ThemeAssets.fromJson({ + required Map json, + required String applicationThemesDirectoryPath, + required String themeId, + }) { + return ThemeAssets() + ..bellNew = + "$applicationThemesDirectoryPath/$themeId/assets/${json["bell_new"] as String}" + ..buy = + "$applicationThemesDirectoryPath/$themeId/assets/${json["buy"] as String}" + ..exchange = + "$applicationThemesDirectoryPath/$themeId/assets/${json["exchange"] as String}" + ..personaIncognito = + "$applicationThemesDirectoryPath/$themeId/assets/${json["persona_incognito"] as String}" + ..personaEasy = + "$applicationThemesDirectoryPath/$themeId/assets/${json["persona_easy"] as String}" + ..stack = + "$applicationThemesDirectoryPath/$themeId/assets/${json["stack"] as String}" + ..stackIcon = + "$applicationThemesDirectoryPath/$themeId/assets/${json["stack_icon"] as String}" + ..receive = + "$applicationThemesDirectoryPath/$themeId/assets/${json["receive"] as String}" + ..receivePending = + "$applicationThemesDirectoryPath/$themeId/assets/${json["receive_pending"] as String}" + ..receiveCancelled = + "$applicationThemesDirectoryPath/$themeId/assets/${json["receive_cancelled"] as String}" + ..send = + "$applicationThemesDirectoryPath/$themeId/assets/${json["send"] as String}" + ..sendPending = + "$applicationThemesDirectoryPath/$themeId/assets/${json["send_pending"] as String}" + ..sendCancelled = + "$applicationThemesDirectoryPath/$themeId/assets/${json["send_cancelled"] as String}" + ..themeSelector = + "$applicationThemesDirectoryPath/$themeId/assets/${json["theme_selector"] as String}" + ..themePreview = + "$applicationThemesDirectoryPath/$themeId/assets/${json["theme_preview"] as String}" + ..txExchange = + "$applicationThemesDirectoryPath/$themeId/assets/${json["tx_exchange"] as String}" + ..txExchangePending = + "$applicationThemesDirectoryPath/$themeId/assets/${json["tx_exchange_pending"] as String}" + ..txExchangeFailed = + "$applicationThemesDirectoryPath/$themeId/assets/${json["tx_exchange_failed"] as String}" + ..bitcoin = + "$applicationThemesDirectoryPath/$themeId/assets/${json["bitcoin"] as String}" + ..litecoin = + "$applicationThemesDirectoryPath/$themeId/assets/${json["litecoin"] as String}" + ..bitcoincash = + "$applicationThemesDirectoryPath/$themeId/assets/${json["bitcoincash"] as String}" + ..dogecoin = + "$applicationThemesDirectoryPath/$themeId/assets/${json["dogecoin"] as String}" + ..epicCash = + "$applicationThemesDirectoryPath/$themeId/assets/${json["epicCash"] as String}" + ..ethereum = + "$applicationThemesDirectoryPath/$themeId/assets/${json["ethereum"] as String}" + ..firo = + "$applicationThemesDirectoryPath/$themeId/assets/${json["firo"] as String}" + ..monero = + "$applicationThemesDirectoryPath/$themeId/assets/${json["monero"] as String}" + ..wownero = + "$applicationThemesDirectoryPath/$themeId/assets/${json["wownero"] as String}" + ..namecoin = + "$applicationThemesDirectoryPath/$themeId/assets/${json["namecoin"] as String}" + ..particl = + "$applicationThemesDirectoryPath/$themeId/assets/${json["particl"] as String}" + ..bitcoinImage = + "$applicationThemesDirectoryPath/$themeId/assets/${json["bitcoin_image"] as String}" + ..bitcoincashImage = + "$applicationThemesDirectoryPath/$themeId/assets/${json["bitcoincash_image"] as String}" + ..dogecoinImage = + "$applicationThemesDirectoryPath/$themeId/assets/${json["dogecoin_image"] as String}" + ..epicCashImage = + "$applicationThemesDirectoryPath/$themeId/assets/${json["epicCash_image"] as String}" + ..ethereumImage = + "$applicationThemesDirectoryPath/$themeId/assets/${json["ethereum_image"] as String}" + ..firoImage = + "$applicationThemesDirectoryPath/$themeId/assets/${json["firo_image"] as String}" + ..litecoinImage = + "$applicationThemesDirectoryPath/$themeId/assets/${json["litecoin_image"] as String}" + ..moneroImage = + "$applicationThemesDirectoryPath/$themeId/assets/${json["monero_image"] as String}" + ..wowneroImage = + "$applicationThemesDirectoryPath/$themeId/assets/${json["wownero_image"] as String}" + ..namecoinImage = + "$applicationThemesDirectoryPath/$themeId/assets/${json["namecoin_image"] as String}" + ..particlImage = + "$applicationThemesDirectoryPath/$themeId/assets/${json["particl_image"] as String}" + ..bitcoinImageSecondary = + "$applicationThemesDirectoryPath/$themeId/assets/${json["bitcoin_image_secondary"] as String}" + ..bitcoincashImageSecondary = + "$applicationThemesDirectoryPath/$themeId/assets/${json["bitcoincash_image_secondary"] as String}" + ..dogecoinImageSecondary = + "$applicationThemesDirectoryPath/$themeId/assets/${json["dogecoin_image_secondary"] as String}" + ..epicCashImageSecondary = + "$applicationThemesDirectoryPath/$themeId/assets/${json["epicCash_image_secondary"] as String}" + ..ethereumImageSecondary = + "$applicationThemesDirectoryPath/$themeId/assets/${json["ethereum_image_secondary"] as String}" + ..firoImageSecondary = + "$applicationThemesDirectoryPath/$themeId/assets/${json["firo_image_secondary"] as String}" + ..litecoinImageSecondary = + "$applicationThemesDirectoryPath/$themeId/assets/${json["litecoin_image_secondary"] as String}" + ..moneroImageSecondary = + "$applicationThemesDirectoryPath/$themeId/assets/${json["monero_image_secondary"] as String}" + ..wowneroImageSecondary = + "$applicationThemesDirectoryPath/$themeId/assets/${json["wownero_image_secondary"] as String}" + ..namecoinImageSecondary = + "$applicationThemesDirectoryPath/$themeId/assets/${json["namecoin_image_secondary"] as String}" + ..particlImageSecondary = + "$applicationThemesDirectoryPath/$themeId/assets/${json["particl_image_secondary"] as String}" + ..loadingGif = json["loading_gif"] is String + ? "$applicationThemesDirectoryPath/$themeId/assets/${json["loading_gif"] as String}" + : null + ..background = json["background"] is String + ? "$applicationThemesDirectoryPath/$themeId/assets/${json["background"] as String}" + : null; + } +} + +@Embedded(inheritance: false) +class ThemeAssetsV2 implements IThemeAssets { + @override + late final String bellNew; + @override + late final String buy; + @override + late final String exchange; + @override + late final String personaIncognito; + @override + late final String personaEasy; + @override + late final String stack; + @override + late final String stackIcon; + @override + late final String receive; + @override + late final String receivePending; + @override + late final String receiveCancelled; + @override + late final String send; + @override + late final String sendPending; + @override + late final String sendCancelled; + @override + late final String themeSelector; + @override + late final String themePreview; + @override + late final String txExchange; + @override + late final String txExchangePending; + @override + late final String txExchangeFailed; + @override + late final String? loadingGif; + @override + late final String? background; + + late final String coinPlaceholder; + + @ignore + Map get coinIcons => _coinIcons ??= parseCoinAssetsString( + coinIconsString, + placeHolder: coinPlaceholder, + ); + @ignore + Map? _coinIcons; + late final String coinIconsString; + + @ignore + Map get coinImages => _coinImages ??= parseCoinAssetsString( + coinImagesString, + placeHolder: coinPlaceholder, + ); + @ignore + Map? _coinImages; + late final String coinImagesString; + + @ignore + Map get coinSecondaryImages => + _coinSecondaryImages ??= parseCoinAssetsString( + coinSecondaryImagesString, + placeHolder: coinPlaceholder, + ); + @ignore + Map? _coinSecondaryImages; + late final String coinSecondaryImagesString; + + ThemeAssetsV2(); + + factory ThemeAssetsV2.fromJson({ + required Map json, + required String applicationThemesDirectoryPath, + required String themeId, + }) { + return ThemeAssetsV2() + ..bellNew = + "$applicationThemesDirectoryPath/$themeId/assets/${json["bell_new"] as String}" + ..buy = + "$applicationThemesDirectoryPath/$themeId/assets/${json["buy"] as String}" + ..exchange = + "$applicationThemesDirectoryPath/$themeId/assets/${json["exchange"] as String}" + ..personaIncognito = + "$applicationThemesDirectoryPath/$themeId/assets/${json["persona_incognito"] as String}" + ..personaEasy = + "$applicationThemesDirectoryPath/$themeId/assets/${json["persona_easy"] as String}" + ..stack = + "$applicationThemesDirectoryPath/$themeId/assets/${json["stack"] as String}" + ..stackIcon = + "$applicationThemesDirectoryPath/$themeId/assets/${json["stack_icon"] as String}" + ..receive = + "$applicationThemesDirectoryPath/$themeId/assets/${json["receive"] as String}" + ..receivePending = + "$applicationThemesDirectoryPath/$themeId/assets/${json["receive_pending"] as String}" + ..receiveCancelled = + "$applicationThemesDirectoryPath/$themeId/assets/${json["receive_cancelled"] as String}" + ..send = + "$applicationThemesDirectoryPath/$themeId/assets/${json["send"] as String}" + ..sendPending = + "$applicationThemesDirectoryPath/$themeId/assets/${json["send_pending"] as String}" + ..sendCancelled = + "$applicationThemesDirectoryPath/$themeId/assets/${json["send_cancelled"] as String}" + ..themeSelector = + "$applicationThemesDirectoryPath/$themeId/assets/${json["theme_selector"] as String}" + ..themePreview = + "$applicationThemesDirectoryPath/$themeId/assets/${json["theme_preview"] as String}" + ..txExchange = + "$applicationThemesDirectoryPath/$themeId/assets/${json["tx_exchange"] as String}" + ..txExchangePending = + "$applicationThemesDirectoryPath/$themeId/assets/${json["tx_exchange_pending"] as String}" + ..txExchangeFailed = + "$applicationThemesDirectoryPath/$themeId/assets/${json["tx_exchange_failed"] as String}" + ..coinPlaceholder = + "$applicationThemesDirectoryPath/$themeId/assets/${json["coin_placeholder"] as String}" + ..coinIconsString = createCoinAssetsString( + "$applicationThemesDirectoryPath/$themeId/assets", + Map.from(json["coins"]["icons"] as Map), + ) + ..coinImagesString = createCoinAssetsString( + "$applicationThemesDirectoryPath/$themeId/assets", + Map.from(json["coins"]["images"] as Map), + ) + ..coinSecondaryImagesString = createCoinAssetsString( + "$applicationThemesDirectoryPath/$themeId/assets", + Map.from(json["coins"]["secondaries"] as Map), + ) + ..loadingGif = json["loading_gif"] is String + ? "$applicationThemesDirectoryPath/$themeId/assets/${json["loading_gif"] as String}" + : null + ..background = json["background"] is String + ? "$applicationThemesDirectoryPath/$themeId/assets/${json["background"] as String}" + : null; + } + + static String createCoinAssetsString(String path, Map json) { + final Map map = {}; + for (final entry in json.entries) { + map[entry.key] = "$path/${entry.value as String}"; + } + return jsonEncode(map); + } + + static Map parseCoinAssetsString( + String jsonString, { + required String placeHolder, + }) { + final json = jsonDecode(jsonString) as Map; + final map = Map.from(json); + + final Map result = {}; + + for (final coin in Coin.values) { + result[coin] = map[coin.name] as String? ?? placeHolder; + } + + return result; + } +} + +abstract class IThemeAssets { + String get bellNew; + String get buy; + String get exchange; + String get personaIncognito; + String get personaEasy; + String get stack; + String get stackIcon; + String get receive; + String get receivePending; + String get receiveCancelled; + String get send; + String get sendPending; + String get sendCancelled; + String get themeSelector; + String get themePreview; + String get txExchange; + String get txExchangePending; + String get txExchangeFailed; + + String? get loadingGif; + String? get background; +} diff --git a/lib/models/isar/stack_theme.g.dart b/lib/models/isar/stack_theme.g.dart new file mode 100644 index 000000000..ad6f905bd --- /dev/null +++ b/lib/models/isar/stack_theme.g.dart @@ -0,0 +1,29378 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'stack_theme.dart'; + +// ************************************************************************** +// IsarCollectionGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters + +extension GetStackThemeCollection on Isar { + IsarCollection get stackThemes => this.collection(); +} + +const StackThemeSchema = CollectionSchema( + name: r'StackTheme', + id: 6699740637547692013, + properties: { + r'accentColorBlueInt': PropertySchema( + id: 0, + name: r'accentColorBlueInt', + type: IsarType.long, + ), + r'accentColorDarkInt': PropertySchema( + id: 1, + name: r'accentColorDarkInt', + type: IsarType.long, + ), + r'accentColorGreenInt': PropertySchema( + id: 2, + name: r'accentColorGreenInt', + type: IsarType.long, + ), + r'accentColorOrangeInt': PropertySchema( + id: 3, + name: r'accentColorOrangeInt', + type: IsarType.long, + ), + r'accentColorRedInt': PropertySchema( + id: 4, + name: r'accentColorRedInt', + type: IsarType.long, + ), + r'accentColorYellowInt': PropertySchema( + id: 5, + name: r'accentColorYellowInt', + type: IsarType.long, + ), + r'assets': PropertySchema( + id: 6, + name: r'assets', + type: IsarType.object, + target: r'ThemeAssets', + ), + r'assetsV2': PropertySchema( + id: 7, + name: r'assetsV2', + type: IsarType.object, + target: r'ThemeAssetsV2', + ), + r'backgroundAppBarInt': PropertySchema( + id: 8, + name: r'backgroundAppBarInt', + type: IsarType.long, + ), + r'backgroundInt': PropertySchema( + id: 9, + name: r'backgroundInt', + type: IsarType.long, + ), + r'bottomNavBackInt': PropertySchema( + id: 10, + name: r'bottomNavBackInt', + type: IsarType.long, + ), + r'bottomNavIconBackInt': PropertySchema( + id: 11, + name: r'bottomNavIconBackInt', + type: IsarType.long, + ), + r'bottomNavIconIconHighlightedInt': PropertySchema( + id: 12, + name: r'bottomNavIconIconHighlightedInt', + type: IsarType.long, + ), + r'bottomNavIconIconInt': PropertySchema( + id: 13, + name: r'bottomNavIconIconInt', + type: IsarType.long, + ), + r'bottomNavShadowInt': PropertySchema( + id: 14, + name: r'bottomNavShadowInt', + type: IsarType.long, + ), + r'bottomNavTextInt': PropertySchema( + id: 15, + name: r'bottomNavTextInt', + type: IsarType.long, + ), + r'brightnessString': PropertySchema( + id: 16, + name: r'brightnessString', + type: IsarType.string, + ), + r'buttonBackBorderDisabledInt': PropertySchema( + id: 17, + name: r'buttonBackBorderDisabledInt', + type: IsarType.long, + ), + r'buttonBackBorderInt': PropertySchema( + id: 18, + name: r'buttonBackBorderInt', + type: IsarType.long, + ), + r'buttonBackBorderSecondaryDisabledInt': PropertySchema( + id: 19, + name: r'buttonBackBorderSecondaryDisabledInt', + type: IsarType.long, + ), + r'buttonBackBorderSecondaryInt': PropertySchema( + id: 20, + name: r'buttonBackBorderSecondaryInt', + type: IsarType.long, + ), + r'buttonBackPrimaryDisabledInt': PropertySchema( + id: 21, + name: r'buttonBackPrimaryDisabledInt', + type: IsarType.long, + ), + r'buttonBackPrimaryInt': PropertySchema( + id: 22, + name: r'buttonBackPrimaryInt', + type: IsarType.long, + ), + r'buttonBackSecondaryDisabledInt': PropertySchema( + id: 23, + name: r'buttonBackSecondaryDisabledInt', + type: IsarType.long, + ), + r'buttonBackSecondaryInt': PropertySchema( + id: 24, + name: r'buttonBackSecondaryInt', + type: IsarType.long, + ), + r'buttonTextBorderInt': PropertySchema( + id: 25, + name: r'buttonTextBorderInt', + type: IsarType.long, + ), + r'buttonTextBorderlessDisabledInt': PropertySchema( + id: 26, + name: r'buttonTextBorderlessDisabledInt', + type: IsarType.long, + ), + r'buttonTextBorderlessInt': PropertySchema( + id: 27, + name: r'buttonTextBorderlessInt', + type: IsarType.long, + ), + r'buttonTextDisabledInt': PropertySchema( + id: 28, + name: r'buttonTextDisabledInt', + type: IsarType.long, + ), + r'buttonTextPrimaryDisabledInt': PropertySchema( + id: 29, + name: r'buttonTextPrimaryDisabledInt', + type: IsarType.long, + ), + r'buttonTextPrimaryInt': PropertySchema( + id: 30, + name: r'buttonTextPrimaryInt', + type: IsarType.long, + ), + r'buttonTextSecondaryDisabledInt': PropertySchema( + id: 31, + name: r'buttonTextSecondaryDisabledInt', + type: IsarType.long, + ), + r'buttonTextSecondaryInt': PropertySchema( + id: 32, + name: r'buttonTextSecondaryInt', + type: IsarType.long, + ), + r'checkboxBGCheckedInt': PropertySchema( + id: 33, + name: r'checkboxBGCheckedInt', + type: IsarType.long, + ), + r'checkboxBGDisabledInt': PropertySchema( + id: 34, + name: r'checkboxBGDisabledInt', + type: IsarType.long, + ), + r'checkboxBorderEmptyInt': PropertySchema( + id: 35, + name: r'checkboxBorderEmptyInt', + type: IsarType.long, + ), + r'checkboxIconCheckedInt': PropertySchema( + id: 36, + name: r'checkboxIconCheckedInt', + type: IsarType.long, + ), + r'checkboxIconDisabledInt': PropertySchema( + id: 37, + name: r'checkboxIconDisabledInt', + type: IsarType.long, + ), + r'checkboxTextLabelInt': PropertySchema( + id: 38, + name: r'checkboxTextLabelInt', + type: IsarType.long, + ), + r'coinColorsJsonString': PropertySchema( + id: 39, + name: r'coinColorsJsonString', + type: IsarType.string, + ), + r'currencyListItemBGInt': PropertySchema( + id: 40, + name: r'currencyListItemBGInt', + type: IsarType.long, + ), + r'customTextButtonDisabledTextInt': PropertySchema( + id: 41, + name: r'customTextButtonDisabledTextInt', + type: IsarType.long, + ), + r'customTextButtonEnabledTextInt': PropertySchema( + id: 42, + name: r'customTextButtonEnabledTextInt', + type: IsarType.long, + ), + r'ethTagBGInt': PropertySchema( + id: 43, + name: r'ethTagBGInt', + type: IsarType.long, + ), + r'ethTagTextInt': PropertySchema( + id: 44, + name: r'ethTagTextInt', + type: IsarType.long, + ), + r'ethWalletTagBGInt': PropertySchema( + id: 45, + name: r'ethWalletTagBGInt', + type: IsarType.long, + ), + r'ethWalletTagTextInt': PropertySchema( + id: 46, + name: r'ethWalletTagTextInt', + type: IsarType.long, + ), + r'favoriteStarActiveInt': PropertySchema( + id: 47, + name: r'favoriteStarActiveInt', + type: IsarType.long, + ), + r'favoriteStarInactiveInt': PropertySchema( + id: 48, + name: r'favoriteStarInactiveInt', + type: IsarType.long, + ), + r'gradientBackgroundString': PropertySchema( + id: 49, + name: r'gradientBackgroundString', + type: IsarType.string, + ), + r'highlightInt': PropertySchema( + id: 50, + name: r'highlightInt', + type: IsarType.long, + ), + r'homeViewButtonBarBoxShadowString': PropertySchema( + id: 51, + name: r'homeViewButtonBarBoxShadowString', + type: IsarType.string, + ), + r'infoItemBGInt': PropertySchema( + id: 52, + name: r'infoItemBGInt', + type: IsarType.long, + ), + r'infoItemIconsInt': PropertySchema( + id: 53, + name: r'infoItemIconsInt', + type: IsarType.long, + ), + r'infoItemLabelInt': PropertySchema( + id: 54, + name: r'infoItemLabelInt', + type: IsarType.long, + ), + r'infoItemTextInt': PropertySchema( + id: 55, + name: r'infoItemTextInt', + type: IsarType.long, + ), + r'loadingOverlayTextColorInt': PropertySchema( + id: 56, + name: r'loadingOverlayTextColorInt', + type: IsarType.long, + ), + r'myStackContactIconBGInt': PropertySchema( + id: 57, + name: r'myStackContactIconBGInt', + type: IsarType.long, + ), + r'name': PropertySchema( + id: 58, + name: r'name', + type: IsarType.string, + ), + r'numberBackDefaultInt': PropertySchema( + id: 59, + name: r'numberBackDefaultInt', + type: IsarType.long, + ), + r'numberTextDefaultInt': PropertySchema( + id: 60, + name: r'numberTextDefaultInt', + type: IsarType.long, + ), + r'numpadBackDefaultInt': PropertySchema( + id: 61, + name: r'numpadBackDefaultInt', + type: IsarType.long, + ), + r'numpadTextDefaultInt': PropertySchema( + id: 62, + name: r'numpadTextDefaultInt', + type: IsarType.long, + ), + r'overlayInt': PropertySchema( + id: 63, + name: r'overlayInt', + type: IsarType.long, + ), + r'popupBGInt': PropertySchema( + id: 64, + name: r'popupBGInt', + type: IsarType.long, + ), + r'radioButtonBorderDisabledInt': PropertySchema( + id: 65, + name: r'radioButtonBorderDisabledInt', + type: IsarType.long, + ), + r'radioButtonBorderEnabledInt': PropertySchema( + id: 66, + name: r'radioButtonBorderEnabledInt', + type: IsarType.long, + ), + r'radioButtonIconBorderDisabledInt': PropertySchema( + id: 67, + name: r'radioButtonIconBorderDisabledInt', + type: IsarType.long, + ), + r'radioButtonIconBorderInt': PropertySchema( + id: 68, + name: r'radioButtonIconBorderInt', + type: IsarType.long, + ), + r'radioButtonIconCircleInt': PropertySchema( + id: 69, + name: r'radioButtonIconCircleInt', + type: IsarType.long, + ), + r'radioButtonIconEnabledInt': PropertySchema( + id: 70, + name: r'radioButtonIconEnabledInt', + type: IsarType.long, + ), + r'radioButtonLabelDisabledInt': PropertySchema( + id: 71, + name: r'radioButtonLabelDisabledInt', + type: IsarType.long, + ), + r'radioButtonLabelEnabledInt': PropertySchema( + id: 72, + name: r'radioButtonLabelEnabledInt', + type: IsarType.long, + ), + r'radioButtonTextDisabledInt': PropertySchema( + id: 73, + name: r'radioButtonTextDisabledInt', + type: IsarType.long, + ), + r'radioButtonTextEnabledInt': PropertySchema( + id: 74, + name: r'radioButtonTextEnabledInt', + type: IsarType.long, + ), + r'rateTypeToggleColorOffInt': PropertySchema( + id: 75, + name: r'rateTypeToggleColorOffInt', + type: IsarType.long, + ), + r'rateTypeToggleColorOnInt': PropertySchema( + id: 76, + name: r'rateTypeToggleColorOnInt', + type: IsarType.long, + ), + r'rateTypeToggleDesktopColorOffInt': PropertySchema( + id: 77, + name: r'rateTypeToggleDesktopColorOffInt', + type: IsarType.long, + ), + r'rateTypeToggleDesktopColorOnInt': PropertySchema( + id: 78, + name: r'rateTypeToggleDesktopColorOnInt', + type: IsarType.long, + ), + r'settingsIconBack2Int': PropertySchema( + id: 79, + name: r'settingsIconBack2Int', + type: IsarType.long, + ), + r'settingsIconBackInt': PropertySchema( + id: 80, + name: r'settingsIconBackInt', + type: IsarType.long, + ), + r'settingsIconElementInt': PropertySchema( + id: 81, + name: r'settingsIconElementInt', + type: IsarType.long, + ), + r'settingsIconIconInt': PropertySchema( + id: 82, + name: r'settingsIconIconInt', + type: IsarType.long, + ), + r'settingsItem2ActiveBGInt': PropertySchema( + id: 83, + name: r'settingsItem2ActiveBGInt', + type: IsarType.long, + ), + r'settingsItem2ActiveSubInt': PropertySchema( + id: 84, + name: r'settingsItem2ActiveSubInt', + type: IsarType.long, + ), + r'settingsItem2ActiveTextInt': PropertySchema( + id: 85, + name: r'settingsItem2ActiveTextInt', + type: IsarType.long, + ), + r'shadowInt': PropertySchema( + id: 86, + name: r'shadowInt', + type: IsarType.long, + ), + r'snackBarBackErrorInt': PropertySchema( + id: 87, + name: r'snackBarBackErrorInt', + type: IsarType.long, + ), + r'snackBarBackInfoInt': PropertySchema( + id: 88, + name: r'snackBarBackInfoInt', + type: IsarType.long, + ), + r'snackBarBackSuccessInt': PropertySchema( + id: 89, + name: r'snackBarBackSuccessInt', + type: IsarType.long, + ), + r'snackBarTextErrorInt': PropertySchema( + id: 90, + name: r'snackBarTextErrorInt', + type: IsarType.long, + ), + r'snackBarTextInfoInt': PropertySchema( + id: 91, + name: r'snackBarTextInfoInt', + type: IsarType.long, + ), + r'snackBarTextSuccessInt': PropertySchema( + id: 92, + name: r'snackBarTextSuccessInt', + type: IsarType.long, + ), + r'splashInt': PropertySchema( + id: 93, + name: r'splashInt', + type: IsarType.long, + ), + r'stackWalletBGInt': PropertySchema( + id: 94, + name: r'stackWalletBGInt', + type: IsarType.long, + ), + r'stackWalletBottomInt': PropertySchema( + id: 95, + name: r'stackWalletBottomInt', + type: IsarType.long, + ), + r'stackWalletMidInt': PropertySchema( + id: 96, + name: r'stackWalletMidInt', + type: IsarType.long, + ), + r'standardBoxShadowString': PropertySchema( + id: 97, + name: r'standardBoxShadowString', + type: IsarType.string, + ), + r'stepIndicatorBGCheckInt': PropertySchema( + id: 98, + name: r'stepIndicatorBGCheckInt', + type: IsarType.long, + ), + r'stepIndicatorBGInactiveInt': PropertySchema( + id: 99, + name: r'stepIndicatorBGInactiveInt', + type: IsarType.long, + ), + r'stepIndicatorBGLinesInactiveInt': PropertySchema( + id: 100, + name: r'stepIndicatorBGLinesInactiveInt', + type: IsarType.long, + ), + r'stepIndicatorBGLinesInt': PropertySchema( + id: 101, + name: r'stepIndicatorBGLinesInt', + type: IsarType.long, + ), + r'stepIndicatorBGNumberInt': PropertySchema( + id: 102, + name: r'stepIndicatorBGNumberInt', + type: IsarType.long, + ), + r'stepIndicatorIconInactiveInt': PropertySchema( + id: 103, + name: r'stepIndicatorIconInactiveInt', + type: IsarType.long, + ), + r'stepIndicatorIconNumberInt': PropertySchema( + id: 104, + name: r'stepIndicatorIconNumberInt', + type: IsarType.long, + ), + r'stepIndicatorIconTextInt': PropertySchema( + id: 105, + name: r'stepIndicatorIconTextInt', + type: IsarType.long, + ), + r'switchBGDisabledInt': PropertySchema( + id: 106, + name: r'switchBGDisabledInt', + type: IsarType.long, + ), + r'switchBGOffInt': PropertySchema( + id: 107, + name: r'switchBGOffInt', + type: IsarType.long, + ), + r'switchBGOnInt': PropertySchema( + id: 108, + name: r'switchBGOnInt', + type: IsarType.long, + ), + r'switchCircleDisabledInt': PropertySchema( + id: 109, + name: r'switchCircleDisabledInt', + type: IsarType.long, + ), + r'switchCircleOffInt': PropertySchema( + id: 110, + name: r'switchCircleOffInt', + type: IsarType.long, + ), + r'switchCircleOnInt': PropertySchema( + id: 111, + name: r'switchCircleOnInt', + type: IsarType.long, + ), + r'textConfirmTotalAmountInt': PropertySchema( + id: 112, + name: r'textConfirmTotalAmountInt', + type: IsarType.long, + ), + r'textDark2Int': PropertySchema( + id: 113, + name: r'textDark2Int', + type: IsarType.long, + ), + r'textDark3Int': PropertySchema( + id: 114, + name: r'textDark3Int', + type: IsarType.long, + ), + r'textDarkInt': PropertySchema( + id: 115, + name: r'textDarkInt', + type: IsarType.long, + ), + r'textErrorInt': PropertySchema( + id: 116, + name: r'textErrorInt', + type: IsarType.long, + ), + r'textFavoriteCardInt': PropertySchema( + id: 117, + name: r'textFavoriteCardInt', + type: IsarType.long, + ), + r'textFieldActiveBGInt': PropertySchema( + id: 118, + name: r'textFieldActiveBGInt', + type: IsarType.long, + ), + r'textFieldActiveLabelInt': PropertySchema( + id: 119, + name: r'textFieldActiveLabelInt', + type: IsarType.long, + ), + r'textFieldActiveSearchIconLeftInt': PropertySchema( + id: 120, + name: r'textFieldActiveSearchIconLeftInt', + type: IsarType.long, + ), + r'textFieldActiveSearchIconRightInt': PropertySchema( + id: 121, + name: r'textFieldActiveSearchIconRightInt', + type: IsarType.long, + ), + r'textFieldActiveTextInt': PropertySchema( + id: 122, + name: r'textFieldActiveTextInt', + type: IsarType.long, + ), + r'textFieldDefaultBGInt': PropertySchema( + id: 123, + name: r'textFieldDefaultBGInt', + type: IsarType.long, + ), + r'textFieldDefaultSearchIconLeftInt': PropertySchema( + id: 124, + name: r'textFieldDefaultSearchIconLeftInt', + type: IsarType.long, + ), + r'textFieldDefaultSearchIconRightInt': PropertySchema( + id: 125, + name: r'textFieldDefaultSearchIconRightInt', + type: IsarType.long, + ), + r'textFieldDefaultTextInt': PropertySchema( + id: 126, + name: r'textFieldDefaultTextInt', + type: IsarType.long, + ), + r'textFieldErrorBGInt': PropertySchema( + id: 127, + name: r'textFieldErrorBGInt', + type: IsarType.long, + ), + r'textFieldErrorBorderInt': PropertySchema( + id: 128, + name: r'textFieldErrorBorderInt', + type: IsarType.long, + ), + r'textFieldErrorLabelInt': PropertySchema( + id: 129, + name: r'textFieldErrorLabelInt', + type: IsarType.long, + ), + r'textFieldErrorSearchIconLeftInt': PropertySchema( + id: 130, + name: r'textFieldErrorSearchIconLeftInt', + type: IsarType.long, + ), + r'textFieldErrorSearchIconRightInt': PropertySchema( + id: 131, + name: r'textFieldErrorSearchIconRightInt', + type: IsarType.long, + ), + r'textFieldErrorTextInt': PropertySchema( + id: 132, + name: r'textFieldErrorTextInt', + type: IsarType.long, + ), + r'textFieldSuccessBGInt': PropertySchema( + id: 133, + name: r'textFieldSuccessBGInt', + type: IsarType.long, + ), + r'textFieldSuccessBorderInt': PropertySchema( + id: 134, + name: r'textFieldSuccessBorderInt', + type: IsarType.long, + ), + r'textFieldSuccessLabelInt': PropertySchema( + id: 135, + name: r'textFieldSuccessLabelInt', + type: IsarType.long, + ), + r'textFieldSuccessSearchIconLeftInt': PropertySchema( + id: 136, + name: r'textFieldSuccessSearchIconLeftInt', + type: IsarType.long, + ), + r'textFieldSuccessSearchIconRightInt': PropertySchema( + id: 137, + name: r'textFieldSuccessSearchIconRightInt', + type: IsarType.long, + ), + r'textFieldSuccessTextInt': PropertySchema( + id: 138, + name: r'textFieldSuccessTextInt', + type: IsarType.long, + ), + r'textRestoreInt': PropertySchema( + id: 139, + name: r'textRestoreInt', + type: IsarType.long, + ), + r'textSelectedWordTableItemInt': PropertySchema( + id: 140, + name: r'textSelectedWordTableItemInt', + type: IsarType.long, + ), + r'textSubtitle1Int': PropertySchema( + id: 141, + name: r'textSubtitle1Int', + type: IsarType.long, + ), + r'textSubtitle2Int': PropertySchema( + id: 142, + name: r'textSubtitle2Int', + type: IsarType.long, + ), + r'textSubtitle3Int': PropertySchema( + id: 143, + name: r'textSubtitle3Int', + type: IsarType.long, + ), + r'textSubtitle4Int': PropertySchema( + id: 144, + name: r'textSubtitle4Int', + type: IsarType.long, + ), + r'textSubtitle5Int': PropertySchema( + id: 145, + name: r'textSubtitle5Int', + type: IsarType.long, + ), + r'textSubtitle6Int': PropertySchema( + id: 146, + name: r'textSubtitle6Int', + type: IsarType.long, + ), + r'textWhiteInt': PropertySchema( + id: 147, + name: r'textWhiteInt', + type: IsarType.long, + ), + r'themeId': PropertySchema( + id: 148, + name: r'themeId', + type: IsarType.string, + ), + r'tokenSummaryBGInt': PropertySchema( + id: 149, + name: r'tokenSummaryBGInt', + type: IsarType.long, + ), + r'tokenSummaryButtonBGInt': PropertySchema( + id: 150, + name: r'tokenSummaryButtonBGInt', + type: IsarType.long, + ), + r'tokenSummaryIconInt': PropertySchema( + id: 151, + name: r'tokenSummaryIconInt', + type: IsarType.long, + ), + r'tokenSummaryTextPrimaryInt': PropertySchema( + id: 152, + name: r'tokenSummaryTextPrimaryInt', + type: IsarType.long, + ), + r'tokenSummaryTextSecondaryInt': PropertySchema( + id: 153, + name: r'tokenSummaryTextSecondaryInt', + type: IsarType.long, + ), + r'topNavIconGreenInt': PropertySchema( + id: 154, + name: r'topNavIconGreenInt', + type: IsarType.long, + ), + r'topNavIconPrimaryInt': PropertySchema( + id: 155, + name: r'topNavIconPrimaryInt', + type: IsarType.long, + ), + r'topNavIconRedInt': PropertySchema( + id: 156, + name: r'topNavIconRedInt', + type: IsarType.long, + ), + r'topNavIconYellowInt': PropertySchema( + id: 157, + name: r'topNavIconYellowInt', + type: IsarType.long, + ), + r'version': PropertySchema( + id: 158, + name: r'version', + type: IsarType.long, + ), + r'warningBackgroundInt': PropertySchema( + id: 159, + name: r'warningBackgroundInt', + type: IsarType.long, + ), + r'warningForegroundInt': PropertySchema( + id: 160, + name: r'warningForegroundInt', + type: IsarType.long, + ) + }, + estimateSize: _stackThemeEstimateSize, + serialize: _stackThemeSerialize, + deserialize: _stackThemeDeserialize, + deserializeProp: _stackThemeDeserializeProp, + idName: r'id', + indexes: { + r'themeId': IndexSchema( + id: 2474229918109385772, + name: r'themeId', + unique: true, + replace: true, + properties: [ + IndexPropertySchema( + name: r'themeId', + type: IndexType.hash, + caseSensitive: true, + ) + ], + ) + }, + links: {}, + embeddedSchemas: { + r'ThemeAssets': ThemeAssetsSchema, + r'ThemeAssetsV2': ThemeAssetsV2Schema + }, + getId: _stackThemeGetId, + getLinks: _stackThemeGetLinks, + attach: _stackThemeAttach, + version: '3.0.5', +); + +int _stackThemeEstimateSize( + StackTheme object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + { + final value = object.assetsV1; + if (value != null) { + bytesCount += 3 + + ThemeAssetsSchema.estimateSize( + value, allOffsets[ThemeAssets]!, allOffsets); + } + } + { + final value = object.assetsV2; + if (value != null) { + bytesCount += 3 + + ThemeAssetsV2Schema.estimateSize( + value, allOffsets[ThemeAssetsV2]!, allOffsets); + } + } + bytesCount += 3 + object.brightnessString.length * 3; + bytesCount += 3 + object.coinColorsJsonString.length * 3; + { + final value = object.gradientBackgroundString; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + { + final value = object.homeViewButtonBarBoxShadowString; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + bytesCount += 3 + object.name.length * 3; + bytesCount += 3 + object.standardBoxShadowString.length * 3; + bytesCount += 3 + object.themeId.length * 3; + return bytesCount; +} + +void _stackThemeSerialize( + StackTheme object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeLong(offsets[0], object.accentColorBlueInt); + writer.writeLong(offsets[1], object.accentColorDarkInt); + writer.writeLong(offsets[2], object.accentColorGreenInt); + writer.writeLong(offsets[3], object.accentColorOrangeInt); + writer.writeLong(offsets[4], object.accentColorRedInt); + writer.writeLong(offsets[5], object.accentColorYellowInt); + writer.writeObject( + offsets[6], + allOffsets, + ThemeAssetsSchema.serialize, + object.assetsV1, + ); + writer.writeObject( + offsets[7], + allOffsets, + ThemeAssetsV2Schema.serialize, + object.assetsV2, + ); + writer.writeLong(offsets[8], object.backgroundAppBarInt); + writer.writeLong(offsets[9], object.backgroundInt); + writer.writeLong(offsets[10], object.bottomNavBackInt); + writer.writeLong(offsets[11], object.bottomNavIconBackInt); + writer.writeLong(offsets[12], object.bottomNavIconIconHighlightedInt); + writer.writeLong(offsets[13], object.bottomNavIconIconInt); + writer.writeLong(offsets[14], object.bottomNavShadowInt); + writer.writeLong(offsets[15], object.bottomNavTextInt); + writer.writeString(offsets[16], object.brightnessString); + writer.writeLong(offsets[17], object.buttonBackBorderDisabledInt); + writer.writeLong(offsets[18], object.buttonBackBorderInt); + writer.writeLong(offsets[19], object.buttonBackBorderSecondaryDisabledInt); + writer.writeLong(offsets[20], object.buttonBackBorderSecondaryInt); + writer.writeLong(offsets[21], object.buttonBackPrimaryDisabledInt); + writer.writeLong(offsets[22], object.buttonBackPrimaryInt); + writer.writeLong(offsets[23], object.buttonBackSecondaryDisabledInt); + writer.writeLong(offsets[24], object.buttonBackSecondaryInt); + writer.writeLong(offsets[25], object.buttonTextBorderInt); + writer.writeLong(offsets[26], object.buttonTextBorderlessDisabledInt); + writer.writeLong(offsets[27], object.buttonTextBorderlessInt); + writer.writeLong(offsets[28], object.buttonTextDisabledInt); + writer.writeLong(offsets[29], object.buttonTextPrimaryDisabledInt); + writer.writeLong(offsets[30], object.buttonTextPrimaryInt); + writer.writeLong(offsets[31], object.buttonTextSecondaryDisabledInt); + writer.writeLong(offsets[32], object.buttonTextSecondaryInt); + writer.writeLong(offsets[33], object.checkboxBGCheckedInt); + writer.writeLong(offsets[34], object.checkboxBGDisabledInt); + writer.writeLong(offsets[35], object.checkboxBorderEmptyInt); + writer.writeLong(offsets[36], object.checkboxIconCheckedInt); + writer.writeLong(offsets[37], object.checkboxIconDisabledInt); + writer.writeLong(offsets[38], object.checkboxTextLabelInt); + writer.writeString(offsets[39], object.coinColorsJsonString); + writer.writeLong(offsets[40], object.currencyListItemBGInt); + writer.writeLong(offsets[41], object.customTextButtonDisabledTextInt); + writer.writeLong(offsets[42], object.customTextButtonEnabledTextInt); + writer.writeLong(offsets[43], object.ethTagBGInt); + writer.writeLong(offsets[44], object.ethTagTextInt); + writer.writeLong(offsets[45], object.ethWalletTagBGInt); + writer.writeLong(offsets[46], object.ethWalletTagTextInt); + writer.writeLong(offsets[47], object.favoriteStarActiveInt); + writer.writeLong(offsets[48], object.favoriteStarInactiveInt); + writer.writeString(offsets[49], object.gradientBackgroundString); + writer.writeLong(offsets[50], object.highlightInt); + writer.writeString(offsets[51], object.homeViewButtonBarBoxShadowString); + writer.writeLong(offsets[52], object.infoItemBGInt); + writer.writeLong(offsets[53], object.infoItemIconsInt); + writer.writeLong(offsets[54], object.infoItemLabelInt); + writer.writeLong(offsets[55], object.infoItemTextInt); + writer.writeLong(offsets[56], object.loadingOverlayTextColorInt); + writer.writeLong(offsets[57], object.myStackContactIconBGInt); + writer.writeString(offsets[58], object.name); + writer.writeLong(offsets[59], object.numberBackDefaultInt); + writer.writeLong(offsets[60], object.numberTextDefaultInt); + writer.writeLong(offsets[61], object.numpadBackDefaultInt); + writer.writeLong(offsets[62], object.numpadTextDefaultInt); + writer.writeLong(offsets[63], object.overlayInt); + writer.writeLong(offsets[64], object.popupBGInt); + writer.writeLong(offsets[65], object.radioButtonBorderDisabledInt); + writer.writeLong(offsets[66], object.radioButtonBorderEnabledInt); + writer.writeLong(offsets[67], object.radioButtonIconBorderDisabledInt); + writer.writeLong(offsets[68], object.radioButtonIconBorderInt); + writer.writeLong(offsets[69], object.radioButtonIconCircleInt); + writer.writeLong(offsets[70], object.radioButtonIconEnabledInt); + writer.writeLong(offsets[71], object.radioButtonLabelDisabledInt); + writer.writeLong(offsets[72], object.radioButtonLabelEnabledInt); + writer.writeLong(offsets[73], object.radioButtonTextDisabledInt); + writer.writeLong(offsets[74], object.radioButtonTextEnabledInt); + writer.writeLong(offsets[75], object.rateTypeToggleColorOffInt); + writer.writeLong(offsets[76], object.rateTypeToggleColorOnInt); + writer.writeLong(offsets[77], object.rateTypeToggleDesktopColorOffInt); + writer.writeLong(offsets[78], object.rateTypeToggleDesktopColorOnInt); + writer.writeLong(offsets[79], object.settingsIconBack2Int); + writer.writeLong(offsets[80], object.settingsIconBackInt); + writer.writeLong(offsets[81], object.settingsIconElementInt); + writer.writeLong(offsets[82], object.settingsIconIconInt); + writer.writeLong(offsets[83], object.settingsItem2ActiveBGInt); + writer.writeLong(offsets[84], object.settingsItem2ActiveSubInt); + writer.writeLong(offsets[85], object.settingsItem2ActiveTextInt); + writer.writeLong(offsets[86], object.shadowInt); + writer.writeLong(offsets[87], object.snackBarBackErrorInt); + writer.writeLong(offsets[88], object.snackBarBackInfoInt); + writer.writeLong(offsets[89], object.snackBarBackSuccessInt); + writer.writeLong(offsets[90], object.snackBarTextErrorInt); + writer.writeLong(offsets[91], object.snackBarTextInfoInt); + writer.writeLong(offsets[92], object.snackBarTextSuccessInt); + writer.writeLong(offsets[93], object.splashInt); + writer.writeLong(offsets[94], object.stackWalletBGInt); + writer.writeLong(offsets[95], object.stackWalletBottomInt); + writer.writeLong(offsets[96], object.stackWalletMidInt); + writer.writeString(offsets[97], object.standardBoxShadowString); + writer.writeLong(offsets[98], object.stepIndicatorBGCheckInt); + writer.writeLong(offsets[99], object.stepIndicatorBGInactiveInt); + writer.writeLong(offsets[100], object.stepIndicatorBGLinesInactiveInt); + writer.writeLong(offsets[101], object.stepIndicatorBGLinesInt); + writer.writeLong(offsets[102], object.stepIndicatorBGNumberInt); + writer.writeLong(offsets[103], object.stepIndicatorIconInactiveInt); + writer.writeLong(offsets[104], object.stepIndicatorIconNumberInt); + writer.writeLong(offsets[105], object.stepIndicatorIconTextInt); + writer.writeLong(offsets[106], object.switchBGDisabledInt); + writer.writeLong(offsets[107], object.switchBGOffInt); + writer.writeLong(offsets[108], object.switchBGOnInt); + writer.writeLong(offsets[109], object.switchCircleDisabledInt); + writer.writeLong(offsets[110], object.switchCircleOffInt); + writer.writeLong(offsets[111], object.switchCircleOnInt); + writer.writeLong(offsets[112], object.textConfirmTotalAmountInt); + writer.writeLong(offsets[113], object.textDark2Int); + writer.writeLong(offsets[114], object.textDark3Int); + writer.writeLong(offsets[115], object.textDarkInt); + writer.writeLong(offsets[116], object.textErrorInt); + writer.writeLong(offsets[117], object.textFavoriteCardInt); + writer.writeLong(offsets[118], object.textFieldActiveBGInt); + writer.writeLong(offsets[119], object.textFieldActiveLabelInt); + writer.writeLong(offsets[120], object.textFieldActiveSearchIconLeftInt); + writer.writeLong(offsets[121], object.textFieldActiveSearchIconRightInt); + writer.writeLong(offsets[122], object.textFieldActiveTextInt); + writer.writeLong(offsets[123], object.textFieldDefaultBGInt); + writer.writeLong(offsets[124], object.textFieldDefaultSearchIconLeftInt); + writer.writeLong(offsets[125], object.textFieldDefaultSearchIconRightInt); + writer.writeLong(offsets[126], object.textFieldDefaultTextInt); + writer.writeLong(offsets[127], object.textFieldErrorBGInt); + writer.writeLong(offsets[128], object.textFieldErrorBorderInt); + writer.writeLong(offsets[129], object.textFieldErrorLabelInt); + writer.writeLong(offsets[130], object.textFieldErrorSearchIconLeftInt); + writer.writeLong(offsets[131], object.textFieldErrorSearchIconRightInt); + writer.writeLong(offsets[132], object.textFieldErrorTextInt); + writer.writeLong(offsets[133], object.textFieldSuccessBGInt); + writer.writeLong(offsets[134], object.textFieldSuccessBorderInt); + writer.writeLong(offsets[135], object.textFieldSuccessLabelInt); + writer.writeLong(offsets[136], object.textFieldSuccessSearchIconLeftInt); + writer.writeLong(offsets[137], object.textFieldSuccessSearchIconRightInt); + writer.writeLong(offsets[138], object.textFieldSuccessTextInt); + writer.writeLong(offsets[139], object.textRestoreInt); + writer.writeLong(offsets[140], object.textSelectedWordTableItemInt); + writer.writeLong(offsets[141], object.textSubtitle1Int); + writer.writeLong(offsets[142], object.textSubtitle2Int); + writer.writeLong(offsets[143], object.textSubtitle3Int); + writer.writeLong(offsets[144], object.textSubtitle4Int); + writer.writeLong(offsets[145], object.textSubtitle5Int); + writer.writeLong(offsets[146], object.textSubtitle6Int); + writer.writeLong(offsets[147], object.textWhiteInt); + writer.writeString(offsets[148], object.themeId); + writer.writeLong(offsets[149], object.tokenSummaryBGInt); + writer.writeLong(offsets[150], object.tokenSummaryButtonBGInt); + writer.writeLong(offsets[151], object.tokenSummaryIconInt); + writer.writeLong(offsets[152], object.tokenSummaryTextPrimaryInt); + writer.writeLong(offsets[153], object.tokenSummaryTextSecondaryInt); + writer.writeLong(offsets[154], object.topNavIconGreenInt); + writer.writeLong(offsets[155], object.topNavIconPrimaryInt); + writer.writeLong(offsets[156], object.topNavIconRedInt); + writer.writeLong(offsets[157], object.topNavIconYellowInt); + writer.writeLong(offsets[158], object.version); + writer.writeLong(offsets[159], object.warningBackgroundInt); + writer.writeLong(offsets[160], object.warningForegroundInt); +} + +StackTheme _stackThemeDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = StackTheme(); + object.accentColorBlueInt = reader.readLong(offsets[0]); + object.accentColorDarkInt = reader.readLong(offsets[1]); + object.accentColorGreenInt = reader.readLong(offsets[2]); + object.accentColorOrangeInt = reader.readLong(offsets[3]); + object.accentColorRedInt = reader.readLong(offsets[4]); + object.accentColorYellowInt = reader.readLong(offsets[5]); + object.assetsV1 = reader.readObjectOrNull( + offsets[6], + ThemeAssetsSchema.deserialize, + allOffsets, + ); + object.assetsV2 = reader.readObjectOrNull( + offsets[7], + ThemeAssetsV2Schema.deserialize, + allOffsets, + ); + object.backgroundAppBarInt = reader.readLong(offsets[8]); + object.backgroundInt = reader.readLong(offsets[9]); + object.bottomNavBackInt = reader.readLong(offsets[10]); + object.bottomNavIconBackInt = reader.readLong(offsets[11]); + object.bottomNavIconIconHighlightedInt = reader.readLong(offsets[12]); + object.bottomNavIconIconInt = reader.readLong(offsets[13]); + object.bottomNavShadowInt = reader.readLong(offsets[14]); + object.bottomNavTextInt = reader.readLong(offsets[15]); + object.brightnessString = reader.readString(offsets[16]); + object.buttonBackBorderDisabledInt = reader.readLong(offsets[17]); + object.buttonBackBorderInt = reader.readLong(offsets[18]); + object.buttonBackBorderSecondaryDisabledInt = reader.readLong(offsets[19]); + object.buttonBackBorderSecondaryInt = reader.readLong(offsets[20]); + object.buttonBackPrimaryDisabledInt = reader.readLong(offsets[21]); + object.buttonBackPrimaryInt = reader.readLong(offsets[22]); + object.buttonBackSecondaryDisabledInt = reader.readLong(offsets[23]); + object.buttonBackSecondaryInt = reader.readLong(offsets[24]); + object.buttonTextBorderInt = reader.readLong(offsets[25]); + object.buttonTextBorderlessDisabledInt = reader.readLong(offsets[26]); + object.buttonTextBorderlessInt = reader.readLong(offsets[27]); + object.buttonTextDisabledInt = reader.readLong(offsets[28]); + object.buttonTextPrimaryDisabledInt = reader.readLong(offsets[29]); + object.buttonTextPrimaryInt = reader.readLong(offsets[30]); + object.buttonTextSecondaryDisabledInt = reader.readLong(offsets[31]); + object.buttonTextSecondaryInt = reader.readLong(offsets[32]); + object.checkboxBGCheckedInt = reader.readLong(offsets[33]); + object.checkboxBGDisabledInt = reader.readLong(offsets[34]); + object.checkboxBorderEmptyInt = reader.readLong(offsets[35]); + object.checkboxIconCheckedInt = reader.readLong(offsets[36]); + object.checkboxIconDisabledInt = reader.readLong(offsets[37]); + object.checkboxTextLabelInt = reader.readLong(offsets[38]); + object.coinColorsJsonString = reader.readString(offsets[39]); + object.currencyListItemBGInt = reader.readLong(offsets[40]); + object.customTextButtonDisabledTextInt = reader.readLong(offsets[41]); + object.customTextButtonEnabledTextInt = reader.readLong(offsets[42]); + object.ethTagBGInt = reader.readLong(offsets[43]); + object.ethTagTextInt = reader.readLong(offsets[44]); + object.ethWalletTagBGInt = reader.readLong(offsets[45]); + object.ethWalletTagTextInt = reader.readLong(offsets[46]); + object.favoriteStarActiveInt = reader.readLong(offsets[47]); + object.favoriteStarInactiveInt = reader.readLong(offsets[48]); + object.gradientBackgroundString = reader.readStringOrNull(offsets[49]); + object.highlightInt = reader.readLong(offsets[50]); + object.homeViewButtonBarBoxShadowString = + reader.readStringOrNull(offsets[51]); + object.id = id; + object.infoItemBGInt = reader.readLong(offsets[52]); + object.infoItemIconsInt = reader.readLong(offsets[53]); + object.infoItemLabelInt = reader.readLong(offsets[54]); + object.infoItemTextInt = reader.readLong(offsets[55]); + object.loadingOverlayTextColorInt = reader.readLong(offsets[56]); + object.myStackContactIconBGInt = reader.readLong(offsets[57]); + object.name = reader.readString(offsets[58]); + object.numberBackDefaultInt = reader.readLong(offsets[59]); + object.numberTextDefaultInt = reader.readLong(offsets[60]); + object.numpadBackDefaultInt = reader.readLong(offsets[61]); + object.numpadTextDefaultInt = reader.readLong(offsets[62]); + object.overlayInt = reader.readLong(offsets[63]); + object.popupBGInt = reader.readLong(offsets[64]); + object.radioButtonBorderDisabledInt = reader.readLong(offsets[65]); + object.radioButtonBorderEnabledInt = reader.readLong(offsets[66]); + object.radioButtonIconBorderDisabledInt = reader.readLong(offsets[67]); + object.radioButtonIconBorderInt = reader.readLong(offsets[68]); + object.radioButtonIconCircleInt = reader.readLong(offsets[69]); + object.radioButtonIconEnabledInt = reader.readLong(offsets[70]); + object.radioButtonLabelDisabledInt = reader.readLong(offsets[71]); + object.radioButtonLabelEnabledInt = reader.readLong(offsets[72]); + object.radioButtonTextDisabledInt = reader.readLong(offsets[73]); + object.radioButtonTextEnabledInt = reader.readLong(offsets[74]); + object.rateTypeToggleColorOffInt = reader.readLong(offsets[75]); + object.rateTypeToggleColorOnInt = reader.readLong(offsets[76]); + object.rateTypeToggleDesktopColorOffInt = reader.readLong(offsets[77]); + object.rateTypeToggleDesktopColorOnInt = reader.readLong(offsets[78]); + object.settingsIconBack2Int = reader.readLong(offsets[79]); + object.settingsIconBackInt = reader.readLong(offsets[80]); + object.settingsIconElementInt = reader.readLong(offsets[81]); + object.settingsIconIconInt = reader.readLong(offsets[82]); + object.settingsItem2ActiveBGInt = reader.readLong(offsets[83]); + object.settingsItem2ActiveSubInt = reader.readLong(offsets[84]); + object.settingsItem2ActiveTextInt = reader.readLong(offsets[85]); + object.shadowInt = reader.readLong(offsets[86]); + object.snackBarBackErrorInt = reader.readLong(offsets[87]); + object.snackBarBackInfoInt = reader.readLong(offsets[88]); + object.snackBarBackSuccessInt = reader.readLong(offsets[89]); + object.snackBarTextErrorInt = reader.readLong(offsets[90]); + object.snackBarTextInfoInt = reader.readLong(offsets[91]); + object.snackBarTextSuccessInt = reader.readLong(offsets[92]); + object.splashInt = reader.readLong(offsets[93]); + object.stackWalletBGInt = reader.readLong(offsets[94]); + object.stackWalletBottomInt = reader.readLong(offsets[95]); + object.stackWalletMidInt = reader.readLong(offsets[96]); + object.standardBoxShadowString = reader.readString(offsets[97]); + object.stepIndicatorBGCheckInt = reader.readLong(offsets[98]); + object.stepIndicatorBGInactiveInt = reader.readLong(offsets[99]); + object.stepIndicatorBGLinesInactiveInt = reader.readLong(offsets[100]); + object.stepIndicatorBGLinesInt = reader.readLong(offsets[101]); + object.stepIndicatorBGNumberInt = reader.readLong(offsets[102]); + object.stepIndicatorIconInactiveInt = reader.readLong(offsets[103]); + object.stepIndicatorIconNumberInt = reader.readLong(offsets[104]); + object.stepIndicatorIconTextInt = reader.readLong(offsets[105]); + object.switchBGDisabledInt = reader.readLong(offsets[106]); + object.switchBGOffInt = reader.readLong(offsets[107]); + object.switchBGOnInt = reader.readLong(offsets[108]); + object.switchCircleDisabledInt = reader.readLong(offsets[109]); + object.switchCircleOffInt = reader.readLong(offsets[110]); + object.switchCircleOnInt = reader.readLong(offsets[111]); + object.textConfirmTotalAmountInt = reader.readLong(offsets[112]); + object.textDark2Int = reader.readLong(offsets[113]); + object.textDark3Int = reader.readLong(offsets[114]); + object.textDarkInt = reader.readLong(offsets[115]); + object.textErrorInt = reader.readLong(offsets[116]); + object.textFavoriteCardInt = reader.readLong(offsets[117]); + object.textFieldActiveBGInt = reader.readLong(offsets[118]); + object.textFieldActiveLabelInt = reader.readLong(offsets[119]); + object.textFieldActiveSearchIconLeftInt = reader.readLong(offsets[120]); + object.textFieldActiveSearchIconRightInt = reader.readLong(offsets[121]); + object.textFieldActiveTextInt = reader.readLong(offsets[122]); + object.textFieldDefaultBGInt = reader.readLong(offsets[123]); + object.textFieldDefaultSearchIconLeftInt = reader.readLong(offsets[124]); + object.textFieldDefaultSearchIconRightInt = reader.readLong(offsets[125]); + object.textFieldDefaultTextInt = reader.readLong(offsets[126]); + object.textFieldErrorBGInt = reader.readLong(offsets[127]); + object.textFieldErrorBorderInt = reader.readLong(offsets[128]); + object.textFieldErrorLabelInt = reader.readLong(offsets[129]); + object.textFieldErrorSearchIconLeftInt = reader.readLong(offsets[130]); + object.textFieldErrorSearchIconRightInt = reader.readLong(offsets[131]); + object.textFieldErrorTextInt = reader.readLong(offsets[132]); + object.textFieldSuccessBGInt = reader.readLong(offsets[133]); + object.textFieldSuccessBorderInt = reader.readLong(offsets[134]); + object.textFieldSuccessLabelInt = reader.readLong(offsets[135]); + object.textFieldSuccessSearchIconLeftInt = reader.readLong(offsets[136]); + object.textFieldSuccessSearchIconRightInt = reader.readLong(offsets[137]); + object.textFieldSuccessTextInt = reader.readLong(offsets[138]); + object.textRestoreInt = reader.readLong(offsets[139]); + object.textSelectedWordTableItemInt = reader.readLong(offsets[140]); + object.textSubtitle1Int = reader.readLong(offsets[141]); + object.textSubtitle2Int = reader.readLong(offsets[142]); + object.textSubtitle3Int = reader.readLong(offsets[143]); + object.textSubtitle4Int = reader.readLong(offsets[144]); + object.textSubtitle5Int = reader.readLong(offsets[145]); + object.textSubtitle6Int = reader.readLong(offsets[146]); + object.textWhiteInt = reader.readLong(offsets[147]); + object.themeId = reader.readString(offsets[148]); + object.tokenSummaryBGInt = reader.readLong(offsets[149]); + object.tokenSummaryButtonBGInt = reader.readLong(offsets[150]); + object.tokenSummaryIconInt = reader.readLong(offsets[151]); + object.tokenSummaryTextPrimaryInt = reader.readLong(offsets[152]); + object.tokenSummaryTextSecondaryInt = reader.readLong(offsets[153]); + object.topNavIconGreenInt = reader.readLong(offsets[154]); + object.topNavIconPrimaryInt = reader.readLong(offsets[155]); + object.topNavIconRedInt = reader.readLong(offsets[156]); + object.topNavIconYellowInt = reader.readLong(offsets[157]); + object.version = reader.readLongOrNull(offsets[158]); + object.warningBackgroundInt = reader.readLong(offsets[159]); + object.warningForegroundInt = reader.readLong(offsets[160]); + return object; +} + +P _stackThemeDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readLong(offset)) as P; + case 1: + return (reader.readLong(offset)) as P; + case 2: + return (reader.readLong(offset)) as P; + case 3: + return (reader.readLong(offset)) as P; + case 4: + return (reader.readLong(offset)) as P; + case 5: + return (reader.readLong(offset)) as P; + case 6: + return (reader.readObjectOrNull( + offset, + ThemeAssetsSchema.deserialize, + allOffsets, + )) as P; + case 7: + return (reader.readObjectOrNull( + offset, + ThemeAssetsV2Schema.deserialize, + allOffsets, + )) as P; + case 8: + return (reader.readLong(offset)) as P; + case 9: + return (reader.readLong(offset)) as P; + case 10: + return (reader.readLong(offset)) as P; + case 11: + return (reader.readLong(offset)) as P; + case 12: + return (reader.readLong(offset)) as P; + case 13: + return (reader.readLong(offset)) as P; + case 14: + return (reader.readLong(offset)) as P; + case 15: + return (reader.readLong(offset)) as P; + case 16: + return (reader.readString(offset)) as P; + case 17: + return (reader.readLong(offset)) as P; + case 18: + return (reader.readLong(offset)) as P; + case 19: + return (reader.readLong(offset)) as P; + case 20: + return (reader.readLong(offset)) as P; + case 21: + return (reader.readLong(offset)) as P; + case 22: + return (reader.readLong(offset)) as P; + case 23: + return (reader.readLong(offset)) as P; + case 24: + return (reader.readLong(offset)) as P; + case 25: + return (reader.readLong(offset)) as P; + case 26: + return (reader.readLong(offset)) as P; + case 27: + return (reader.readLong(offset)) as P; + case 28: + return (reader.readLong(offset)) as P; + case 29: + return (reader.readLong(offset)) as P; + case 30: + return (reader.readLong(offset)) as P; + case 31: + return (reader.readLong(offset)) as P; + case 32: + return (reader.readLong(offset)) as P; + case 33: + return (reader.readLong(offset)) as P; + case 34: + return (reader.readLong(offset)) as P; + case 35: + return (reader.readLong(offset)) as P; + case 36: + return (reader.readLong(offset)) as P; + case 37: + return (reader.readLong(offset)) as P; + case 38: + return (reader.readLong(offset)) as P; + case 39: + return (reader.readString(offset)) as P; + case 40: + return (reader.readLong(offset)) as P; + case 41: + return (reader.readLong(offset)) as P; + case 42: + return (reader.readLong(offset)) as P; + case 43: + return (reader.readLong(offset)) as P; + case 44: + return (reader.readLong(offset)) as P; + case 45: + return (reader.readLong(offset)) as P; + case 46: + return (reader.readLong(offset)) as P; + case 47: + return (reader.readLong(offset)) as P; + case 48: + return (reader.readLong(offset)) as P; + case 49: + return (reader.readStringOrNull(offset)) as P; + case 50: + return (reader.readLong(offset)) as P; + case 51: + return (reader.readStringOrNull(offset)) as P; + case 52: + return (reader.readLong(offset)) as P; + case 53: + return (reader.readLong(offset)) as P; + case 54: + return (reader.readLong(offset)) as P; + case 55: + return (reader.readLong(offset)) as P; + case 56: + return (reader.readLong(offset)) as P; + case 57: + return (reader.readLong(offset)) as P; + case 58: + return (reader.readString(offset)) as P; + case 59: + return (reader.readLong(offset)) as P; + case 60: + return (reader.readLong(offset)) as P; + case 61: + return (reader.readLong(offset)) as P; + case 62: + return (reader.readLong(offset)) as P; + case 63: + return (reader.readLong(offset)) as P; + case 64: + return (reader.readLong(offset)) as P; + case 65: + return (reader.readLong(offset)) as P; + case 66: + return (reader.readLong(offset)) as P; + case 67: + return (reader.readLong(offset)) as P; + case 68: + return (reader.readLong(offset)) as P; + case 69: + return (reader.readLong(offset)) as P; + case 70: + return (reader.readLong(offset)) as P; + case 71: + return (reader.readLong(offset)) as P; + case 72: + return (reader.readLong(offset)) as P; + case 73: + return (reader.readLong(offset)) as P; + case 74: + return (reader.readLong(offset)) as P; + case 75: + return (reader.readLong(offset)) as P; + case 76: + return (reader.readLong(offset)) as P; + case 77: + return (reader.readLong(offset)) as P; + case 78: + return (reader.readLong(offset)) as P; + case 79: + return (reader.readLong(offset)) as P; + case 80: + return (reader.readLong(offset)) as P; + case 81: + return (reader.readLong(offset)) as P; + case 82: + return (reader.readLong(offset)) as P; + case 83: + return (reader.readLong(offset)) as P; + case 84: + return (reader.readLong(offset)) as P; + case 85: + return (reader.readLong(offset)) as P; + case 86: + return (reader.readLong(offset)) as P; + case 87: + return (reader.readLong(offset)) as P; + case 88: + return (reader.readLong(offset)) as P; + case 89: + return (reader.readLong(offset)) as P; + case 90: + return (reader.readLong(offset)) as P; + case 91: + return (reader.readLong(offset)) as P; + case 92: + return (reader.readLong(offset)) as P; + case 93: + return (reader.readLong(offset)) as P; + case 94: + return (reader.readLong(offset)) as P; + case 95: + return (reader.readLong(offset)) as P; + case 96: + return (reader.readLong(offset)) as P; + case 97: + return (reader.readString(offset)) as P; + case 98: + return (reader.readLong(offset)) as P; + case 99: + return (reader.readLong(offset)) as P; + case 100: + return (reader.readLong(offset)) as P; + case 101: + return (reader.readLong(offset)) as P; + case 102: + return (reader.readLong(offset)) as P; + case 103: + return (reader.readLong(offset)) as P; + case 104: + return (reader.readLong(offset)) as P; + case 105: + return (reader.readLong(offset)) as P; + case 106: + return (reader.readLong(offset)) as P; + case 107: + return (reader.readLong(offset)) as P; + case 108: + return (reader.readLong(offset)) as P; + case 109: + return (reader.readLong(offset)) as P; + case 110: + return (reader.readLong(offset)) as P; + case 111: + return (reader.readLong(offset)) as P; + case 112: + return (reader.readLong(offset)) as P; + case 113: + return (reader.readLong(offset)) as P; + case 114: + return (reader.readLong(offset)) as P; + case 115: + return (reader.readLong(offset)) as P; + case 116: + return (reader.readLong(offset)) as P; + case 117: + return (reader.readLong(offset)) as P; + case 118: + return (reader.readLong(offset)) as P; + case 119: + return (reader.readLong(offset)) as P; + case 120: + return (reader.readLong(offset)) as P; + case 121: + return (reader.readLong(offset)) as P; + case 122: + return (reader.readLong(offset)) as P; + case 123: + return (reader.readLong(offset)) as P; + case 124: + return (reader.readLong(offset)) as P; + case 125: + return (reader.readLong(offset)) as P; + case 126: + return (reader.readLong(offset)) as P; + case 127: + return (reader.readLong(offset)) as P; + case 128: + return (reader.readLong(offset)) as P; + case 129: + return (reader.readLong(offset)) as P; + case 130: + return (reader.readLong(offset)) as P; + case 131: + return (reader.readLong(offset)) as P; + case 132: + return (reader.readLong(offset)) as P; + case 133: + return (reader.readLong(offset)) as P; + case 134: + return (reader.readLong(offset)) as P; + case 135: + return (reader.readLong(offset)) as P; + case 136: + return (reader.readLong(offset)) as P; + case 137: + return (reader.readLong(offset)) as P; + case 138: + return (reader.readLong(offset)) as P; + case 139: + return (reader.readLong(offset)) as P; + case 140: + return (reader.readLong(offset)) as P; + case 141: + return (reader.readLong(offset)) as P; + case 142: + return (reader.readLong(offset)) as P; + case 143: + return (reader.readLong(offset)) as P; + case 144: + return (reader.readLong(offset)) as P; + case 145: + return (reader.readLong(offset)) as P; + case 146: + return (reader.readLong(offset)) as P; + case 147: + return (reader.readLong(offset)) as P; + case 148: + return (reader.readString(offset)) as P; + case 149: + return (reader.readLong(offset)) as P; + case 150: + return (reader.readLong(offset)) as P; + case 151: + return (reader.readLong(offset)) as P; + case 152: + return (reader.readLong(offset)) as P; + case 153: + return (reader.readLong(offset)) as P; + case 154: + return (reader.readLong(offset)) as P; + case 155: + return (reader.readLong(offset)) as P; + case 156: + return (reader.readLong(offset)) as P; + case 157: + return (reader.readLong(offset)) as P; + case 158: + return (reader.readLongOrNull(offset)) as P; + case 159: + return (reader.readLong(offset)) as P; + case 160: + return (reader.readLong(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +Id _stackThemeGetId(StackTheme object) { + return object.id; +} + +List> _stackThemeGetLinks(StackTheme object) { + return []; +} + +void _stackThemeAttach(IsarCollection col, Id id, StackTheme object) { + object.id = id; +} + +extension StackThemeByIndex on IsarCollection { + Future getByThemeId(String themeId) { + return getByIndex(r'themeId', [themeId]); + } + + StackTheme? getByThemeIdSync(String themeId) { + return getByIndexSync(r'themeId', [themeId]); + } + + Future deleteByThemeId(String themeId) { + return deleteByIndex(r'themeId', [themeId]); + } + + bool deleteByThemeIdSync(String themeId) { + return deleteByIndexSync(r'themeId', [themeId]); + } + + Future> getAllByThemeId(List themeIdValues) { + final values = themeIdValues.map((e) => [e]).toList(); + return getAllByIndex(r'themeId', values); + } + + List getAllByThemeIdSync(List themeIdValues) { + final values = themeIdValues.map((e) => [e]).toList(); + return getAllByIndexSync(r'themeId', values); + } + + Future deleteAllByThemeId(List themeIdValues) { + final values = themeIdValues.map((e) => [e]).toList(); + return deleteAllByIndex(r'themeId', values); + } + + int deleteAllByThemeIdSync(List themeIdValues) { + final values = themeIdValues.map((e) => [e]).toList(); + return deleteAllByIndexSync(r'themeId', values); + } + + Future putByThemeId(StackTheme object) { + return putByIndex(r'themeId', object); + } + + Id putByThemeIdSync(StackTheme object, {bool saveLinks = true}) { + return putByIndexSync(r'themeId', object, saveLinks: saveLinks); + } + + Future> putAllByThemeId(List objects) { + return putAllByIndex(r'themeId', objects); + } + + List putAllByThemeIdSync(List objects, + {bool saveLinks = true}) { + return putAllByIndexSync(r'themeId', objects, saveLinks: saveLinks); + } +} + +extension StackThemeQueryWhereSort + on QueryBuilder { + QueryBuilder anyId() { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(const IdWhereClause.any()); + }); + } +} + +extension StackThemeQueryWhere + on QueryBuilder { + QueryBuilder idEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: id, + upper: id, + )); + }); + } + + QueryBuilder idNotEqualTo(Id id) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ) + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ); + } else { + return query + .addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: false), + ) + .addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: false), + ); + } + }); + } + + QueryBuilder idGreaterThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.greaterThan(lower: id, includeLower: include), + ); + }); + } + + QueryBuilder idLessThan(Id id, + {bool include = false}) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause( + IdWhereClause.lessThan(upper: id, includeUpper: include), + ); + }); + } + + QueryBuilder idBetween( + Id lowerId, + Id upperId, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IdWhereClause.between( + lower: lowerId, + includeLower: includeLower, + upper: upperId, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder themeIdEqualTo( + String themeId) { + return QueryBuilder.apply(this, (query) { + return query.addWhereClause(IndexWhereClause.equalTo( + indexName: r'themeId', + value: [themeId], + )); + }); + } + + QueryBuilder themeIdNotEqualTo( + String themeId) { + return QueryBuilder.apply(this, (query) { + if (query.whereSort == Sort.asc) { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'themeId', + lower: [], + upper: [themeId], + includeUpper: false, + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'themeId', + lower: [themeId], + includeLower: false, + upper: [], + )); + } else { + return query + .addWhereClause(IndexWhereClause.between( + indexName: r'themeId', + lower: [themeId], + includeLower: false, + upper: [], + )) + .addWhereClause(IndexWhereClause.between( + indexName: r'themeId', + lower: [], + upper: [themeId], + includeUpper: false, + )); + } + }); + } +} + +extension StackThemeQueryFilter + on QueryBuilder { + QueryBuilder + accentColorBlueIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'accentColorBlueInt', + value: value, + )); + }); + } + + QueryBuilder + accentColorBlueIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'accentColorBlueInt', + value: value, + )); + }); + } + + QueryBuilder + accentColorBlueIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'accentColorBlueInt', + value: value, + )); + }); + } + + QueryBuilder + accentColorBlueIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'accentColorBlueInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + accentColorDarkIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'accentColorDarkInt', + value: value, + )); + }); + } + + QueryBuilder + accentColorDarkIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'accentColorDarkInt', + value: value, + )); + }); + } + + QueryBuilder + accentColorDarkIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'accentColorDarkInt', + value: value, + )); + }); + } + + QueryBuilder + accentColorDarkIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'accentColorDarkInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + accentColorGreenIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'accentColorGreenInt', + value: value, + )); + }); + } + + QueryBuilder + accentColorGreenIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'accentColorGreenInt', + value: value, + )); + }); + } + + QueryBuilder + accentColorGreenIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'accentColorGreenInt', + value: value, + )); + }); + } + + QueryBuilder + accentColorGreenIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'accentColorGreenInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + accentColorOrangeIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'accentColorOrangeInt', + value: value, + )); + }); + } + + QueryBuilder + accentColorOrangeIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'accentColorOrangeInt', + value: value, + )); + }); + } + + QueryBuilder + accentColorOrangeIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'accentColorOrangeInt', + value: value, + )); + }); + } + + QueryBuilder + accentColorOrangeIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'accentColorOrangeInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + accentColorRedIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'accentColorRedInt', + value: value, + )); + }); + } + + QueryBuilder + accentColorRedIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'accentColorRedInt', + value: value, + )); + }); + } + + QueryBuilder + accentColorRedIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'accentColorRedInt', + value: value, + )); + }); + } + + QueryBuilder + accentColorRedIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'accentColorRedInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + accentColorYellowIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'accentColorYellowInt', + value: value, + )); + }); + } + + QueryBuilder + accentColorYellowIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'accentColorYellowInt', + value: value, + )); + }); + } + + QueryBuilder + accentColorYellowIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'accentColorYellowInt', + value: value, + )); + }); + } + + QueryBuilder + accentColorYellowIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'accentColorYellowInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder assetsV1IsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'assets', + )); + }); + } + + QueryBuilder + assetsV1IsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'assets', + )); + }); + } + + QueryBuilder assetsV2IsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'assetsV2', + )); + }); + } + + QueryBuilder + assetsV2IsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'assetsV2', + )); + }); + } + + QueryBuilder + backgroundAppBarIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'backgroundAppBarInt', + value: value, + )); + }); + } + + QueryBuilder + backgroundAppBarIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'backgroundAppBarInt', + value: value, + )); + }); + } + + QueryBuilder + backgroundAppBarIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'backgroundAppBarInt', + value: value, + )); + }); + } + + QueryBuilder + backgroundAppBarIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'backgroundAppBarInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + backgroundIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'backgroundInt', + value: value, + )); + }); + } + + QueryBuilder + backgroundIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'backgroundInt', + value: value, + )); + }); + } + + QueryBuilder + backgroundIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'backgroundInt', + value: value, + )); + }); + } + + QueryBuilder + backgroundIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'backgroundInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + bottomNavBackIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'bottomNavBackInt', + value: value, + )); + }); + } + + QueryBuilder + bottomNavBackIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'bottomNavBackInt', + value: value, + )); + }); + } + + QueryBuilder + bottomNavBackIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'bottomNavBackInt', + value: value, + )); + }); + } + + QueryBuilder + bottomNavBackIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'bottomNavBackInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + bottomNavIconBackIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'bottomNavIconBackInt', + value: value, + )); + }); + } + + QueryBuilder + bottomNavIconBackIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'bottomNavIconBackInt', + value: value, + )); + }); + } + + QueryBuilder + bottomNavIconBackIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'bottomNavIconBackInt', + value: value, + )); + }); + } + + QueryBuilder + bottomNavIconBackIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'bottomNavIconBackInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + bottomNavIconIconHighlightedIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'bottomNavIconIconHighlightedInt', + value: value, + )); + }); + } + + QueryBuilder + bottomNavIconIconHighlightedIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'bottomNavIconIconHighlightedInt', + value: value, + )); + }); + } + + QueryBuilder + bottomNavIconIconHighlightedIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'bottomNavIconIconHighlightedInt', + value: value, + )); + }); + } + + QueryBuilder + bottomNavIconIconHighlightedIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'bottomNavIconIconHighlightedInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + bottomNavIconIconIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'bottomNavIconIconInt', + value: value, + )); + }); + } + + QueryBuilder + bottomNavIconIconIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'bottomNavIconIconInt', + value: value, + )); + }); + } + + QueryBuilder + bottomNavIconIconIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'bottomNavIconIconInt', + value: value, + )); + }); + } + + QueryBuilder + bottomNavIconIconIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'bottomNavIconIconInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + bottomNavShadowIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'bottomNavShadowInt', + value: value, + )); + }); + } + + QueryBuilder + bottomNavShadowIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'bottomNavShadowInt', + value: value, + )); + }); + } + + QueryBuilder + bottomNavShadowIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'bottomNavShadowInt', + value: value, + )); + }); + } + + QueryBuilder + bottomNavShadowIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'bottomNavShadowInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + bottomNavTextIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'bottomNavTextInt', + value: value, + )); + }); + } + + QueryBuilder + bottomNavTextIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'bottomNavTextInt', + value: value, + )); + }); + } + + QueryBuilder + bottomNavTextIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'bottomNavTextInt', + value: value, + )); + }); + } + + QueryBuilder + bottomNavTextIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'bottomNavTextInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + brightnessStringEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'brightnessString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + brightnessStringGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'brightnessString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + brightnessStringLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'brightnessString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + brightnessStringBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'brightnessString', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + brightnessStringStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'brightnessString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + brightnessStringEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'brightnessString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + brightnessStringContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'brightnessString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + brightnessStringMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'brightnessString', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + brightnessStringIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'brightnessString', + value: '', + )); + }); + } + + QueryBuilder + brightnessStringIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'brightnessString', + value: '', + )); + }); + } + + QueryBuilder + buttonBackBorderDisabledIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'buttonBackBorderDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + buttonBackBorderDisabledIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'buttonBackBorderDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + buttonBackBorderDisabledIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'buttonBackBorderDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + buttonBackBorderDisabledIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'buttonBackBorderDisabledInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + buttonBackBorderIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'buttonBackBorderInt', + value: value, + )); + }); + } + + QueryBuilder + buttonBackBorderIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'buttonBackBorderInt', + value: value, + )); + }); + } + + QueryBuilder + buttonBackBorderIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'buttonBackBorderInt', + value: value, + )); + }); + } + + QueryBuilder + buttonBackBorderIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'buttonBackBorderInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + buttonBackBorderSecondaryDisabledIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'buttonBackBorderSecondaryDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + buttonBackBorderSecondaryDisabledIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'buttonBackBorderSecondaryDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + buttonBackBorderSecondaryDisabledIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'buttonBackBorderSecondaryDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + buttonBackBorderSecondaryDisabledIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'buttonBackBorderSecondaryDisabledInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + buttonBackBorderSecondaryIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'buttonBackBorderSecondaryInt', + value: value, + )); + }); + } + + QueryBuilder + buttonBackBorderSecondaryIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'buttonBackBorderSecondaryInt', + value: value, + )); + }); + } + + QueryBuilder + buttonBackBorderSecondaryIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'buttonBackBorderSecondaryInt', + value: value, + )); + }); + } + + QueryBuilder + buttonBackBorderSecondaryIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'buttonBackBorderSecondaryInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + buttonBackPrimaryDisabledIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'buttonBackPrimaryDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + buttonBackPrimaryDisabledIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'buttonBackPrimaryDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + buttonBackPrimaryDisabledIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'buttonBackPrimaryDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + buttonBackPrimaryDisabledIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'buttonBackPrimaryDisabledInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + buttonBackPrimaryIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'buttonBackPrimaryInt', + value: value, + )); + }); + } + + QueryBuilder + buttonBackPrimaryIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'buttonBackPrimaryInt', + value: value, + )); + }); + } + + QueryBuilder + buttonBackPrimaryIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'buttonBackPrimaryInt', + value: value, + )); + }); + } + + QueryBuilder + buttonBackPrimaryIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'buttonBackPrimaryInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + buttonBackSecondaryDisabledIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'buttonBackSecondaryDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + buttonBackSecondaryDisabledIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'buttonBackSecondaryDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + buttonBackSecondaryDisabledIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'buttonBackSecondaryDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + buttonBackSecondaryDisabledIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'buttonBackSecondaryDisabledInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + buttonBackSecondaryIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'buttonBackSecondaryInt', + value: value, + )); + }); + } + + QueryBuilder + buttonBackSecondaryIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'buttonBackSecondaryInt', + value: value, + )); + }); + } + + QueryBuilder + buttonBackSecondaryIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'buttonBackSecondaryInt', + value: value, + )); + }); + } + + QueryBuilder + buttonBackSecondaryIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'buttonBackSecondaryInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + buttonTextBorderIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'buttonTextBorderInt', + value: value, + )); + }); + } + + QueryBuilder + buttonTextBorderIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'buttonTextBorderInt', + value: value, + )); + }); + } + + QueryBuilder + buttonTextBorderIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'buttonTextBorderInt', + value: value, + )); + }); + } + + QueryBuilder + buttonTextBorderIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'buttonTextBorderInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + buttonTextBorderlessDisabledIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'buttonTextBorderlessDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + buttonTextBorderlessDisabledIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'buttonTextBorderlessDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + buttonTextBorderlessDisabledIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'buttonTextBorderlessDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + buttonTextBorderlessDisabledIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'buttonTextBorderlessDisabledInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + buttonTextBorderlessIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'buttonTextBorderlessInt', + value: value, + )); + }); + } + + QueryBuilder + buttonTextBorderlessIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'buttonTextBorderlessInt', + value: value, + )); + }); + } + + QueryBuilder + buttonTextBorderlessIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'buttonTextBorderlessInt', + value: value, + )); + }); + } + + QueryBuilder + buttonTextBorderlessIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'buttonTextBorderlessInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + buttonTextDisabledIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'buttonTextDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + buttonTextDisabledIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'buttonTextDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + buttonTextDisabledIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'buttonTextDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + buttonTextDisabledIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'buttonTextDisabledInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + buttonTextPrimaryDisabledIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'buttonTextPrimaryDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + buttonTextPrimaryDisabledIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'buttonTextPrimaryDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + buttonTextPrimaryDisabledIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'buttonTextPrimaryDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + buttonTextPrimaryDisabledIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'buttonTextPrimaryDisabledInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + buttonTextPrimaryIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'buttonTextPrimaryInt', + value: value, + )); + }); + } + + QueryBuilder + buttonTextPrimaryIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'buttonTextPrimaryInt', + value: value, + )); + }); + } + + QueryBuilder + buttonTextPrimaryIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'buttonTextPrimaryInt', + value: value, + )); + }); + } + + QueryBuilder + buttonTextPrimaryIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'buttonTextPrimaryInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + buttonTextSecondaryDisabledIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'buttonTextSecondaryDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + buttonTextSecondaryDisabledIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'buttonTextSecondaryDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + buttonTextSecondaryDisabledIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'buttonTextSecondaryDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + buttonTextSecondaryDisabledIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'buttonTextSecondaryDisabledInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + buttonTextSecondaryIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'buttonTextSecondaryInt', + value: value, + )); + }); + } + + QueryBuilder + buttonTextSecondaryIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'buttonTextSecondaryInt', + value: value, + )); + }); + } + + QueryBuilder + buttonTextSecondaryIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'buttonTextSecondaryInt', + value: value, + )); + }); + } + + QueryBuilder + buttonTextSecondaryIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'buttonTextSecondaryInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + checkboxBGCheckedIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'checkboxBGCheckedInt', + value: value, + )); + }); + } + + QueryBuilder + checkboxBGCheckedIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'checkboxBGCheckedInt', + value: value, + )); + }); + } + + QueryBuilder + checkboxBGCheckedIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'checkboxBGCheckedInt', + value: value, + )); + }); + } + + QueryBuilder + checkboxBGCheckedIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'checkboxBGCheckedInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + checkboxBGDisabledIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'checkboxBGDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + checkboxBGDisabledIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'checkboxBGDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + checkboxBGDisabledIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'checkboxBGDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + checkboxBGDisabledIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'checkboxBGDisabledInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + checkboxBorderEmptyIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'checkboxBorderEmptyInt', + value: value, + )); + }); + } + + QueryBuilder + checkboxBorderEmptyIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'checkboxBorderEmptyInt', + value: value, + )); + }); + } + + QueryBuilder + checkboxBorderEmptyIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'checkboxBorderEmptyInt', + value: value, + )); + }); + } + + QueryBuilder + checkboxBorderEmptyIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'checkboxBorderEmptyInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + checkboxIconCheckedIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'checkboxIconCheckedInt', + value: value, + )); + }); + } + + QueryBuilder + checkboxIconCheckedIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'checkboxIconCheckedInt', + value: value, + )); + }); + } + + QueryBuilder + checkboxIconCheckedIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'checkboxIconCheckedInt', + value: value, + )); + }); + } + + QueryBuilder + checkboxIconCheckedIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'checkboxIconCheckedInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + checkboxIconDisabledIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'checkboxIconDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + checkboxIconDisabledIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'checkboxIconDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + checkboxIconDisabledIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'checkboxIconDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + checkboxIconDisabledIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'checkboxIconDisabledInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + checkboxTextLabelIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'checkboxTextLabelInt', + value: value, + )); + }); + } + + QueryBuilder + checkboxTextLabelIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'checkboxTextLabelInt', + value: value, + )); + }); + } + + QueryBuilder + checkboxTextLabelIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'checkboxTextLabelInt', + value: value, + )); + }); + } + + QueryBuilder + checkboxTextLabelIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'checkboxTextLabelInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + coinColorsJsonStringEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'coinColorsJsonString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinColorsJsonStringGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'coinColorsJsonString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinColorsJsonStringLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'coinColorsJsonString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinColorsJsonStringBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'coinColorsJsonString', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinColorsJsonStringStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'coinColorsJsonString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinColorsJsonStringEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'coinColorsJsonString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinColorsJsonStringContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'coinColorsJsonString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinColorsJsonStringMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'coinColorsJsonString', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinColorsJsonStringIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'coinColorsJsonString', + value: '', + )); + }); + } + + QueryBuilder + coinColorsJsonStringIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'coinColorsJsonString', + value: '', + )); + }); + } + + QueryBuilder + currencyListItemBGIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'currencyListItemBGInt', + value: value, + )); + }); + } + + QueryBuilder + currencyListItemBGIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'currencyListItemBGInt', + value: value, + )); + }); + } + + QueryBuilder + currencyListItemBGIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'currencyListItemBGInt', + value: value, + )); + }); + } + + QueryBuilder + currencyListItemBGIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'currencyListItemBGInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + customTextButtonDisabledTextIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'customTextButtonDisabledTextInt', + value: value, + )); + }); + } + + QueryBuilder + customTextButtonDisabledTextIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'customTextButtonDisabledTextInt', + value: value, + )); + }); + } + + QueryBuilder + customTextButtonDisabledTextIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'customTextButtonDisabledTextInt', + value: value, + )); + }); + } + + QueryBuilder + customTextButtonDisabledTextIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'customTextButtonDisabledTextInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + customTextButtonEnabledTextIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'customTextButtonEnabledTextInt', + value: value, + )); + }); + } + + QueryBuilder + customTextButtonEnabledTextIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'customTextButtonEnabledTextInt', + value: value, + )); + }); + } + + QueryBuilder + customTextButtonEnabledTextIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'customTextButtonEnabledTextInt', + value: value, + )); + }); + } + + QueryBuilder + customTextButtonEnabledTextIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'customTextButtonEnabledTextInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + ethTagBGIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'ethTagBGInt', + value: value, + )); + }); + } + + QueryBuilder + ethTagBGIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'ethTagBGInt', + value: value, + )); + }); + } + + QueryBuilder + ethTagBGIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'ethTagBGInt', + value: value, + )); + }); + } + + QueryBuilder + ethTagBGIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'ethTagBGInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + ethTagTextIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'ethTagTextInt', + value: value, + )); + }); + } + + QueryBuilder + ethTagTextIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'ethTagTextInt', + value: value, + )); + }); + } + + QueryBuilder + ethTagTextIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'ethTagTextInt', + value: value, + )); + }); + } + + QueryBuilder + ethTagTextIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'ethTagTextInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + ethWalletTagBGIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'ethWalletTagBGInt', + value: value, + )); + }); + } + + QueryBuilder + ethWalletTagBGIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'ethWalletTagBGInt', + value: value, + )); + }); + } + + QueryBuilder + ethWalletTagBGIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'ethWalletTagBGInt', + value: value, + )); + }); + } + + QueryBuilder + ethWalletTagBGIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'ethWalletTagBGInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + ethWalletTagTextIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'ethWalletTagTextInt', + value: value, + )); + }); + } + + QueryBuilder + ethWalletTagTextIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'ethWalletTagTextInt', + value: value, + )); + }); + } + + QueryBuilder + ethWalletTagTextIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'ethWalletTagTextInt', + value: value, + )); + }); + } + + QueryBuilder + ethWalletTagTextIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'ethWalletTagTextInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + favoriteStarActiveIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'favoriteStarActiveInt', + value: value, + )); + }); + } + + QueryBuilder + favoriteStarActiveIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'favoriteStarActiveInt', + value: value, + )); + }); + } + + QueryBuilder + favoriteStarActiveIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'favoriteStarActiveInt', + value: value, + )); + }); + } + + QueryBuilder + favoriteStarActiveIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'favoriteStarActiveInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + favoriteStarInactiveIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'favoriteStarInactiveInt', + value: value, + )); + }); + } + + QueryBuilder + favoriteStarInactiveIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'favoriteStarInactiveInt', + value: value, + )); + }); + } + + QueryBuilder + favoriteStarInactiveIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'favoriteStarInactiveInt', + value: value, + )); + }); + } + + QueryBuilder + favoriteStarInactiveIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'favoriteStarInactiveInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + gradientBackgroundStringIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'gradientBackgroundString', + )); + }); + } + + QueryBuilder + gradientBackgroundStringIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'gradientBackgroundString', + )); + }); + } + + QueryBuilder + gradientBackgroundStringEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'gradientBackgroundString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + gradientBackgroundStringGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'gradientBackgroundString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + gradientBackgroundStringLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'gradientBackgroundString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + gradientBackgroundStringBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'gradientBackgroundString', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + gradientBackgroundStringStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'gradientBackgroundString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + gradientBackgroundStringEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'gradientBackgroundString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + gradientBackgroundStringContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'gradientBackgroundString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + gradientBackgroundStringMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'gradientBackgroundString', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + gradientBackgroundStringIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'gradientBackgroundString', + value: '', + )); + }); + } + + QueryBuilder + gradientBackgroundStringIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'gradientBackgroundString', + value: '', + )); + }); + } + + QueryBuilder + highlightIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'highlightInt', + value: value, + )); + }); + } + + QueryBuilder + highlightIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'highlightInt', + value: value, + )); + }); + } + + QueryBuilder + highlightIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'highlightInt', + value: value, + )); + }); + } + + QueryBuilder + highlightIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'highlightInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + homeViewButtonBarBoxShadowStringIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'homeViewButtonBarBoxShadowString', + )); + }); + } + + QueryBuilder + homeViewButtonBarBoxShadowStringIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'homeViewButtonBarBoxShadowString', + )); + }); + } + + QueryBuilder + homeViewButtonBarBoxShadowStringEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'homeViewButtonBarBoxShadowString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + homeViewButtonBarBoxShadowStringGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'homeViewButtonBarBoxShadowString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + homeViewButtonBarBoxShadowStringLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'homeViewButtonBarBoxShadowString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + homeViewButtonBarBoxShadowStringBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'homeViewButtonBarBoxShadowString', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + homeViewButtonBarBoxShadowStringStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'homeViewButtonBarBoxShadowString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + homeViewButtonBarBoxShadowStringEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'homeViewButtonBarBoxShadowString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + homeViewButtonBarBoxShadowStringContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'homeViewButtonBarBoxShadowString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + homeViewButtonBarBoxShadowStringMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'homeViewButtonBarBoxShadowString', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + homeViewButtonBarBoxShadowStringIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'homeViewButtonBarBoxShadowString', + value: '', + )); + }); + } + + QueryBuilder + homeViewButtonBarBoxShadowStringIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'homeViewButtonBarBoxShadowString', + value: '', + )); + }); + } + + QueryBuilder idEqualTo( + Id value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idGreaterThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idLessThan( + Id value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'id', + value: value, + )); + }); + } + + QueryBuilder idBetween( + Id lower, + Id upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'id', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + infoItemBGIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'infoItemBGInt', + value: value, + )); + }); + } + + QueryBuilder + infoItemBGIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'infoItemBGInt', + value: value, + )); + }); + } + + QueryBuilder + infoItemBGIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'infoItemBGInt', + value: value, + )); + }); + } + + QueryBuilder + infoItemBGIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'infoItemBGInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + infoItemIconsIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'infoItemIconsInt', + value: value, + )); + }); + } + + QueryBuilder + infoItemIconsIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'infoItemIconsInt', + value: value, + )); + }); + } + + QueryBuilder + infoItemIconsIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'infoItemIconsInt', + value: value, + )); + }); + } + + QueryBuilder + infoItemIconsIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'infoItemIconsInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + infoItemLabelIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'infoItemLabelInt', + value: value, + )); + }); + } + + QueryBuilder + infoItemLabelIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'infoItemLabelInt', + value: value, + )); + }); + } + + QueryBuilder + infoItemLabelIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'infoItemLabelInt', + value: value, + )); + }); + } + + QueryBuilder + infoItemLabelIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'infoItemLabelInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + infoItemTextIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'infoItemTextInt', + value: value, + )); + }); + } + + QueryBuilder + infoItemTextIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'infoItemTextInt', + value: value, + )); + }); + } + + QueryBuilder + infoItemTextIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'infoItemTextInt', + value: value, + )); + }); + } + + QueryBuilder + infoItemTextIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'infoItemTextInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + loadingOverlayTextColorIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'loadingOverlayTextColorInt', + value: value, + )); + }); + } + + QueryBuilder + loadingOverlayTextColorIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'loadingOverlayTextColorInt', + value: value, + )); + }); + } + + QueryBuilder + loadingOverlayTextColorIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'loadingOverlayTextColorInt', + value: value, + )); + }); + } + + QueryBuilder + loadingOverlayTextColorIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'loadingOverlayTextColorInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + myStackContactIconBGIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'myStackContactIconBGInt', + value: value, + )); + }); + } + + QueryBuilder + myStackContactIconBGIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'myStackContactIconBGInt', + value: value, + )); + }); + } + + QueryBuilder + myStackContactIconBGIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'myStackContactIconBGInt', + value: value, + )); + }); + } + + QueryBuilder + myStackContactIconBGIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'myStackContactIconBGInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder nameEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'name', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'name', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'name', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder nameIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'name', + value: '', + )); + }); + } + + QueryBuilder nameIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'name', + value: '', + )); + }); + } + + QueryBuilder + numberBackDefaultIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'numberBackDefaultInt', + value: value, + )); + }); + } + + QueryBuilder + numberBackDefaultIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'numberBackDefaultInt', + value: value, + )); + }); + } + + QueryBuilder + numberBackDefaultIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'numberBackDefaultInt', + value: value, + )); + }); + } + + QueryBuilder + numberBackDefaultIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'numberBackDefaultInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + numberTextDefaultIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'numberTextDefaultInt', + value: value, + )); + }); + } + + QueryBuilder + numberTextDefaultIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'numberTextDefaultInt', + value: value, + )); + }); + } + + QueryBuilder + numberTextDefaultIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'numberTextDefaultInt', + value: value, + )); + }); + } + + QueryBuilder + numberTextDefaultIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'numberTextDefaultInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + numpadBackDefaultIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'numpadBackDefaultInt', + value: value, + )); + }); + } + + QueryBuilder + numpadBackDefaultIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'numpadBackDefaultInt', + value: value, + )); + }); + } + + QueryBuilder + numpadBackDefaultIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'numpadBackDefaultInt', + value: value, + )); + }); + } + + QueryBuilder + numpadBackDefaultIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'numpadBackDefaultInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + numpadTextDefaultIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'numpadTextDefaultInt', + value: value, + )); + }); + } + + QueryBuilder + numpadTextDefaultIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'numpadTextDefaultInt', + value: value, + )); + }); + } + + QueryBuilder + numpadTextDefaultIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'numpadTextDefaultInt', + value: value, + )); + }); + } + + QueryBuilder + numpadTextDefaultIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'numpadTextDefaultInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder overlayIntEqualTo( + int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'overlayInt', + value: value, + )); + }); + } + + QueryBuilder + overlayIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'overlayInt', + value: value, + )); + }); + } + + QueryBuilder + overlayIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'overlayInt', + value: value, + )); + }); + } + + QueryBuilder overlayIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'overlayInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder popupBGIntEqualTo( + int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'popupBGInt', + value: value, + )); + }); + } + + QueryBuilder + popupBGIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'popupBGInt', + value: value, + )); + }); + } + + QueryBuilder + popupBGIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'popupBGInt', + value: value, + )); + }); + } + + QueryBuilder popupBGIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'popupBGInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + radioButtonBorderDisabledIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'radioButtonBorderDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonBorderDisabledIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'radioButtonBorderDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonBorderDisabledIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'radioButtonBorderDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonBorderDisabledIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'radioButtonBorderDisabledInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + radioButtonBorderEnabledIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'radioButtonBorderEnabledInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonBorderEnabledIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'radioButtonBorderEnabledInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonBorderEnabledIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'radioButtonBorderEnabledInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonBorderEnabledIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'radioButtonBorderEnabledInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + radioButtonIconBorderDisabledIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'radioButtonIconBorderDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonIconBorderDisabledIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'radioButtonIconBorderDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonIconBorderDisabledIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'radioButtonIconBorderDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonIconBorderDisabledIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'radioButtonIconBorderDisabledInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + radioButtonIconBorderIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'radioButtonIconBorderInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonIconBorderIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'radioButtonIconBorderInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonIconBorderIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'radioButtonIconBorderInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonIconBorderIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'radioButtonIconBorderInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + radioButtonIconCircleIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'radioButtonIconCircleInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonIconCircleIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'radioButtonIconCircleInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonIconCircleIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'radioButtonIconCircleInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonIconCircleIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'radioButtonIconCircleInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + radioButtonIconEnabledIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'radioButtonIconEnabledInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonIconEnabledIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'radioButtonIconEnabledInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonIconEnabledIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'radioButtonIconEnabledInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonIconEnabledIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'radioButtonIconEnabledInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + radioButtonLabelDisabledIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'radioButtonLabelDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonLabelDisabledIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'radioButtonLabelDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonLabelDisabledIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'radioButtonLabelDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonLabelDisabledIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'radioButtonLabelDisabledInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + radioButtonLabelEnabledIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'radioButtonLabelEnabledInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonLabelEnabledIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'radioButtonLabelEnabledInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonLabelEnabledIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'radioButtonLabelEnabledInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonLabelEnabledIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'radioButtonLabelEnabledInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + radioButtonTextDisabledIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'radioButtonTextDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonTextDisabledIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'radioButtonTextDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonTextDisabledIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'radioButtonTextDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonTextDisabledIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'radioButtonTextDisabledInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + radioButtonTextEnabledIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'radioButtonTextEnabledInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonTextEnabledIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'radioButtonTextEnabledInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonTextEnabledIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'radioButtonTextEnabledInt', + value: value, + )); + }); + } + + QueryBuilder + radioButtonTextEnabledIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'radioButtonTextEnabledInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + rateTypeToggleColorOffIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'rateTypeToggleColorOffInt', + value: value, + )); + }); + } + + QueryBuilder + rateTypeToggleColorOffIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'rateTypeToggleColorOffInt', + value: value, + )); + }); + } + + QueryBuilder + rateTypeToggleColorOffIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'rateTypeToggleColorOffInt', + value: value, + )); + }); + } + + QueryBuilder + rateTypeToggleColorOffIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'rateTypeToggleColorOffInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + rateTypeToggleColorOnIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'rateTypeToggleColorOnInt', + value: value, + )); + }); + } + + QueryBuilder + rateTypeToggleColorOnIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'rateTypeToggleColorOnInt', + value: value, + )); + }); + } + + QueryBuilder + rateTypeToggleColorOnIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'rateTypeToggleColorOnInt', + value: value, + )); + }); + } + + QueryBuilder + rateTypeToggleColorOnIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'rateTypeToggleColorOnInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + rateTypeToggleDesktopColorOffIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'rateTypeToggleDesktopColorOffInt', + value: value, + )); + }); + } + + QueryBuilder + rateTypeToggleDesktopColorOffIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'rateTypeToggleDesktopColorOffInt', + value: value, + )); + }); + } + + QueryBuilder + rateTypeToggleDesktopColorOffIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'rateTypeToggleDesktopColorOffInt', + value: value, + )); + }); + } + + QueryBuilder + rateTypeToggleDesktopColorOffIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'rateTypeToggleDesktopColorOffInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + rateTypeToggleDesktopColorOnIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'rateTypeToggleDesktopColorOnInt', + value: value, + )); + }); + } + + QueryBuilder + rateTypeToggleDesktopColorOnIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'rateTypeToggleDesktopColorOnInt', + value: value, + )); + }); + } + + QueryBuilder + rateTypeToggleDesktopColorOnIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'rateTypeToggleDesktopColorOnInt', + value: value, + )); + }); + } + + QueryBuilder + rateTypeToggleDesktopColorOnIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'rateTypeToggleDesktopColorOnInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + settingsIconBack2IntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'settingsIconBack2Int', + value: value, + )); + }); + } + + QueryBuilder + settingsIconBack2IntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'settingsIconBack2Int', + value: value, + )); + }); + } + + QueryBuilder + settingsIconBack2IntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'settingsIconBack2Int', + value: value, + )); + }); + } + + QueryBuilder + settingsIconBack2IntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'settingsIconBack2Int', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + settingsIconBackIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'settingsIconBackInt', + value: value, + )); + }); + } + + QueryBuilder + settingsIconBackIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'settingsIconBackInt', + value: value, + )); + }); + } + + QueryBuilder + settingsIconBackIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'settingsIconBackInt', + value: value, + )); + }); + } + + QueryBuilder + settingsIconBackIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'settingsIconBackInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + settingsIconElementIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'settingsIconElementInt', + value: value, + )); + }); + } + + QueryBuilder + settingsIconElementIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'settingsIconElementInt', + value: value, + )); + }); + } + + QueryBuilder + settingsIconElementIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'settingsIconElementInt', + value: value, + )); + }); + } + + QueryBuilder + settingsIconElementIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'settingsIconElementInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + settingsIconIconIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'settingsIconIconInt', + value: value, + )); + }); + } + + QueryBuilder + settingsIconIconIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'settingsIconIconInt', + value: value, + )); + }); + } + + QueryBuilder + settingsIconIconIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'settingsIconIconInt', + value: value, + )); + }); + } + + QueryBuilder + settingsIconIconIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'settingsIconIconInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + settingsItem2ActiveBGIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'settingsItem2ActiveBGInt', + value: value, + )); + }); + } + + QueryBuilder + settingsItem2ActiveBGIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'settingsItem2ActiveBGInt', + value: value, + )); + }); + } + + QueryBuilder + settingsItem2ActiveBGIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'settingsItem2ActiveBGInt', + value: value, + )); + }); + } + + QueryBuilder + settingsItem2ActiveBGIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'settingsItem2ActiveBGInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + settingsItem2ActiveSubIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'settingsItem2ActiveSubInt', + value: value, + )); + }); + } + + QueryBuilder + settingsItem2ActiveSubIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'settingsItem2ActiveSubInt', + value: value, + )); + }); + } + + QueryBuilder + settingsItem2ActiveSubIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'settingsItem2ActiveSubInt', + value: value, + )); + }); + } + + QueryBuilder + settingsItem2ActiveSubIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'settingsItem2ActiveSubInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + settingsItem2ActiveTextIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'settingsItem2ActiveTextInt', + value: value, + )); + }); + } + + QueryBuilder + settingsItem2ActiveTextIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'settingsItem2ActiveTextInt', + value: value, + )); + }); + } + + QueryBuilder + settingsItem2ActiveTextIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'settingsItem2ActiveTextInt', + value: value, + )); + }); + } + + QueryBuilder + settingsItem2ActiveTextIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'settingsItem2ActiveTextInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder shadowIntEqualTo( + int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'shadowInt', + value: value, + )); + }); + } + + QueryBuilder + shadowIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'shadowInt', + value: value, + )); + }); + } + + QueryBuilder shadowIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'shadowInt', + value: value, + )); + }); + } + + QueryBuilder shadowIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'shadowInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + snackBarBackErrorIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'snackBarBackErrorInt', + value: value, + )); + }); + } + + QueryBuilder + snackBarBackErrorIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'snackBarBackErrorInt', + value: value, + )); + }); + } + + QueryBuilder + snackBarBackErrorIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'snackBarBackErrorInt', + value: value, + )); + }); + } + + QueryBuilder + snackBarBackErrorIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'snackBarBackErrorInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + snackBarBackInfoIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'snackBarBackInfoInt', + value: value, + )); + }); + } + + QueryBuilder + snackBarBackInfoIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'snackBarBackInfoInt', + value: value, + )); + }); + } + + QueryBuilder + snackBarBackInfoIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'snackBarBackInfoInt', + value: value, + )); + }); + } + + QueryBuilder + snackBarBackInfoIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'snackBarBackInfoInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + snackBarBackSuccessIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'snackBarBackSuccessInt', + value: value, + )); + }); + } + + QueryBuilder + snackBarBackSuccessIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'snackBarBackSuccessInt', + value: value, + )); + }); + } + + QueryBuilder + snackBarBackSuccessIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'snackBarBackSuccessInt', + value: value, + )); + }); + } + + QueryBuilder + snackBarBackSuccessIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'snackBarBackSuccessInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + snackBarTextErrorIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'snackBarTextErrorInt', + value: value, + )); + }); + } + + QueryBuilder + snackBarTextErrorIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'snackBarTextErrorInt', + value: value, + )); + }); + } + + QueryBuilder + snackBarTextErrorIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'snackBarTextErrorInt', + value: value, + )); + }); + } + + QueryBuilder + snackBarTextErrorIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'snackBarTextErrorInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + snackBarTextInfoIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'snackBarTextInfoInt', + value: value, + )); + }); + } + + QueryBuilder + snackBarTextInfoIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'snackBarTextInfoInt', + value: value, + )); + }); + } + + QueryBuilder + snackBarTextInfoIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'snackBarTextInfoInt', + value: value, + )); + }); + } + + QueryBuilder + snackBarTextInfoIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'snackBarTextInfoInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + snackBarTextSuccessIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'snackBarTextSuccessInt', + value: value, + )); + }); + } + + QueryBuilder + snackBarTextSuccessIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'snackBarTextSuccessInt', + value: value, + )); + }); + } + + QueryBuilder + snackBarTextSuccessIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'snackBarTextSuccessInt', + value: value, + )); + }); + } + + QueryBuilder + snackBarTextSuccessIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'snackBarTextSuccessInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder splashIntEqualTo( + int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'splashInt', + value: value, + )); + }); + } + + QueryBuilder + splashIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'splashInt', + value: value, + )); + }); + } + + QueryBuilder splashIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'splashInt', + value: value, + )); + }); + } + + QueryBuilder splashIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'splashInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + stackWalletBGIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'stackWalletBGInt', + value: value, + )); + }); + } + + QueryBuilder + stackWalletBGIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'stackWalletBGInt', + value: value, + )); + }); + } + + QueryBuilder + stackWalletBGIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'stackWalletBGInt', + value: value, + )); + }); + } + + QueryBuilder + stackWalletBGIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'stackWalletBGInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + stackWalletBottomIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'stackWalletBottomInt', + value: value, + )); + }); + } + + QueryBuilder + stackWalletBottomIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'stackWalletBottomInt', + value: value, + )); + }); + } + + QueryBuilder + stackWalletBottomIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'stackWalletBottomInt', + value: value, + )); + }); + } + + QueryBuilder + stackWalletBottomIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'stackWalletBottomInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + stackWalletMidIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'stackWalletMidInt', + value: value, + )); + }); + } + + QueryBuilder + stackWalletMidIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'stackWalletMidInt', + value: value, + )); + }); + } + + QueryBuilder + stackWalletMidIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'stackWalletMidInt', + value: value, + )); + }); + } + + QueryBuilder + stackWalletMidIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'stackWalletMidInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + standardBoxShadowStringEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'standardBoxShadowString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + standardBoxShadowStringGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'standardBoxShadowString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + standardBoxShadowStringLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'standardBoxShadowString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + standardBoxShadowStringBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'standardBoxShadowString', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + standardBoxShadowStringStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'standardBoxShadowString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + standardBoxShadowStringEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'standardBoxShadowString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + standardBoxShadowStringContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'standardBoxShadowString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + standardBoxShadowStringMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'standardBoxShadowString', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + standardBoxShadowStringIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'standardBoxShadowString', + value: '', + )); + }); + } + + QueryBuilder + standardBoxShadowStringIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'standardBoxShadowString', + value: '', + )); + }); + } + + QueryBuilder + stepIndicatorBGCheckIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'stepIndicatorBGCheckInt', + value: value, + )); + }); + } + + QueryBuilder + stepIndicatorBGCheckIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'stepIndicatorBGCheckInt', + value: value, + )); + }); + } + + QueryBuilder + stepIndicatorBGCheckIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'stepIndicatorBGCheckInt', + value: value, + )); + }); + } + + QueryBuilder + stepIndicatorBGCheckIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'stepIndicatorBGCheckInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + stepIndicatorBGInactiveIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'stepIndicatorBGInactiveInt', + value: value, + )); + }); + } + + QueryBuilder + stepIndicatorBGInactiveIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'stepIndicatorBGInactiveInt', + value: value, + )); + }); + } + + QueryBuilder + stepIndicatorBGInactiveIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'stepIndicatorBGInactiveInt', + value: value, + )); + }); + } + + QueryBuilder + stepIndicatorBGInactiveIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'stepIndicatorBGInactiveInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + stepIndicatorBGLinesInactiveIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'stepIndicatorBGLinesInactiveInt', + value: value, + )); + }); + } + + QueryBuilder + stepIndicatorBGLinesInactiveIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'stepIndicatorBGLinesInactiveInt', + value: value, + )); + }); + } + + QueryBuilder + stepIndicatorBGLinesInactiveIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'stepIndicatorBGLinesInactiveInt', + value: value, + )); + }); + } + + QueryBuilder + stepIndicatorBGLinesInactiveIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'stepIndicatorBGLinesInactiveInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + stepIndicatorBGLinesIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'stepIndicatorBGLinesInt', + value: value, + )); + }); + } + + QueryBuilder + stepIndicatorBGLinesIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'stepIndicatorBGLinesInt', + value: value, + )); + }); + } + + QueryBuilder + stepIndicatorBGLinesIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'stepIndicatorBGLinesInt', + value: value, + )); + }); + } + + QueryBuilder + stepIndicatorBGLinesIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'stepIndicatorBGLinesInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + stepIndicatorBGNumberIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'stepIndicatorBGNumberInt', + value: value, + )); + }); + } + + QueryBuilder + stepIndicatorBGNumberIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'stepIndicatorBGNumberInt', + value: value, + )); + }); + } + + QueryBuilder + stepIndicatorBGNumberIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'stepIndicatorBGNumberInt', + value: value, + )); + }); + } + + QueryBuilder + stepIndicatorBGNumberIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'stepIndicatorBGNumberInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + stepIndicatorIconInactiveIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'stepIndicatorIconInactiveInt', + value: value, + )); + }); + } + + QueryBuilder + stepIndicatorIconInactiveIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'stepIndicatorIconInactiveInt', + value: value, + )); + }); + } + + QueryBuilder + stepIndicatorIconInactiveIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'stepIndicatorIconInactiveInt', + value: value, + )); + }); + } + + QueryBuilder + stepIndicatorIconInactiveIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'stepIndicatorIconInactiveInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + stepIndicatorIconNumberIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'stepIndicatorIconNumberInt', + value: value, + )); + }); + } + + QueryBuilder + stepIndicatorIconNumberIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'stepIndicatorIconNumberInt', + value: value, + )); + }); + } + + QueryBuilder + stepIndicatorIconNumberIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'stepIndicatorIconNumberInt', + value: value, + )); + }); + } + + QueryBuilder + stepIndicatorIconNumberIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'stepIndicatorIconNumberInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + stepIndicatorIconTextIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'stepIndicatorIconTextInt', + value: value, + )); + }); + } + + QueryBuilder + stepIndicatorIconTextIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'stepIndicatorIconTextInt', + value: value, + )); + }); + } + + QueryBuilder + stepIndicatorIconTextIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'stepIndicatorIconTextInt', + value: value, + )); + }); + } + + QueryBuilder + stepIndicatorIconTextIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'stepIndicatorIconTextInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + switchBGDisabledIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'switchBGDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + switchBGDisabledIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'switchBGDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + switchBGDisabledIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'switchBGDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + switchBGDisabledIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'switchBGDisabledInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + switchBGOffIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'switchBGOffInt', + value: value, + )); + }); + } + + QueryBuilder + switchBGOffIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'switchBGOffInt', + value: value, + )); + }); + } + + QueryBuilder + switchBGOffIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'switchBGOffInt', + value: value, + )); + }); + } + + QueryBuilder + switchBGOffIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'switchBGOffInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + switchBGOnIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'switchBGOnInt', + value: value, + )); + }); + } + + QueryBuilder + switchBGOnIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'switchBGOnInt', + value: value, + )); + }); + } + + QueryBuilder + switchBGOnIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'switchBGOnInt', + value: value, + )); + }); + } + + QueryBuilder + switchBGOnIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'switchBGOnInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + switchCircleDisabledIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'switchCircleDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + switchCircleDisabledIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'switchCircleDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + switchCircleDisabledIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'switchCircleDisabledInt', + value: value, + )); + }); + } + + QueryBuilder + switchCircleDisabledIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'switchCircleDisabledInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + switchCircleOffIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'switchCircleOffInt', + value: value, + )); + }); + } + + QueryBuilder + switchCircleOffIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'switchCircleOffInt', + value: value, + )); + }); + } + + QueryBuilder + switchCircleOffIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'switchCircleOffInt', + value: value, + )); + }); + } + + QueryBuilder + switchCircleOffIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'switchCircleOffInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + switchCircleOnIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'switchCircleOnInt', + value: value, + )); + }); + } + + QueryBuilder + switchCircleOnIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'switchCircleOnInt', + value: value, + )); + }); + } + + QueryBuilder + switchCircleOnIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'switchCircleOnInt', + value: value, + )); + }); + } + + QueryBuilder + switchCircleOnIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'switchCircleOnInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textConfirmTotalAmountIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textConfirmTotalAmountInt', + value: value, + )); + }); + } + + QueryBuilder + textConfirmTotalAmountIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textConfirmTotalAmountInt', + value: value, + )); + }); + } + + QueryBuilder + textConfirmTotalAmountIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textConfirmTotalAmountInt', + value: value, + )); + }); + } + + QueryBuilder + textConfirmTotalAmountIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textConfirmTotalAmountInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textDark2IntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textDark2Int', + value: value, + )); + }); + } + + QueryBuilder + textDark2IntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textDark2Int', + value: value, + )); + }); + } + + QueryBuilder + textDark2IntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textDark2Int', + value: value, + )); + }); + } + + QueryBuilder + textDark2IntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textDark2Int', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textDark3IntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textDark3Int', + value: value, + )); + }); + } + + QueryBuilder + textDark3IntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textDark3Int', + value: value, + )); + }); + } + + QueryBuilder + textDark3IntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textDark3Int', + value: value, + )); + }); + } + + QueryBuilder + textDark3IntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textDark3Int', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textDarkIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textDarkInt', + value: value, + )); + }); + } + + QueryBuilder + textDarkIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textDarkInt', + value: value, + )); + }); + } + + QueryBuilder + textDarkIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textDarkInt', + value: value, + )); + }); + } + + QueryBuilder + textDarkIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textDarkInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textErrorIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textErrorInt', + value: value, + )); + }); + } + + QueryBuilder + textErrorIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textErrorInt', + value: value, + )); + }); + } + + QueryBuilder + textErrorIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textErrorInt', + value: value, + )); + }); + } + + QueryBuilder + textErrorIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textErrorInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textFavoriteCardIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textFavoriteCardInt', + value: value, + )); + }); + } + + QueryBuilder + textFavoriteCardIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textFavoriteCardInt', + value: value, + )); + }); + } + + QueryBuilder + textFavoriteCardIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textFavoriteCardInt', + value: value, + )); + }); + } + + QueryBuilder + textFavoriteCardIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textFavoriteCardInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textFieldActiveBGIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textFieldActiveBGInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldActiveBGIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textFieldActiveBGInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldActiveBGIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textFieldActiveBGInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldActiveBGIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textFieldActiveBGInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textFieldActiveLabelIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textFieldActiveLabelInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldActiveLabelIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textFieldActiveLabelInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldActiveLabelIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textFieldActiveLabelInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldActiveLabelIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textFieldActiveLabelInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textFieldActiveSearchIconLeftIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textFieldActiveSearchIconLeftInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldActiveSearchIconLeftIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textFieldActiveSearchIconLeftInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldActiveSearchIconLeftIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textFieldActiveSearchIconLeftInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldActiveSearchIconLeftIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textFieldActiveSearchIconLeftInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textFieldActiveSearchIconRightIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textFieldActiveSearchIconRightInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldActiveSearchIconRightIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textFieldActiveSearchIconRightInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldActiveSearchIconRightIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textFieldActiveSearchIconRightInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldActiveSearchIconRightIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textFieldActiveSearchIconRightInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textFieldActiveTextIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textFieldActiveTextInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldActiveTextIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textFieldActiveTextInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldActiveTextIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textFieldActiveTextInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldActiveTextIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textFieldActiveTextInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textFieldDefaultBGIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textFieldDefaultBGInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldDefaultBGIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textFieldDefaultBGInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldDefaultBGIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textFieldDefaultBGInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldDefaultBGIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textFieldDefaultBGInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textFieldDefaultSearchIconLeftIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textFieldDefaultSearchIconLeftInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldDefaultSearchIconLeftIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textFieldDefaultSearchIconLeftInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldDefaultSearchIconLeftIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textFieldDefaultSearchIconLeftInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldDefaultSearchIconLeftIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textFieldDefaultSearchIconLeftInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textFieldDefaultSearchIconRightIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textFieldDefaultSearchIconRightInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldDefaultSearchIconRightIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textFieldDefaultSearchIconRightInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldDefaultSearchIconRightIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textFieldDefaultSearchIconRightInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldDefaultSearchIconRightIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textFieldDefaultSearchIconRightInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textFieldDefaultTextIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textFieldDefaultTextInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldDefaultTextIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textFieldDefaultTextInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldDefaultTextIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textFieldDefaultTextInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldDefaultTextIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textFieldDefaultTextInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textFieldErrorBGIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textFieldErrorBGInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldErrorBGIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textFieldErrorBGInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldErrorBGIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textFieldErrorBGInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldErrorBGIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textFieldErrorBGInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textFieldErrorBorderIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textFieldErrorBorderInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldErrorBorderIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textFieldErrorBorderInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldErrorBorderIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textFieldErrorBorderInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldErrorBorderIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textFieldErrorBorderInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textFieldErrorLabelIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textFieldErrorLabelInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldErrorLabelIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textFieldErrorLabelInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldErrorLabelIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textFieldErrorLabelInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldErrorLabelIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textFieldErrorLabelInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textFieldErrorSearchIconLeftIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textFieldErrorSearchIconLeftInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldErrorSearchIconLeftIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textFieldErrorSearchIconLeftInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldErrorSearchIconLeftIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textFieldErrorSearchIconLeftInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldErrorSearchIconLeftIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textFieldErrorSearchIconLeftInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textFieldErrorSearchIconRightIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textFieldErrorSearchIconRightInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldErrorSearchIconRightIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textFieldErrorSearchIconRightInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldErrorSearchIconRightIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textFieldErrorSearchIconRightInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldErrorSearchIconRightIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textFieldErrorSearchIconRightInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textFieldErrorTextIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textFieldErrorTextInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldErrorTextIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textFieldErrorTextInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldErrorTextIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textFieldErrorTextInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldErrorTextIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textFieldErrorTextInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textFieldSuccessBGIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textFieldSuccessBGInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldSuccessBGIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textFieldSuccessBGInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldSuccessBGIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textFieldSuccessBGInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldSuccessBGIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textFieldSuccessBGInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textFieldSuccessBorderIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textFieldSuccessBorderInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldSuccessBorderIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textFieldSuccessBorderInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldSuccessBorderIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textFieldSuccessBorderInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldSuccessBorderIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textFieldSuccessBorderInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textFieldSuccessLabelIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textFieldSuccessLabelInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldSuccessLabelIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textFieldSuccessLabelInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldSuccessLabelIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textFieldSuccessLabelInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldSuccessLabelIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textFieldSuccessLabelInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textFieldSuccessSearchIconLeftIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textFieldSuccessSearchIconLeftInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldSuccessSearchIconLeftIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textFieldSuccessSearchIconLeftInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldSuccessSearchIconLeftIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textFieldSuccessSearchIconLeftInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldSuccessSearchIconLeftIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textFieldSuccessSearchIconLeftInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textFieldSuccessSearchIconRightIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textFieldSuccessSearchIconRightInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldSuccessSearchIconRightIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textFieldSuccessSearchIconRightInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldSuccessSearchIconRightIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textFieldSuccessSearchIconRightInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldSuccessSearchIconRightIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textFieldSuccessSearchIconRightInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textFieldSuccessTextIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textFieldSuccessTextInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldSuccessTextIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textFieldSuccessTextInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldSuccessTextIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textFieldSuccessTextInt', + value: value, + )); + }); + } + + QueryBuilder + textFieldSuccessTextIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textFieldSuccessTextInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textRestoreIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textRestoreInt', + value: value, + )); + }); + } + + QueryBuilder + textRestoreIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textRestoreInt', + value: value, + )); + }); + } + + QueryBuilder + textRestoreIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textRestoreInt', + value: value, + )); + }); + } + + QueryBuilder + textRestoreIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textRestoreInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textSelectedWordTableItemIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textSelectedWordTableItemInt', + value: value, + )); + }); + } + + QueryBuilder + textSelectedWordTableItemIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textSelectedWordTableItemInt', + value: value, + )); + }); + } + + QueryBuilder + textSelectedWordTableItemIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textSelectedWordTableItemInt', + value: value, + )); + }); + } + + QueryBuilder + textSelectedWordTableItemIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textSelectedWordTableItemInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textSubtitle1IntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textSubtitle1Int', + value: value, + )); + }); + } + + QueryBuilder + textSubtitle1IntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textSubtitle1Int', + value: value, + )); + }); + } + + QueryBuilder + textSubtitle1IntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textSubtitle1Int', + value: value, + )); + }); + } + + QueryBuilder + textSubtitle1IntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textSubtitle1Int', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textSubtitle2IntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textSubtitle2Int', + value: value, + )); + }); + } + + QueryBuilder + textSubtitle2IntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textSubtitle2Int', + value: value, + )); + }); + } + + QueryBuilder + textSubtitle2IntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textSubtitle2Int', + value: value, + )); + }); + } + + QueryBuilder + textSubtitle2IntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textSubtitle2Int', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textSubtitle3IntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textSubtitle3Int', + value: value, + )); + }); + } + + QueryBuilder + textSubtitle3IntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textSubtitle3Int', + value: value, + )); + }); + } + + QueryBuilder + textSubtitle3IntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textSubtitle3Int', + value: value, + )); + }); + } + + QueryBuilder + textSubtitle3IntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textSubtitle3Int', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textSubtitle4IntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textSubtitle4Int', + value: value, + )); + }); + } + + QueryBuilder + textSubtitle4IntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textSubtitle4Int', + value: value, + )); + }); + } + + QueryBuilder + textSubtitle4IntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textSubtitle4Int', + value: value, + )); + }); + } + + QueryBuilder + textSubtitle4IntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textSubtitle4Int', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textSubtitle5IntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textSubtitle5Int', + value: value, + )); + }); + } + + QueryBuilder + textSubtitle5IntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textSubtitle5Int', + value: value, + )); + }); + } + + QueryBuilder + textSubtitle5IntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textSubtitle5Int', + value: value, + )); + }); + } + + QueryBuilder + textSubtitle5IntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textSubtitle5Int', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textSubtitle6IntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textSubtitle6Int', + value: value, + )); + }); + } + + QueryBuilder + textSubtitle6IntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textSubtitle6Int', + value: value, + )); + }); + } + + QueryBuilder + textSubtitle6IntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textSubtitle6Int', + value: value, + )); + }); + } + + QueryBuilder + textSubtitle6IntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textSubtitle6Int', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + textWhiteIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'textWhiteInt', + value: value, + )); + }); + } + + QueryBuilder + textWhiteIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'textWhiteInt', + value: value, + )); + }); + } + + QueryBuilder + textWhiteIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'textWhiteInt', + value: value, + )); + }); + } + + QueryBuilder + textWhiteIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'textWhiteInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder themeIdEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'themeId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themeIdGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'themeId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder themeIdLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'themeId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder themeIdBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'themeId', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder themeIdStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'themeId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder themeIdEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'themeId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder themeIdContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'themeId', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder themeIdMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'themeId', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder themeIdIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'themeId', + value: '', + )); + }); + } + + QueryBuilder + themeIdIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'themeId', + value: '', + )); + }); + } + + QueryBuilder + tokenSummaryBGIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'tokenSummaryBGInt', + value: value, + )); + }); + } + + QueryBuilder + tokenSummaryBGIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'tokenSummaryBGInt', + value: value, + )); + }); + } + + QueryBuilder + tokenSummaryBGIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'tokenSummaryBGInt', + value: value, + )); + }); + } + + QueryBuilder + tokenSummaryBGIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'tokenSummaryBGInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + tokenSummaryButtonBGIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'tokenSummaryButtonBGInt', + value: value, + )); + }); + } + + QueryBuilder + tokenSummaryButtonBGIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'tokenSummaryButtonBGInt', + value: value, + )); + }); + } + + QueryBuilder + tokenSummaryButtonBGIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'tokenSummaryButtonBGInt', + value: value, + )); + }); + } + + QueryBuilder + tokenSummaryButtonBGIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'tokenSummaryButtonBGInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + tokenSummaryIconIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'tokenSummaryIconInt', + value: value, + )); + }); + } + + QueryBuilder + tokenSummaryIconIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'tokenSummaryIconInt', + value: value, + )); + }); + } + + QueryBuilder + tokenSummaryIconIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'tokenSummaryIconInt', + value: value, + )); + }); + } + + QueryBuilder + tokenSummaryIconIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'tokenSummaryIconInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + tokenSummaryTextPrimaryIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'tokenSummaryTextPrimaryInt', + value: value, + )); + }); + } + + QueryBuilder + tokenSummaryTextPrimaryIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'tokenSummaryTextPrimaryInt', + value: value, + )); + }); + } + + QueryBuilder + tokenSummaryTextPrimaryIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'tokenSummaryTextPrimaryInt', + value: value, + )); + }); + } + + QueryBuilder + tokenSummaryTextPrimaryIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'tokenSummaryTextPrimaryInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + tokenSummaryTextSecondaryIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'tokenSummaryTextSecondaryInt', + value: value, + )); + }); + } + + QueryBuilder + tokenSummaryTextSecondaryIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'tokenSummaryTextSecondaryInt', + value: value, + )); + }); + } + + QueryBuilder + tokenSummaryTextSecondaryIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'tokenSummaryTextSecondaryInt', + value: value, + )); + }); + } + + QueryBuilder + tokenSummaryTextSecondaryIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'tokenSummaryTextSecondaryInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + topNavIconGreenIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'topNavIconGreenInt', + value: value, + )); + }); + } + + QueryBuilder + topNavIconGreenIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'topNavIconGreenInt', + value: value, + )); + }); + } + + QueryBuilder + topNavIconGreenIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'topNavIconGreenInt', + value: value, + )); + }); + } + + QueryBuilder + topNavIconGreenIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'topNavIconGreenInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + topNavIconPrimaryIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'topNavIconPrimaryInt', + value: value, + )); + }); + } + + QueryBuilder + topNavIconPrimaryIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'topNavIconPrimaryInt', + value: value, + )); + }); + } + + QueryBuilder + topNavIconPrimaryIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'topNavIconPrimaryInt', + value: value, + )); + }); + } + + QueryBuilder + topNavIconPrimaryIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'topNavIconPrimaryInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + topNavIconRedIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'topNavIconRedInt', + value: value, + )); + }); + } + + QueryBuilder + topNavIconRedIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'topNavIconRedInt', + value: value, + )); + }); + } + + QueryBuilder + topNavIconRedIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'topNavIconRedInt', + value: value, + )); + }); + } + + QueryBuilder + topNavIconRedIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'topNavIconRedInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + topNavIconYellowIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'topNavIconYellowInt', + value: value, + )); + }); + } + + QueryBuilder + topNavIconYellowIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'topNavIconYellowInt', + value: value, + )); + }); + } + + QueryBuilder + topNavIconYellowIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'topNavIconYellowInt', + value: value, + )); + }); + } + + QueryBuilder + topNavIconYellowIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'topNavIconYellowInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder versionIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'version', + )); + }); + } + + QueryBuilder + versionIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'version', + )); + }); + } + + QueryBuilder versionEqualTo( + int? value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'version', + value: value, + )); + }); + } + + QueryBuilder + versionGreaterThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'version', + value: value, + )); + }); + } + + QueryBuilder versionLessThan( + int? value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'version', + value: value, + )); + }); + } + + QueryBuilder versionBetween( + int? lower, + int? upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'version', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + warningBackgroundIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'warningBackgroundInt', + value: value, + )); + }); + } + + QueryBuilder + warningBackgroundIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'warningBackgroundInt', + value: value, + )); + }); + } + + QueryBuilder + warningBackgroundIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'warningBackgroundInt', + value: value, + )); + }); + } + + QueryBuilder + warningBackgroundIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'warningBackgroundInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } + + QueryBuilder + warningForegroundIntEqualTo(int value) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'warningForegroundInt', + value: value, + )); + }); + } + + QueryBuilder + warningForegroundIntGreaterThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'warningForegroundInt', + value: value, + )); + }); + } + + QueryBuilder + warningForegroundIntLessThan( + int value, { + bool include = false, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'warningForegroundInt', + value: value, + )); + }); + } + + QueryBuilder + warningForegroundIntBetween( + int lower, + int upper, { + bool includeLower = true, + bool includeUpper = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'warningForegroundInt', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + )); + }); + } +} + +extension StackThemeQueryObject + on QueryBuilder { + QueryBuilder assetsV1( + FilterQuery q) { + return QueryBuilder.apply(this, (query) { + return query.object(q, r'assets'); + }); + } + + QueryBuilder assetsV2( + FilterQuery q) { + return QueryBuilder.apply(this, (query) { + return query.object(q, r'assetsV2'); + }); + } +} + +extension StackThemeQueryLinks + on QueryBuilder {} + +extension StackThemeQuerySortBy + on QueryBuilder { + QueryBuilder + sortByAccentColorBlueInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'accentColorBlueInt', Sort.asc); + }); + } + + QueryBuilder + sortByAccentColorBlueIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'accentColorBlueInt', Sort.desc); + }); + } + + QueryBuilder + sortByAccentColorDarkInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'accentColorDarkInt', Sort.asc); + }); + } + + QueryBuilder + sortByAccentColorDarkIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'accentColorDarkInt', Sort.desc); + }); + } + + QueryBuilder + sortByAccentColorGreenInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'accentColorGreenInt', Sort.asc); + }); + } + + QueryBuilder + sortByAccentColorGreenIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'accentColorGreenInt', Sort.desc); + }); + } + + QueryBuilder + sortByAccentColorOrangeInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'accentColorOrangeInt', Sort.asc); + }); + } + + QueryBuilder + sortByAccentColorOrangeIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'accentColorOrangeInt', Sort.desc); + }); + } + + QueryBuilder sortByAccentColorRedInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'accentColorRedInt', Sort.asc); + }); + } + + QueryBuilder + sortByAccentColorRedIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'accentColorRedInt', Sort.desc); + }); + } + + QueryBuilder + sortByAccentColorYellowInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'accentColorYellowInt', Sort.asc); + }); + } + + QueryBuilder + sortByAccentColorYellowIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'accentColorYellowInt', Sort.desc); + }); + } + + QueryBuilder + sortByBackgroundAppBarInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'backgroundAppBarInt', Sort.asc); + }); + } + + QueryBuilder + sortByBackgroundAppBarIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'backgroundAppBarInt', Sort.desc); + }); + } + + QueryBuilder sortByBackgroundInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'backgroundInt', Sort.asc); + }); + } + + QueryBuilder sortByBackgroundIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'backgroundInt', Sort.desc); + }); + } + + QueryBuilder sortByBottomNavBackInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'bottomNavBackInt', Sort.asc); + }); + } + + QueryBuilder + sortByBottomNavBackIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'bottomNavBackInt', Sort.desc); + }); + } + + QueryBuilder + sortByBottomNavIconBackInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'bottomNavIconBackInt', Sort.asc); + }); + } + + QueryBuilder + sortByBottomNavIconBackIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'bottomNavIconBackInt', Sort.desc); + }); + } + + QueryBuilder + sortByBottomNavIconIconHighlightedInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'bottomNavIconIconHighlightedInt', Sort.asc); + }); + } + + QueryBuilder + sortByBottomNavIconIconHighlightedIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'bottomNavIconIconHighlightedInt', Sort.desc); + }); + } + + QueryBuilder + sortByBottomNavIconIconInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'bottomNavIconIconInt', Sort.asc); + }); + } + + QueryBuilder + sortByBottomNavIconIconIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'bottomNavIconIconInt', Sort.desc); + }); + } + + QueryBuilder + sortByBottomNavShadowInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'bottomNavShadowInt', Sort.asc); + }); + } + + QueryBuilder + sortByBottomNavShadowIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'bottomNavShadowInt', Sort.desc); + }); + } + + QueryBuilder sortByBottomNavTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'bottomNavTextInt', Sort.asc); + }); + } + + QueryBuilder + sortByBottomNavTextIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'bottomNavTextInt', Sort.desc); + }); + } + + QueryBuilder sortByBrightnessString() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'brightnessString', Sort.asc); + }); + } + + QueryBuilder + sortByBrightnessStringDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'brightnessString', Sort.desc); + }); + } + + QueryBuilder + sortByButtonBackBorderDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackBorderDisabledInt', Sort.asc); + }); + } + + QueryBuilder + sortByButtonBackBorderDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackBorderDisabledInt', Sort.desc); + }); + } + + QueryBuilder + sortByButtonBackBorderInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackBorderInt', Sort.asc); + }); + } + + QueryBuilder + sortByButtonBackBorderIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackBorderInt', Sort.desc); + }); + } + + QueryBuilder + sortByButtonBackBorderSecondaryDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackBorderSecondaryDisabledInt', Sort.asc); + }); + } + + QueryBuilder + sortByButtonBackBorderSecondaryDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy( + r'buttonBackBorderSecondaryDisabledInt', Sort.desc); + }); + } + + QueryBuilder + sortByButtonBackBorderSecondaryInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackBorderSecondaryInt', Sort.asc); + }); + } + + QueryBuilder + sortByButtonBackBorderSecondaryIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackBorderSecondaryInt', Sort.desc); + }); + } + + QueryBuilder + sortByButtonBackPrimaryDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackPrimaryDisabledInt', Sort.asc); + }); + } + + QueryBuilder + sortByButtonBackPrimaryDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackPrimaryDisabledInt', Sort.desc); + }); + } + + QueryBuilder + sortByButtonBackPrimaryInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackPrimaryInt', Sort.asc); + }); + } + + QueryBuilder + sortByButtonBackPrimaryIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackPrimaryInt', Sort.desc); + }); + } + + QueryBuilder + sortByButtonBackSecondaryDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackSecondaryDisabledInt', Sort.asc); + }); + } + + QueryBuilder + sortByButtonBackSecondaryDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackSecondaryDisabledInt', Sort.desc); + }); + } + + QueryBuilder + sortByButtonBackSecondaryInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackSecondaryInt', Sort.asc); + }); + } + + QueryBuilder + sortByButtonBackSecondaryIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackSecondaryInt', Sort.desc); + }); + } + + QueryBuilder + sortByButtonTextBorderInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextBorderInt', Sort.asc); + }); + } + + QueryBuilder + sortByButtonTextBorderIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextBorderInt', Sort.desc); + }); + } + + QueryBuilder + sortByButtonTextBorderlessDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextBorderlessDisabledInt', Sort.asc); + }); + } + + QueryBuilder + sortByButtonTextBorderlessDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextBorderlessDisabledInt', Sort.desc); + }); + } + + QueryBuilder + sortByButtonTextBorderlessInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextBorderlessInt', Sort.asc); + }); + } + + QueryBuilder + sortByButtonTextBorderlessIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextBorderlessInt', Sort.desc); + }); + } + + QueryBuilder + sortByButtonTextDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextDisabledInt', Sort.asc); + }); + } + + QueryBuilder + sortByButtonTextDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextDisabledInt', Sort.desc); + }); + } + + QueryBuilder + sortByButtonTextPrimaryDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextPrimaryDisabledInt', Sort.asc); + }); + } + + QueryBuilder + sortByButtonTextPrimaryDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextPrimaryDisabledInt', Sort.desc); + }); + } + + QueryBuilder + sortByButtonTextPrimaryInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextPrimaryInt', Sort.asc); + }); + } + + QueryBuilder + sortByButtonTextPrimaryIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextPrimaryInt', Sort.desc); + }); + } + + QueryBuilder + sortByButtonTextSecondaryDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextSecondaryDisabledInt', Sort.asc); + }); + } + + QueryBuilder + sortByButtonTextSecondaryDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextSecondaryDisabledInt', Sort.desc); + }); + } + + QueryBuilder + sortByButtonTextSecondaryInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextSecondaryInt', Sort.asc); + }); + } + + QueryBuilder + sortByButtonTextSecondaryIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextSecondaryInt', Sort.desc); + }); + } + + QueryBuilder + sortByCheckboxBGCheckedInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checkboxBGCheckedInt', Sort.asc); + }); + } + + QueryBuilder + sortByCheckboxBGCheckedIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checkboxBGCheckedInt', Sort.desc); + }); + } + + QueryBuilder + sortByCheckboxBGDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checkboxBGDisabledInt', Sort.asc); + }); + } + + QueryBuilder + sortByCheckboxBGDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checkboxBGDisabledInt', Sort.desc); + }); + } + + QueryBuilder + sortByCheckboxBorderEmptyInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checkboxBorderEmptyInt', Sort.asc); + }); + } + + QueryBuilder + sortByCheckboxBorderEmptyIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checkboxBorderEmptyInt', Sort.desc); + }); + } + + QueryBuilder + sortByCheckboxIconCheckedInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checkboxIconCheckedInt', Sort.asc); + }); + } + + QueryBuilder + sortByCheckboxIconCheckedIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checkboxIconCheckedInt', Sort.desc); + }); + } + + QueryBuilder + sortByCheckboxIconDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checkboxIconDisabledInt', Sort.asc); + }); + } + + QueryBuilder + sortByCheckboxIconDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checkboxIconDisabledInt', Sort.desc); + }); + } + + QueryBuilder + sortByCheckboxTextLabelInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checkboxTextLabelInt', Sort.asc); + }); + } + + QueryBuilder + sortByCheckboxTextLabelIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checkboxTextLabelInt', Sort.desc); + }); + } + + QueryBuilder + sortByCoinColorsJsonString() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'coinColorsJsonString', Sort.asc); + }); + } + + QueryBuilder + sortByCoinColorsJsonStringDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'coinColorsJsonString', Sort.desc); + }); + } + + QueryBuilder + sortByCurrencyListItemBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'currencyListItemBGInt', Sort.asc); + }); + } + + QueryBuilder + sortByCurrencyListItemBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'currencyListItemBGInt', Sort.desc); + }); + } + + QueryBuilder + sortByCustomTextButtonDisabledTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'customTextButtonDisabledTextInt', Sort.asc); + }); + } + + QueryBuilder + sortByCustomTextButtonDisabledTextIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'customTextButtonDisabledTextInt', Sort.desc); + }); + } + + QueryBuilder + sortByCustomTextButtonEnabledTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'customTextButtonEnabledTextInt', Sort.asc); + }); + } + + QueryBuilder + sortByCustomTextButtonEnabledTextIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'customTextButtonEnabledTextInt', Sort.desc); + }); + } + + QueryBuilder sortByEthTagBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ethTagBGInt', Sort.asc); + }); + } + + QueryBuilder sortByEthTagBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ethTagBGInt', Sort.desc); + }); + } + + QueryBuilder sortByEthTagTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ethTagTextInt', Sort.asc); + }); + } + + QueryBuilder sortByEthTagTextIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ethTagTextInt', Sort.desc); + }); + } + + QueryBuilder sortByEthWalletTagBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ethWalletTagBGInt', Sort.asc); + }); + } + + QueryBuilder + sortByEthWalletTagBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ethWalletTagBGInt', Sort.desc); + }); + } + + QueryBuilder + sortByEthWalletTagTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ethWalletTagTextInt', Sort.asc); + }); + } + + QueryBuilder + sortByEthWalletTagTextIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ethWalletTagTextInt', Sort.desc); + }); + } + + QueryBuilder + sortByFavoriteStarActiveInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'favoriteStarActiveInt', Sort.asc); + }); + } + + QueryBuilder + sortByFavoriteStarActiveIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'favoriteStarActiveInt', Sort.desc); + }); + } + + QueryBuilder + sortByFavoriteStarInactiveInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'favoriteStarInactiveInt', Sort.asc); + }); + } + + QueryBuilder + sortByFavoriteStarInactiveIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'favoriteStarInactiveInt', Sort.desc); + }); + } + + QueryBuilder + sortByGradientBackgroundString() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'gradientBackgroundString', Sort.asc); + }); + } + + QueryBuilder + sortByGradientBackgroundStringDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'gradientBackgroundString', Sort.desc); + }); + } + + QueryBuilder sortByHighlightInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'highlightInt', Sort.asc); + }); + } + + QueryBuilder sortByHighlightIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'highlightInt', Sort.desc); + }); + } + + QueryBuilder + sortByHomeViewButtonBarBoxShadowString() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'homeViewButtonBarBoxShadowString', Sort.asc); + }); + } + + QueryBuilder + sortByHomeViewButtonBarBoxShadowStringDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'homeViewButtonBarBoxShadowString', Sort.desc); + }); + } + + QueryBuilder sortByInfoItemBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'infoItemBGInt', Sort.asc); + }); + } + + QueryBuilder sortByInfoItemBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'infoItemBGInt', Sort.desc); + }); + } + + QueryBuilder sortByInfoItemIconsInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'infoItemIconsInt', Sort.asc); + }); + } + + QueryBuilder + sortByInfoItemIconsIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'infoItemIconsInt', Sort.desc); + }); + } + + QueryBuilder sortByInfoItemLabelInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'infoItemLabelInt', Sort.asc); + }); + } + + QueryBuilder + sortByInfoItemLabelIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'infoItemLabelInt', Sort.desc); + }); + } + + QueryBuilder sortByInfoItemTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'infoItemTextInt', Sort.asc); + }); + } + + QueryBuilder + sortByInfoItemTextIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'infoItemTextInt', Sort.desc); + }); + } + + QueryBuilder + sortByLoadingOverlayTextColorInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'loadingOverlayTextColorInt', Sort.asc); + }); + } + + QueryBuilder + sortByLoadingOverlayTextColorIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'loadingOverlayTextColorInt', Sort.desc); + }); + } + + QueryBuilder + sortByMyStackContactIconBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'myStackContactIconBGInt', Sort.asc); + }); + } + + QueryBuilder + sortByMyStackContactIconBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'myStackContactIconBGInt', Sort.desc); + }); + } + + QueryBuilder sortByName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.asc); + }); + } + + QueryBuilder sortByNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.desc); + }); + } + + QueryBuilder + sortByNumberBackDefaultInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'numberBackDefaultInt', Sort.asc); + }); + } + + QueryBuilder + sortByNumberBackDefaultIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'numberBackDefaultInt', Sort.desc); + }); + } + + QueryBuilder + sortByNumberTextDefaultInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'numberTextDefaultInt', Sort.asc); + }); + } + + QueryBuilder + sortByNumberTextDefaultIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'numberTextDefaultInt', Sort.desc); + }); + } + + QueryBuilder + sortByNumpadBackDefaultInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'numpadBackDefaultInt', Sort.asc); + }); + } + + QueryBuilder + sortByNumpadBackDefaultIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'numpadBackDefaultInt', Sort.desc); + }); + } + + QueryBuilder + sortByNumpadTextDefaultInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'numpadTextDefaultInt', Sort.asc); + }); + } + + QueryBuilder + sortByNumpadTextDefaultIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'numpadTextDefaultInt', Sort.desc); + }); + } + + QueryBuilder sortByOverlayInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'overlayInt', Sort.asc); + }); + } + + QueryBuilder sortByOverlayIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'overlayInt', Sort.desc); + }); + } + + QueryBuilder sortByPopupBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'popupBGInt', Sort.asc); + }); + } + + QueryBuilder sortByPopupBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'popupBGInt', Sort.desc); + }); + } + + QueryBuilder + sortByRadioButtonBorderDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonBorderDisabledInt', Sort.asc); + }); + } + + QueryBuilder + sortByRadioButtonBorderDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonBorderDisabledInt', Sort.desc); + }); + } + + QueryBuilder + sortByRadioButtonBorderEnabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonBorderEnabledInt', Sort.asc); + }); + } + + QueryBuilder + sortByRadioButtonBorderEnabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonBorderEnabledInt', Sort.desc); + }); + } + + QueryBuilder + sortByRadioButtonIconBorderDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonIconBorderDisabledInt', Sort.asc); + }); + } + + QueryBuilder + sortByRadioButtonIconBorderDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonIconBorderDisabledInt', Sort.desc); + }); + } + + QueryBuilder + sortByRadioButtonIconBorderInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonIconBorderInt', Sort.asc); + }); + } + + QueryBuilder + sortByRadioButtonIconBorderIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonIconBorderInt', Sort.desc); + }); + } + + QueryBuilder + sortByRadioButtonIconCircleInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonIconCircleInt', Sort.asc); + }); + } + + QueryBuilder + sortByRadioButtonIconCircleIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonIconCircleInt', Sort.desc); + }); + } + + QueryBuilder + sortByRadioButtonIconEnabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonIconEnabledInt', Sort.asc); + }); + } + + QueryBuilder + sortByRadioButtonIconEnabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonIconEnabledInt', Sort.desc); + }); + } + + QueryBuilder + sortByRadioButtonLabelDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonLabelDisabledInt', Sort.asc); + }); + } + + QueryBuilder + sortByRadioButtonLabelDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonLabelDisabledInt', Sort.desc); + }); + } + + QueryBuilder + sortByRadioButtonLabelEnabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonLabelEnabledInt', Sort.asc); + }); + } + + QueryBuilder + sortByRadioButtonLabelEnabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonLabelEnabledInt', Sort.desc); + }); + } + + QueryBuilder + sortByRadioButtonTextDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonTextDisabledInt', Sort.asc); + }); + } + + QueryBuilder + sortByRadioButtonTextDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonTextDisabledInt', Sort.desc); + }); + } + + QueryBuilder + sortByRadioButtonTextEnabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonTextEnabledInt', Sort.asc); + }); + } + + QueryBuilder + sortByRadioButtonTextEnabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonTextEnabledInt', Sort.desc); + }); + } + + QueryBuilder + sortByRateTypeToggleColorOffInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'rateTypeToggleColorOffInt', Sort.asc); + }); + } + + QueryBuilder + sortByRateTypeToggleColorOffIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'rateTypeToggleColorOffInt', Sort.desc); + }); + } + + QueryBuilder + sortByRateTypeToggleColorOnInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'rateTypeToggleColorOnInt', Sort.asc); + }); + } + + QueryBuilder + sortByRateTypeToggleColorOnIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'rateTypeToggleColorOnInt', Sort.desc); + }); + } + + QueryBuilder + sortByRateTypeToggleDesktopColorOffInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'rateTypeToggleDesktopColorOffInt', Sort.asc); + }); + } + + QueryBuilder + sortByRateTypeToggleDesktopColorOffIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'rateTypeToggleDesktopColorOffInt', Sort.desc); + }); + } + + QueryBuilder + sortByRateTypeToggleDesktopColorOnInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'rateTypeToggleDesktopColorOnInt', Sort.asc); + }); + } + + QueryBuilder + sortByRateTypeToggleDesktopColorOnIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'rateTypeToggleDesktopColorOnInt', Sort.desc); + }); + } + + QueryBuilder + sortBySettingsIconBack2Int() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsIconBack2Int', Sort.asc); + }); + } + + QueryBuilder + sortBySettingsIconBack2IntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsIconBack2Int', Sort.desc); + }); + } + + QueryBuilder + sortBySettingsIconBackInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsIconBackInt', Sort.asc); + }); + } + + QueryBuilder + sortBySettingsIconBackIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsIconBackInt', Sort.desc); + }); + } + + QueryBuilder + sortBySettingsIconElementInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsIconElementInt', Sort.asc); + }); + } + + QueryBuilder + sortBySettingsIconElementIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsIconElementInt', Sort.desc); + }); + } + + QueryBuilder + sortBySettingsIconIconInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsIconIconInt', Sort.asc); + }); + } + + QueryBuilder + sortBySettingsIconIconIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsIconIconInt', Sort.desc); + }); + } + + QueryBuilder + sortBySettingsItem2ActiveBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsItem2ActiveBGInt', Sort.asc); + }); + } + + QueryBuilder + sortBySettingsItem2ActiveBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsItem2ActiveBGInt', Sort.desc); + }); + } + + QueryBuilder + sortBySettingsItem2ActiveSubInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsItem2ActiveSubInt', Sort.asc); + }); + } + + QueryBuilder + sortBySettingsItem2ActiveSubIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsItem2ActiveSubInt', Sort.desc); + }); + } + + QueryBuilder + sortBySettingsItem2ActiveTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsItem2ActiveTextInt', Sort.asc); + }); + } + + QueryBuilder + sortBySettingsItem2ActiveTextIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsItem2ActiveTextInt', Sort.desc); + }); + } + + QueryBuilder sortByShadowInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shadowInt', Sort.asc); + }); + } + + QueryBuilder sortByShadowIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shadowInt', Sort.desc); + }); + } + + QueryBuilder + sortBySnackBarBackErrorInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'snackBarBackErrorInt', Sort.asc); + }); + } + + QueryBuilder + sortBySnackBarBackErrorIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'snackBarBackErrorInt', Sort.desc); + }); + } + + QueryBuilder + sortBySnackBarBackInfoInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'snackBarBackInfoInt', Sort.asc); + }); + } + + QueryBuilder + sortBySnackBarBackInfoIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'snackBarBackInfoInt', Sort.desc); + }); + } + + QueryBuilder + sortBySnackBarBackSuccessInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'snackBarBackSuccessInt', Sort.asc); + }); + } + + QueryBuilder + sortBySnackBarBackSuccessIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'snackBarBackSuccessInt', Sort.desc); + }); + } + + QueryBuilder + sortBySnackBarTextErrorInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'snackBarTextErrorInt', Sort.asc); + }); + } + + QueryBuilder + sortBySnackBarTextErrorIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'snackBarTextErrorInt', Sort.desc); + }); + } + + QueryBuilder + sortBySnackBarTextInfoInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'snackBarTextInfoInt', Sort.asc); + }); + } + + QueryBuilder + sortBySnackBarTextInfoIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'snackBarTextInfoInt', Sort.desc); + }); + } + + QueryBuilder + sortBySnackBarTextSuccessInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'snackBarTextSuccessInt', Sort.asc); + }); + } + + QueryBuilder + sortBySnackBarTextSuccessIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'snackBarTextSuccessInt', Sort.desc); + }); + } + + QueryBuilder sortBySplashInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'splashInt', Sort.asc); + }); + } + + QueryBuilder sortBySplashIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'splashInt', Sort.desc); + }); + } + + QueryBuilder sortByStackWalletBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stackWalletBGInt', Sort.asc); + }); + } + + QueryBuilder + sortByStackWalletBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stackWalletBGInt', Sort.desc); + }); + } + + QueryBuilder + sortByStackWalletBottomInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stackWalletBottomInt', Sort.asc); + }); + } + + QueryBuilder + sortByStackWalletBottomIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stackWalletBottomInt', Sort.desc); + }); + } + + QueryBuilder sortByStackWalletMidInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stackWalletMidInt', Sort.asc); + }); + } + + QueryBuilder + sortByStackWalletMidIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stackWalletMidInt', Sort.desc); + }); + } + + QueryBuilder + sortByStandardBoxShadowString() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'standardBoxShadowString', Sort.asc); + }); + } + + QueryBuilder + sortByStandardBoxShadowStringDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'standardBoxShadowString', Sort.desc); + }); + } + + QueryBuilder + sortByStepIndicatorBGCheckInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorBGCheckInt', Sort.asc); + }); + } + + QueryBuilder + sortByStepIndicatorBGCheckIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorBGCheckInt', Sort.desc); + }); + } + + QueryBuilder + sortByStepIndicatorBGInactiveInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorBGInactiveInt', Sort.asc); + }); + } + + QueryBuilder + sortByStepIndicatorBGInactiveIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorBGInactiveInt', Sort.desc); + }); + } + + QueryBuilder + sortByStepIndicatorBGLinesInactiveInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorBGLinesInactiveInt', Sort.asc); + }); + } + + QueryBuilder + sortByStepIndicatorBGLinesInactiveIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorBGLinesInactiveInt', Sort.desc); + }); + } + + QueryBuilder + sortByStepIndicatorBGLinesInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorBGLinesInt', Sort.asc); + }); + } + + QueryBuilder + sortByStepIndicatorBGLinesIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorBGLinesInt', Sort.desc); + }); + } + + QueryBuilder + sortByStepIndicatorBGNumberInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorBGNumberInt', Sort.asc); + }); + } + + QueryBuilder + sortByStepIndicatorBGNumberIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorBGNumberInt', Sort.desc); + }); + } + + QueryBuilder + sortByStepIndicatorIconInactiveInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorIconInactiveInt', Sort.asc); + }); + } + + QueryBuilder + sortByStepIndicatorIconInactiveIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorIconInactiveInt', Sort.desc); + }); + } + + QueryBuilder + sortByStepIndicatorIconNumberInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorIconNumberInt', Sort.asc); + }); + } + + QueryBuilder + sortByStepIndicatorIconNumberIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorIconNumberInt', Sort.desc); + }); + } + + QueryBuilder + sortByStepIndicatorIconTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorIconTextInt', Sort.asc); + }); + } + + QueryBuilder + sortByStepIndicatorIconTextIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorIconTextInt', Sort.desc); + }); + } + + QueryBuilder + sortBySwitchBGDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'switchBGDisabledInt', Sort.asc); + }); + } + + QueryBuilder + sortBySwitchBGDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'switchBGDisabledInt', Sort.desc); + }); + } + + QueryBuilder sortBySwitchBGOffInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'switchBGOffInt', Sort.asc); + }); + } + + QueryBuilder + sortBySwitchBGOffIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'switchBGOffInt', Sort.desc); + }); + } + + QueryBuilder sortBySwitchBGOnInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'switchBGOnInt', Sort.asc); + }); + } + + QueryBuilder sortBySwitchBGOnIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'switchBGOnInt', Sort.desc); + }); + } + + QueryBuilder + sortBySwitchCircleDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'switchCircleDisabledInt', Sort.asc); + }); + } + + QueryBuilder + sortBySwitchCircleDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'switchCircleDisabledInt', Sort.desc); + }); + } + + QueryBuilder + sortBySwitchCircleOffInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'switchCircleOffInt', Sort.asc); + }); + } + + QueryBuilder + sortBySwitchCircleOffIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'switchCircleOffInt', Sort.desc); + }); + } + + QueryBuilder sortBySwitchCircleOnInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'switchCircleOnInt', Sort.asc); + }); + } + + QueryBuilder + sortBySwitchCircleOnIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'switchCircleOnInt', Sort.desc); + }); + } + + QueryBuilder + sortByTextConfirmTotalAmountInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textConfirmTotalAmountInt', Sort.asc); + }); + } + + QueryBuilder + sortByTextConfirmTotalAmountIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textConfirmTotalAmountInt', Sort.desc); + }); + } + + QueryBuilder sortByTextDark2Int() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textDark2Int', Sort.asc); + }); + } + + QueryBuilder sortByTextDark2IntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textDark2Int', Sort.desc); + }); + } + + QueryBuilder sortByTextDark3Int() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textDark3Int', Sort.asc); + }); + } + + QueryBuilder sortByTextDark3IntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textDark3Int', Sort.desc); + }); + } + + QueryBuilder sortByTextDarkInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textDarkInt', Sort.asc); + }); + } + + QueryBuilder sortByTextDarkIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textDarkInt', Sort.desc); + }); + } + + QueryBuilder sortByTextErrorInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textErrorInt', Sort.asc); + }); + } + + QueryBuilder sortByTextErrorIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textErrorInt', Sort.desc); + }); + } + + QueryBuilder + sortByTextFavoriteCardInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFavoriteCardInt', Sort.asc); + }); + } + + QueryBuilder + sortByTextFavoriteCardIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFavoriteCardInt', Sort.desc); + }); + } + + QueryBuilder + sortByTextFieldActiveBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldActiveBGInt', Sort.asc); + }); + } + + QueryBuilder + sortByTextFieldActiveBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldActiveBGInt', Sort.desc); + }); + } + + QueryBuilder + sortByTextFieldActiveLabelInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldActiveLabelInt', Sort.asc); + }); + } + + QueryBuilder + sortByTextFieldActiveLabelIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldActiveLabelInt', Sort.desc); + }); + } + + QueryBuilder + sortByTextFieldActiveSearchIconLeftInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldActiveSearchIconLeftInt', Sort.asc); + }); + } + + QueryBuilder + sortByTextFieldActiveSearchIconLeftIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldActiveSearchIconLeftInt', Sort.desc); + }); + } + + QueryBuilder + sortByTextFieldActiveSearchIconRightInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldActiveSearchIconRightInt', Sort.asc); + }); + } + + QueryBuilder + sortByTextFieldActiveSearchIconRightIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldActiveSearchIconRightInt', Sort.desc); + }); + } + + QueryBuilder + sortByTextFieldActiveTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldActiveTextInt', Sort.asc); + }); + } + + QueryBuilder + sortByTextFieldActiveTextIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldActiveTextInt', Sort.desc); + }); + } + + QueryBuilder + sortByTextFieldDefaultBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldDefaultBGInt', Sort.asc); + }); + } + + QueryBuilder + sortByTextFieldDefaultBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldDefaultBGInt', Sort.desc); + }); + } + + QueryBuilder + sortByTextFieldDefaultSearchIconLeftInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldDefaultSearchIconLeftInt', Sort.asc); + }); + } + + QueryBuilder + sortByTextFieldDefaultSearchIconLeftIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldDefaultSearchIconLeftInt', Sort.desc); + }); + } + + QueryBuilder + sortByTextFieldDefaultSearchIconRightInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldDefaultSearchIconRightInt', Sort.asc); + }); + } + + QueryBuilder + sortByTextFieldDefaultSearchIconRightIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldDefaultSearchIconRightInt', Sort.desc); + }); + } + + QueryBuilder + sortByTextFieldDefaultTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldDefaultTextInt', Sort.asc); + }); + } + + QueryBuilder + sortByTextFieldDefaultTextIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldDefaultTextInt', Sort.desc); + }); + } + + QueryBuilder + sortByTextFieldErrorBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldErrorBGInt', Sort.asc); + }); + } + + QueryBuilder + sortByTextFieldErrorBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldErrorBGInt', Sort.desc); + }); + } + + QueryBuilder + sortByTextFieldErrorBorderInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldErrorBorderInt', Sort.asc); + }); + } + + QueryBuilder + sortByTextFieldErrorBorderIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldErrorBorderInt', Sort.desc); + }); + } + + QueryBuilder + sortByTextFieldErrorLabelInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldErrorLabelInt', Sort.asc); + }); + } + + QueryBuilder + sortByTextFieldErrorLabelIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldErrorLabelInt', Sort.desc); + }); + } + + QueryBuilder + sortByTextFieldErrorSearchIconLeftInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldErrorSearchIconLeftInt', Sort.asc); + }); + } + + QueryBuilder + sortByTextFieldErrorSearchIconLeftIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldErrorSearchIconLeftInt', Sort.desc); + }); + } + + QueryBuilder + sortByTextFieldErrorSearchIconRightInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldErrorSearchIconRightInt', Sort.asc); + }); + } + + QueryBuilder + sortByTextFieldErrorSearchIconRightIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldErrorSearchIconRightInt', Sort.desc); + }); + } + + QueryBuilder + sortByTextFieldErrorTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldErrorTextInt', Sort.asc); + }); + } + + QueryBuilder + sortByTextFieldErrorTextIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldErrorTextInt', Sort.desc); + }); + } + + QueryBuilder + sortByTextFieldSuccessBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldSuccessBGInt', Sort.asc); + }); + } + + QueryBuilder + sortByTextFieldSuccessBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldSuccessBGInt', Sort.desc); + }); + } + + QueryBuilder + sortByTextFieldSuccessBorderInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldSuccessBorderInt', Sort.asc); + }); + } + + QueryBuilder + sortByTextFieldSuccessBorderIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldSuccessBorderInt', Sort.desc); + }); + } + + QueryBuilder + sortByTextFieldSuccessLabelInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldSuccessLabelInt', Sort.asc); + }); + } + + QueryBuilder + sortByTextFieldSuccessLabelIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldSuccessLabelInt', Sort.desc); + }); + } + + QueryBuilder + sortByTextFieldSuccessSearchIconLeftInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldSuccessSearchIconLeftInt', Sort.asc); + }); + } + + QueryBuilder + sortByTextFieldSuccessSearchIconLeftIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldSuccessSearchIconLeftInt', Sort.desc); + }); + } + + QueryBuilder + sortByTextFieldSuccessSearchIconRightInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldSuccessSearchIconRightInt', Sort.asc); + }); + } + + QueryBuilder + sortByTextFieldSuccessSearchIconRightIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldSuccessSearchIconRightInt', Sort.desc); + }); + } + + QueryBuilder + sortByTextFieldSuccessTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldSuccessTextInt', Sort.asc); + }); + } + + QueryBuilder + sortByTextFieldSuccessTextIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldSuccessTextInt', Sort.desc); + }); + } + + QueryBuilder sortByTextRestoreInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textRestoreInt', Sort.asc); + }); + } + + QueryBuilder + sortByTextRestoreIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textRestoreInt', Sort.desc); + }); + } + + QueryBuilder + sortByTextSelectedWordTableItemInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSelectedWordTableItemInt', Sort.asc); + }); + } + + QueryBuilder + sortByTextSelectedWordTableItemIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSelectedWordTableItemInt', Sort.desc); + }); + } + + QueryBuilder sortByTextSubtitle1Int() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSubtitle1Int', Sort.asc); + }); + } + + QueryBuilder + sortByTextSubtitle1IntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSubtitle1Int', Sort.desc); + }); + } + + QueryBuilder sortByTextSubtitle2Int() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSubtitle2Int', Sort.asc); + }); + } + + QueryBuilder + sortByTextSubtitle2IntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSubtitle2Int', Sort.desc); + }); + } + + QueryBuilder sortByTextSubtitle3Int() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSubtitle3Int', Sort.asc); + }); + } + + QueryBuilder + sortByTextSubtitle3IntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSubtitle3Int', Sort.desc); + }); + } + + QueryBuilder sortByTextSubtitle4Int() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSubtitle4Int', Sort.asc); + }); + } + + QueryBuilder + sortByTextSubtitle4IntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSubtitle4Int', Sort.desc); + }); + } + + QueryBuilder sortByTextSubtitle5Int() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSubtitle5Int', Sort.asc); + }); + } + + QueryBuilder + sortByTextSubtitle5IntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSubtitle5Int', Sort.desc); + }); + } + + QueryBuilder sortByTextSubtitle6Int() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSubtitle6Int', Sort.asc); + }); + } + + QueryBuilder + sortByTextSubtitle6IntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSubtitle6Int', Sort.desc); + }); + } + + QueryBuilder sortByTextWhiteInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textWhiteInt', Sort.asc); + }); + } + + QueryBuilder sortByTextWhiteIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textWhiteInt', Sort.desc); + }); + } + + QueryBuilder sortByThemeId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'themeId', Sort.asc); + }); + } + + QueryBuilder sortByThemeIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'themeId', Sort.desc); + }); + } + + QueryBuilder sortByTokenSummaryBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'tokenSummaryBGInt', Sort.asc); + }); + } + + QueryBuilder + sortByTokenSummaryBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'tokenSummaryBGInt', Sort.desc); + }); + } + + QueryBuilder + sortByTokenSummaryButtonBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'tokenSummaryButtonBGInt', Sort.asc); + }); + } + + QueryBuilder + sortByTokenSummaryButtonBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'tokenSummaryButtonBGInt', Sort.desc); + }); + } + + QueryBuilder + sortByTokenSummaryIconInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'tokenSummaryIconInt', Sort.asc); + }); + } + + QueryBuilder + sortByTokenSummaryIconIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'tokenSummaryIconInt', Sort.desc); + }); + } + + QueryBuilder + sortByTokenSummaryTextPrimaryInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'tokenSummaryTextPrimaryInt', Sort.asc); + }); + } + + QueryBuilder + sortByTokenSummaryTextPrimaryIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'tokenSummaryTextPrimaryInt', Sort.desc); + }); + } + + QueryBuilder + sortByTokenSummaryTextSecondaryInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'tokenSummaryTextSecondaryInt', Sort.asc); + }); + } + + QueryBuilder + sortByTokenSummaryTextSecondaryIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'tokenSummaryTextSecondaryInt', Sort.desc); + }); + } + + QueryBuilder + sortByTopNavIconGreenInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'topNavIconGreenInt', Sort.asc); + }); + } + + QueryBuilder + sortByTopNavIconGreenIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'topNavIconGreenInt', Sort.desc); + }); + } + + QueryBuilder + sortByTopNavIconPrimaryInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'topNavIconPrimaryInt', Sort.asc); + }); + } + + QueryBuilder + sortByTopNavIconPrimaryIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'topNavIconPrimaryInt', Sort.desc); + }); + } + + QueryBuilder sortByTopNavIconRedInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'topNavIconRedInt', Sort.asc); + }); + } + + QueryBuilder + sortByTopNavIconRedIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'topNavIconRedInt', Sort.desc); + }); + } + + QueryBuilder + sortByTopNavIconYellowInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'topNavIconYellowInt', Sort.asc); + }); + } + + QueryBuilder + sortByTopNavIconYellowIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'topNavIconYellowInt', Sort.desc); + }); + } + + QueryBuilder sortByVersion() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'version', Sort.asc); + }); + } + + QueryBuilder sortByVersionDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'version', Sort.desc); + }); + } + + QueryBuilder + sortByWarningBackgroundInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'warningBackgroundInt', Sort.asc); + }); + } + + QueryBuilder + sortByWarningBackgroundIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'warningBackgroundInt', Sort.desc); + }); + } + + QueryBuilder + sortByWarningForegroundInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'warningForegroundInt', Sort.asc); + }); + } + + QueryBuilder + sortByWarningForegroundIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'warningForegroundInt', Sort.desc); + }); + } +} + +extension StackThemeQuerySortThenBy + on QueryBuilder { + QueryBuilder + thenByAccentColorBlueInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'accentColorBlueInt', Sort.asc); + }); + } + + QueryBuilder + thenByAccentColorBlueIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'accentColorBlueInt', Sort.desc); + }); + } + + QueryBuilder + thenByAccentColorDarkInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'accentColorDarkInt', Sort.asc); + }); + } + + QueryBuilder + thenByAccentColorDarkIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'accentColorDarkInt', Sort.desc); + }); + } + + QueryBuilder + thenByAccentColorGreenInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'accentColorGreenInt', Sort.asc); + }); + } + + QueryBuilder + thenByAccentColorGreenIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'accentColorGreenInt', Sort.desc); + }); + } + + QueryBuilder + thenByAccentColorOrangeInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'accentColorOrangeInt', Sort.asc); + }); + } + + QueryBuilder + thenByAccentColorOrangeIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'accentColorOrangeInt', Sort.desc); + }); + } + + QueryBuilder thenByAccentColorRedInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'accentColorRedInt', Sort.asc); + }); + } + + QueryBuilder + thenByAccentColorRedIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'accentColorRedInt', Sort.desc); + }); + } + + QueryBuilder + thenByAccentColorYellowInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'accentColorYellowInt', Sort.asc); + }); + } + + QueryBuilder + thenByAccentColorYellowIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'accentColorYellowInt', Sort.desc); + }); + } + + QueryBuilder + thenByBackgroundAppBarInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'backgroundAppBarInt', Sort.asc); + }); + } + + QueryBuilder + thenByBackgroundAppBarIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'backgroundAppBarInt', Sort.desc); + }); + } + + QueryBuilder thenByBackgroundInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'backgroundInt', Sort.asc); + }); + } + + QueryBuilder thenByBackgroundIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'backgroundInt', Sort.desc); + }); + } + + QueryBuilder thenByBottomNavBackInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'bottomNavBackInt', Sort.asc); + }); + } + + QueryBuilder + thenByBottomNavBackIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'bottomNavBackInt', Sort.desc); + }); + } + + QueryBuilder + thenByBottomNavIconBackInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'bottomNavIconBackInt', Sort.asc); + }); + } + + QueryBuilder + thenByBottomNavIconBackIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'bottomNavIconBackInt', Sort.desc); + }); + } + + QueryBuilder + thenByBottomNavIconIconHighlightedInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'bottomNavIconIconHighlightedInt', Sort.asc); + }); + } + + QueryBuilder + thenByBottomNavIconIconHighlightedIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'bottomNavIconIconHighlightedInt', Sort.desc); + }); + } + + QueryBuilder + thenByBottomNavIconIconInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'bottomNavIconIconInt', Sort.asc); + }); + } + + QueryBuilder + thenByBottomNavIconIconIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'bottomNavIconIconInt', Sort.desc); + }); + } + + QueryBuilder + thenByBottomNavShadowInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'bottomNavShadowInt', Sort.asc); + }); + } + + QueryBuilder + thenByBottomNavShadowIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'bottomNavShadowInt', Sort.desc); + }); + } + + QueryBuilder thenByBottomNavTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'bottomNavTextInt', Sort.asc); + }); + } + + QueryBuilder + thenByBottomNavTextIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'bottomNavTextInt', Sort.desc); + }); + } + + QueryBuilder thenByBrightnessString() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'brightnessString', Sort.asc); + }); + } + + QueryBuilder + thenByBrightnessStringDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'brightnessString', Sort.desc); + }); + } + + QueryBuilder + thenByButtonBackBorderDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackBorderDisabledInt', Sort.asc); + }); + } + + QueryBuilder + thenByButtonBackBorderDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackBorderDisabledInt', Sort.desc); + }); + } + + QueryBuilder + thenByButtonBackBorderInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackBorderInt', Sort.asc); + }); + } + + QueryBuilder + thenByButtonBackBorderIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackBorderInt', Sort.desc); + }); + } + + QueryBuilder + thenByButtonBackBorderSecondaryDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackBorderSecondaryDisabledInt', Sort.asc); + }); + } + + QueryBuilder + thenByButtonBackBorderSecondaryDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy( + r'buttonBackBorderSecondaryDisabledInt', Sort.desc); + }); + } + + QueryBuilder + thenByButtonBackBorderSecondaryInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackBorderSecondaryInt', Sort.asc); + }); + } + + QueryBuilder + thenByButtonBackBorderSecondaryIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackBorderSecondaryInt', Sort.desc); + }); + } + + QueryBuilder + thenByButtonBackPrimaryDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackPrimaryDisabledInt', Sort.asc); + }); + } + + QueryBuilder + thenByButtonBackPrimaryDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackPrimaryDisabledInt', Sort.desc); + }); + } + + QueryBuilder + thenByButtonBackPrimaryInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackPrimaryInt', Sort.asc); + }); + } + + QueryBuilder + thenByButtonBackPrimaryIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackPrimaryInt', Sort.desc); + }); + } + + QueryBuilder + thenByButtonBackSecondaryDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackSecondaryDisabledInt', Sort.asc); + }); + } + + QueryBuilder + thenByButtonBackSecondaryDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackSecondaryDisabledInt', Sort.desc); + }); + } + + QueryBuilder + thenByButtonBackSecondaryInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackSecondaryInt', Sort.asc); + }); + } + + QueryBuilder + thenByButtonBackSecondaryIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonBackSecondaryInt', Sort.desc); + }); + } + + QueryBuilder + thenByButtonTextBorderInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextBorderInt', Sort.asc); + }); + } + + QueryBuilder + thenByButtonTextBorderIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextBorderInt', Sort.desc); + }); + } + + QueryBuilder + thenByButtonTextBorderlessDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextBorderlessDisabledInt', Sort.asc); + }); + } + + QueryBuilder + thenByButtonTextBorderlessDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextBorderlessDisabledInt', Sort.desc); + }); + } + + QueryBuilder + thenByButtonTextBorderlessInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextBorderlessInt', Sort.asc); + }); + } + + QueryBuilder + thenByButtonTextBorderlessIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextBorderlessInt', Sort.desc); + }); + } + + QueryBuilder + thenByButtonTextDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextDisabledInt', Sort.asc); + }); + } + + QueryBuilder + thenByButtonTextDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextDisabledInt', Sort.desc); + }); + } + + QueryBuilder + thenByButtonTextPrimaryDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextPrimaryDisabledInt', Sort.asc); + }); + } + + QueryBuilder + thenByButtonTextPrimaryDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextPrimaryDisabledInt', Sort.desc); + }); + } + + QueryBuilder + thenByButtonTextPrimaryInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextPrimaryInt', Sort.asc); + }); + } + + QueryBuilder + thenByButtonTextPrimaryIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextPrimaryInt', Sort.desc); + }); + } + + QueryBuilder + thenByButtonTextSecondaryDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextSecondaryDisabledInt', Sort.asc); + }); + } + + QueryBuilder + thenByButtonTextSecondaryDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextSecondaryDisabledInt', Sort.desc); + }); + } + + QueryBuilder + thenByButtonTextSecondaryInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextSecondaryInt', Sort.asc); + }); + } + + QueryBuilder + thenByButtonTextSecondaryIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'buttonTextSecondaryInt', Sort.desc); + }); + } + + QueryBuilder + thenByCheckboxBGCheckedInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checkboxBGCheckedInt', Sort.asc); + }); + } + + QueryBuilder + thenByCheckboxBGCheckedIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checkboxBGCheckedInt', Sort.desc); + }); + } + + QueryBuilder + thenByCheckboxBGDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checkboxBGDisabledInt', Sort.asc); + }); + } + + QueryBuilder + thenByCheckboxBGDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checkboxBGDisabledInt', Sort.desc); + }); + } + + QueryBuilder + thenByCheckboxBorderEmptyInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checkboxBorderEmptyInt', Sort.asc); + }); + } + + QueryBuilder + thenByCheckboxBorderEmptyIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checkboxBorderEmptyInt', Sort.desc); + }); + } + + QueryBuilder + thenByCheckboxIconCheckedInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checkboxIconCheckedInt', Sort.asc); + }); + } + + QueryBuilder + thenByCheckboxIconCheckedIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checkboxIconCheckedInt', Sort.desc); + }); + } + + QueryBuilder + thenByCheckboxIconDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checkboxIconDisabledInt', Sort.asc); + }); + } + + QueryBuilder + thenByCheckboxIconDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checkboxIconDisabledInt', Sort.desc); + }); + } + + QueryBuilder + thenByCheckboxTextLabelInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checkboxTextLabelInt', Sort.asc); + }); + } + + QueryBuilder + thenByCheckboxTextLabelIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'checkboxTextLabelInt', Sort.desc); + }); + } + + QueryBuilder + thenByCoinColorsJsonString() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'coinColorsJsonString', Sort.asc); + }); + } + + QueryBuilder + thenByCoinColorsJsonStringDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'coinColorsJsonString', Sort.desc); + }); + } + + QueryBuilder + thenByCurrencyListItemBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'currencyListItemBGInt', Sort.asc); + }); + } + + QueryBuilder + thenByCurrencyListItemBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'currencyListItemBGInt', Sort.desc); + }); + } + + QueryBuilder + thenByCustomTextButtonDisabledTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'customTextButtonDisabledTextInt', Sort.asc); + }); + } + + QueryBuilder + thenByCustomTextButtonDisabledTextIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'customTextButtonDisabledTextInt', Sort.desc); + }); + } + + QueryBuilder + thenByCustomTextButtonEnabledTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'customTextButtonEnabledTextInt', Sort.asc); + }); + } + + QueryBuilder + thenByCustomTextButtonEnabledTextIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'customTextButtonEnabledTextInt', Sort.desc); + }); + } + + QueryBuilder thenByEthTagBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ethTagBGInt', Sort.asc); + }); + } + + QueryBuilder thenByEthTagBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ethTagBGInt', Sort.desc); + }); + } + + QueryBuilder thenByEthTagTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ethTagTextInt', Sort.asc); + }); + } + + QueryBuilder thenByEthTagTextIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ethTagTextInt', Sort.desc); + }); + } + + QueryBuilder thenByEthWalletTagBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ethWalletTagBGInt', Sort.asc); + }); + } + + QueryBuilder + thenByEthWalletTagBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ethWalletTagBGInt', Sort.desc); + }); + } + + QueryBuilder + thenByEthWalletTagTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ethWalletTagTextInt', Sort.asc); + }); + } + + QueryBuilder + thenByEthWalletTagTextIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'ethWalletTagTextInt', Sort.desc); + }); + } + + QueryBuilder + thenByFavoriteStarActiveInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'favoriteStarActiveInt', Sort.asc); + }); + } + + QueryBuilder + thenByFavoriteStarActiveIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'favoriteStarActiveInt', Sort.desc); + }); + } + + QueryBuilder + thenByFavoriteStarInactiveInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'favoriteStarInactiveInt', Sort.asc); + }); + } + + QueryBuilder + thenByFavoriteStarInactiveIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'favoriteStarInactiveInt', Sort.desc); + }); + } + + QueryBuilder + thenByGradientBackgroundString() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'gradientBackgroundString', Sort.asc); + }); + } + + QueryBuilder + thenByGradientBackgroundStringDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'gradientBackgroundString', Sort.desc); + }); + } + + QueryBuilder thenByHighlightInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'highlightInt', Sort.asc); + }); + } + + QueryBuilder thenByHighlightIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'highlightInt', Sort.desc); + }); + } + + QueryBuilder + thenByHomeViewButtonBarBoxShadowString() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'homeViewButtonBarBoxShadowString', Sort.asc); + }); + } + + QueryBuilder + thenByHomeViewButtonBarBoxShadowStringDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'homeViewButtonBarBoxShadowString', Sort.desc); + }); + } + + QueryBuilder thenById() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.asc); + }); + } + + QueryBuilder thenByIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'id', Sort.desc); + }); + } + + QueryBuilder thenByInfoItemBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'infoItemBGInt', Sort.asc); + }); + } + + QueryBuilder thenByInfoItemBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'infoItemBGInt', Sort.desc); + }); + } + + QueryBuilder thenByInfoItemIconsInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'infoItemIconsInt', Sort.asc); + }); + } + + QueryBuilder + thenByInfoItemIconsIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'infoItemIconsInt', Sort.desc); + }); + } + + QueryBuilder thenByInfoItemLabelInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'infoItemLabelInt', Sort.asc); + }); + } + + QueryBuilder + thenByInfoItemLabelIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'infoItemLabelInt', Sort.desc); + }); + } + + QueryBuilder thenByInfoItemTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'infoItemTextInt', Sort.asc); + }); + } + + QueryBuilder + thenByInfoItemTextIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'infoItemTextInt', Sort.desc); + }); + } + + QueryBuilder + thenByLoadingOverlayTextColorInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'loadingOverlayTextColorInt', Sort.asc); + }); + } + + QueryBuilder + thenByLoadingOverlayTextColorIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'loadingOverlayTextColorInt', Sort.desc); + }); + } + + QueryBuilder + thenByMyStackContactIconBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'myStackContactIconBGInt', Sort.asc); + }); + } + + QueryBuilder + thenByMyStackContactIconBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'myStackContactIconBGInt', Sort.desc); + }); + } + + QueryBuilder thenByName() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.asc); + }); + } + + QueryBuilder thenByNameDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'name', Sort.desc); + }); + } + + QueryBuilder + thenByNumberBackDefaultInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'numberBackDefaultInt', Sort.asc); + }); + } + + QueryBuilder + thenByNumberBackDefaultIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'numberBackDefaultInt', Sort.desc); + }); + } + + QueryBuilder + thenByNumberTextDefaultInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'numberTextDefaultInt', Sort.asc); + }); + } + + QueryBuilder + thenByNumberTextDefaultIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'numberTextDefaultInt', Sort.desc); + }); + } + + QueryBuilder + thenByNumpadBackDefaultInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'numpadBackDefaultInt', Sort.asc); + }); + } + + QueryBuilder + thenByNumpadBackDefaultIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'numpadBackDefaultInt', Sort.desc); + }); + } + + QueryBuilder + thenByNumpadTextDefaultInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'numpadTextDefaultInt', Sort.asc); + }); + } + + QueryBuilder + thenByNumpadTextDefaultIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'numpadTextDefaultInt', Sort.desc); + }); + } + + QueryBuilder thenByOverlayInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'overlayInt', Sort.asc); + }); + } + + QueryBuilder thenByOverlayIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'overlayInt', Sort.desc); + }); + } + + QueryBuilder thenByPopupBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'popupBGInt', Sort.asc); + }); + } + + QueryBuilder thenByPopupBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'popupBGInt', Sort.desc); + }); + } + + QueryBuilder + thenByRadioButtonBorderDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonBorderDisabledInt', Sort.asc); + }); + } + + QueryBuilder + thenByRadioButtonBorderDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonBorderDisabledInt', Sort.desc); + }); + } + + QueryBuilder + thenByRadioButtonBorderEnabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonBorderEnabledInt', Sort.asc); + }); + } + + QueryBuilder + thenByRadioButtonBorderEnabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonBorderEnabledInt', Sort.desc); + }); + } + + QueryBuilder + thenByRadioButtonIconBorderDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonIconBorderDisabledInt', Sort.asc); + }); + } + + QueryBuilder + thenByRadioButtonIconBorderDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonIconBorderDisabledInt', Sort.desc); + }); + } + + QueryBuilder + thenByRadioButtonIconBorderInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonIconBorderInt', Sort.asc); + }); + } + + QueryBuilder + thenByRadioButtonIconBorderIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonIconBorderInt', Sort.desc); + }); + } + + QueryBuilder + thenByRadioButtonIconCircleInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonIconCircleInt', Sort.asc); + }); + } + + QueryBuilder + thenByRadioButtonIconCircleIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonIconCircleInt', Sort.desc); + }); + } + + QueryBuilder + thenByRadioButtonIconEnabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonIconEnabledInt', Sort.asc); + }); + } + + QueryBuilder + thenByRadioButtonIconEnabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonIconEnabledInt', Sort.desc); + }); + } + + QueryBuilder + thenByRadioButtonLabelDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonLabelDisabledInt', Sort.asc); + }); + } + + QueryBuilder + thenByRadioButtonLabelDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonLabelDisabledInt', Sort.desc); + }); + } + + QueryBuilder + thenByRadioButtonLabelEnabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonLabelEnabledInt', Sort.asc); + }); + } + + QueryBuilder + thenByRadioButtonLabelEnabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonLabelEnabledInt', Sort.desc); + }); + } + + QueryBuilder + thenByRadioButtonTextDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonTextDisabledInt', Sort.asc); + }); + } + + QueryBuilder + thenByRadioButtonTextDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonTextDisabledInt', Sort.desc); + }); + } + + QueryBuilder + thenByRadioButtonTextEnabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonTextEnabledInt', Sort.asc); + }); + } + + QueryBuilder + thenByRadioButtonTextEnabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'radioButtonTextEnabledInt', Sort.desc); + }); + } + + QueryBuilder + thenByRateTypeToggleColorOffInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'rateTypeToggleColorOffInt', Sort.asc); + }); + } + + QueryBuilder + thenByRateTypeToggleColorOffIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'rateTypeToggleColorOffInt', Sort.desc); + }); + } + + QueryBuilder + thenByRateTypeToggleColorOnInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'rateTypeToggleColorOnInt', Sort.asc); + }); + } + + QueryBuilder + thenByRateTypeToggleColorOnIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'rateTypeToggleColorOnInt', Sort.desc); + }); + } + + QueryBuilder + thenByRateTypeToggleDesktopColorOffInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'rateTypeToggleDesktopColorOffInt', Sort.asc); + }); + } + + QueryBuilder + thenByRateTypeToggleDesktopColorOffIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'rateTypeToggleDesktopColorOffInt', Sort.desc); + }); + } + + QueryBuilder + thenByRateTypeToggleDesktopColorOnInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'rateTypeToggleDesktopColorOnInt', Sort.asc); + }); + } + + QueryBuilder + thenByRateTypeToggleDesktopColorOnIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'rateTypeToggleDesktopColorOnInt', Sort.desc); + }); + } + + QueryBuilder + thenBySettingsIconBack2Int() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsIconBack2Int', Sort.asc); + }); + } + + QueryBuilder + thenBySettingsIconBack2IntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsIconBack2Int', Sort.desc); + }); + } + + QueryBuilder + thenBySettingsIconBackInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsIconBackInt', Sort.asc); + }); + } + + QueryBuilder + thenBySettingsIconBackIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsIconBackInt', Sort.desc); + }); + } + + QueryBuilder + thenBySettingsIconElementInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsIconElementInt', Sort.asc); + }); + } + + QueryBuilder + thenBySettingsIconElementIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsIconElementInt', Sort.desc); + }); + } + + QueryBuilder + thenBySettingsIconIconInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsIconIconInt', Sort.asc); + }); + } + + QueryBuilder + thenBySettingsIconIconIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsIconIconInt', Sort.desc); + }); + } + + QueryBuilder + thenBySettingsItem2ActiveBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsItem2ActiveBGInt', Sort.asc); + }); + } + + QueryBuilder + thenBySettingsItem2ActiveBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsItem2ActiveBGInt', Sort.desc); + }); + } + + QueryBuilder + thenBySettingsItem2ActiveSubInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsItem2ActiveSubInt', Sort.asc); + }); + } + + QueryBuilder + thenBySettingsItem2ActiveSubIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsItem2ActiveSubInt', Sort.desc); + }); + } + + QueryBuilder + thenBySettingsItem2ActiveTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsItem2ActiveTextInt', Sort.asc); + }); + } + + QueryBuilder + thenBySettingsItem2ActiveTextIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'settingsItem2ActiveTextInt', Sort.desc); + }); + } + + QueryBuilder thenByShadowInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shadowInt', Sort.asc); + }); + } + + QueryBuilder thenByShadowIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'shadowInt', Sort.desc); + }); + } + + QueryBuilder + thenBySnackBarBackErrorInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'snackBarBackErrorInt', Sort.asc); + }); + } + + QueryBuilder + thenBySnackBarBackErrorIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'snackBarBackErrorInt', Sort.desc); + }); + } + + QueryBuilder + thenBySnackBarBackInfoInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'snackBarBackInfoInt', Sort.asc); + }); + } + + QueryBuilder + thenBySnackBarBackInfoIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'snackBarBackInfoInt', Sort.desc); + }); + } + + QueryBuilder + thenBySnackBarBackSuccessInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'snackBarBackSuccessInt', Sort.asc); + }); + } + + QueryBuilder + thenBySnackBarBackSuccessIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'snackBarBackSuccessInt', Sort.desc); + }); + } + + QueryBuilder + thenBySnackBarTextErrorInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'snackBarTextErrorInt', Sort.asc); + }); + } + + QueryBuilder + thenBySnackBarTextErrorIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'snackBarTextErrorInt', Sort.desc); + }); + } + + QueryBuilder + thenBySnackBarTextInfoInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'snackBarTextInfoInt', Sort.asc); + }); + } + + QueryBuilder + thenBySnackBarTextInfoIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'snackBarTextInfoInt', Sort.desc); + }); + } + + QueryBuilder + thenBySnackBarTextSuccessInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'snackBarTextSuccessInt', Sort.asc); + }); + } + + QueryBuilder + thenBySnackBarTextSuccessIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'snackBarTextSuccessInt', Sort.desc); + }); + } + + QueryBuilder thenBySplashInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'splashInt', Sort.asc); + }); + } + + QueryBuilder thenBySplashIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'splashInt', Sort.desc); + }); + } + + QueryBuilder thenByStackWalletBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stackWalletBGInt', Sort.asc); + }); + } + + QueryBuilder + thenByStackWalletBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stackWalletBGInt', Sort.desc); + }); + } + + QueryBuilder + thenByStackWalletBottomInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stackWalletBottomInt', Sort.asc); + }); + } + + QueryBuilder + thenByStackWalletBottomIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stackWalletBottomInt', Sort.desc); + }); + } + + QueryBuilder thenByStackWalletMidInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stackWalletMidInt', Sort.asc); + }); + } + + QueryBuilder + thenByStackWalletMidIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stackWalletMidInt', Sort.desc); + }); + } + + QueryBuilder + thenByStandardBoxShadowString() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'standardBoxShadowString', Sort.asc); + }); + } + + QueryBuilder + thenByStandardBoxShadowStringDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'standardBoxShadowString', Sort.desc); + }); + } + + QueryBuilder + thenByStepIndicatorBGCheckInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorBGCheckInt', Sort.asc); + }); + } + + QueryBuilder + thenByStepIndicatorBGCheckIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorBGCheckInt', Sort.desc); + }); + } + + QueryBuilder + thenByStepIndicatorBGInactiveInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorBGInactiveInt', Sort.asc); + }); + } + + QueryBuilder + thenByStepIndicatorBGInactiveIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorBGInactiveInt', Sort.desc); + }); + } + + QueryBuilder + thenByStepIndicatorBGLinesInactiveInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorBGLinesInactiveInt', Sort.asc); + }); + } + + QueryBuilder + thenByStepIndicatorBGLinesInactiveIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorBGLinesInactiveInt', Sort.desc); + }); + } + + QueryBuilder + thenByStepIndicatorBGLinesInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorBGLinesInt', Sort.asc); + }); + } + + QueryBuilder + thenByStepIndicatorBGLinesIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorBGLinesInt', Sort.desc); + }); + } + + QueryBuilder + thenByStepIndicatorBGNumberInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorBGNumberInt', Sort.asc); + }); + } + + QueryBuilder + thenByStepIndicatorBGNumberIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorBGNumberInt', Sort.desc); + }); + } + + QueryBuilder + thenByStepIndicatorIconInactiveInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorIconInactiveInt', Sort.asc); + }); + } + + QueryBuilder + thenByStepIndicatorIconInactiveIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorIconInactiveInt', Sort.desc); + }); + } + + QueryBuilder + thenByStepIndicatorIconNumberInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorIconNumberInt', Sort.asc); + }); + } + + QueryBuilder + thenByStepIndicatorIconNumberIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorIconNumberInt', Sort.desc); + }); + } + + QueryBuilder + thenByStepIndicatorIconTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorIconTextInt', Sort.asc); + }); + } + + QueryBuilder + thenByStepIndicatorIconTextIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'stepIndicatorIconTextInt', Sort.desc); + }); + } + + QueryBuilder + thenBySwitchBGDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'switchBGDisabledInt', Sort.asc); + }); + } + + QueryBuilder + thenBySwitchBGDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'switchBGDisabledInt', Sort.desc); + }); + } + + QueryBuilder thenBySwitchBGOffInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'switchBGOffInt', Sort.asc); + }); + } + + QueryBuilder + thenBySwitchBGOffIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'switchBGOffInt', Sort.desc); + }); + } + + QueryBuilder thenBySwitchBGOnInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'switchBGOnInt', Sort.asc); + }); + } + + QueryBuilder thenBySwitchBGOnIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'switchBGOnInt', Sort.desc); + }); + } + + QueryBuilder + thenBySwitchCircleDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'switchCircleDisabledInt', Sort.asc); + }); + } + + QueryBuilder + thenBySwitchCircleDisabledIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'switchCircleDisabledInt', Sort.desc); + }); + } + + QueryBuilder + thenBySwitchCircleOffInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'switchCircleOffInt', Sort.asc); + }); + } + + QueryBuilder + thenBySwitchCircleOffIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'switchCircleOffInt', Sort.desc); + }); + } + + QueryBuilder thenBySwitchCircleOnInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'switchCircleOnInt', Sort.asc); + }); + } + + QueryBuilder + thenBySwitchCircleOnIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'switchCircleOnInt', Sort.desc); + }); + } + + QueryBuilder + thenByTextConfirmTotalAmountInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textConfirmTotalAmountInt', Sort.asc); + }); + } + + QueryBuilder + thenByTextConfirmTotalAmountIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textConfirmTotalAmountInt', Sort.desc); + }); + } + + QueryBuilder thenByTextDark2Int() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textDark2Int', Sort.asc); + }); + } + + QueryBuilder thenByTextDark2IntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textDark2Int', Sort.desc); + }); + } + + QueryBuilder thenByTextDark3Int() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textDark3Int', Sort.asc); + }); + } + + QueryBuilder thenByTextDark3IntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textDark3Int', Sort.desc); + }); + } + + QueryBuilder thenByTextDarkInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textDarkInt', Sort.asc); + }); + } + + QueryBuilder thenByTextDarkIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textDarkInt', Sort.desc); + }); + } + + QueryBuilder thenByTextErrorInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textErrorInt', Sort.asc); + }); + } + + QueryBuilder thenByTextErrorIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textErrorInt', Sort.desc); + }); + } + + QueryBuilder + thenByTextFavoriteCardInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFavoriteCardInt', Sort.asc); + }); + } + + QueryBuilder + thenByTextFavoriteCardIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFavoriteCardInt', Sort.desc); + }); + } + + QueryBuilder + thenByTextFieldActiveBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldActiveBGInt', Sort.asc); + }); + } + + QueryBuilder + thenByTextFieldActiveBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldActiveBGInt', Sort.desc); + }); + } + + QueryBuilder + thenByTextFieldActiveLabelInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldActiveLabelInt', Sort.asc); + }); + } + + QueryBuilder + thenByTextFieldActiveLabelIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldActiveLabelInt', Sort.desc); + }); + } + + QueryBuilder + thenByTextFieldActiveSearchIconLeftInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldActiveSearchIconLeftInt', Sort.asc); + }); + } + + QueryBuilder + thenByTextFieldActiveSearchIconLeftIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldActiveSearchIconLeftInt', Sort.desc); + }); + } + + QueryBuilder + thenByTextFieldActiveSearchIconRightInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldActiveSearchIconRightInt', Sort.asc); + }); + } + + QueryBuilder + thenByTextFieldActiveSearchIconRightIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldActiveSearchIconRightInt', Sort.desc); + }); + } + + QueryBuilder + thenByTextFieldActiveTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldActiveTextInt', Sort.asc); + }); + } + + QueryBuilder + thenByTextFieldActiveTextIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldActiveTextInt', Sort.desc); + }); + } + + QueryBuilder + thenByTextFieldDefaultBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldDefaultBGInt', Sort.asc); + }); + } + + QueryBuilder + thenByTextFieldDefaultBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldDefaultBGInt', Sort.desc); + }); + } + + QueryBuilder + thenByTextFieldDefaultSearchIconLeftInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldDefaultSearchIconLeftInt', Sort.asc); + }); + } + + QueryBuilder + thenByTextFieldDefaultSearchIconLeftIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldDefaultSearchIconLeftInt', Sort.desc); + }); + } + + QueryBuilder + thenByTextFieldDefaultSearchIconRightInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldDefaultSearchIconRightInt', Sort.asc); + }); + } + + QueryBuilder + thenByTextFieldDefaultSearchIconRightIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldDefaultSearchIconRightInt', Sort.desc); + }); + } + + QueryBuilder + thenByTextFieldDefaultTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldDefaultTextInt', Sort.asc); + }); + } + + QueryBuilder + thenByTextFieldDefaultTextIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldDefaultTextInt', Sort.desc); + }); + } + + QueryBuilder + thenByTextFieldErrorBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldErrorBGInt', Sort.asc); + }); + } + + QueryBuilder + thenByTextFieldErrorBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldErrorBGInt', Sort.desc); + }); + } + + QueryBuilder + thenByTextFieldErrorBorderInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldErrorBorderInt', Sort.asc); + }); + } + + QueryBuilder + thenByTextFieldErrorBorderIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldErrorBorderInt', Sort.desc); + }); + } + + QueryBuilder + thenByTextFieldErrorLabelInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldErrorLabelInt', Sort.asc); + }); + } + + QueryBuilder + thenByTextFieldErrorLabelIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldErrorLabelInt', Sort.desc); + }); + } + + QueryBuilder + thenByTextFieldErrorSearchIconLeftInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldErrorSearchIconLeftInt', Sort.asc); + }); + } + + QueryBuilder + thenByTextFieldErrorSearchIconLeftIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldErrorSearchIconLeftInt', Sort.desc); + }); + } + + QueryBuilder + thenByTextFieldErrorSearchIconRightInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldErrorSearchIconRightInt', Sort.asc); + }); + } + + QueryBuilder + thenByTextFieldErrorSearchIconRightIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldErrorSearchIconRightInt', Sort.desc); + }); + } + + QueryBuilder + thenByTextFieldErrorTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldErrorTextInt', Sort.asc); + }); + } + + QueryBuilder + thenByTextFieldErrorTextIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldErrorTextInt', Sort.desc); + }); + } + + QueryBuilder + thenByTextFieldSuccessBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldSuccessBGInt', Sort.asc); + }); + } + + QueryBuilder + thenByTextFieldSuccessBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldSuccessBGInt', Sort.desc); + }); + } + + QueryBuilder + thenByTextFieldSuccessBorderInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldSuccessBorderInt', Sort.asc); + }); + } + + QueryBuilder + thenByTextFieldSuccessBorderIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldSuccessBorderInt', Sort.desc); + }); + } + + QueryBuilder + thenByTextFieldSuccessLabelInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldSuccessLabelInt', Sort.asc); + }); + } + + QueryBuilder + thenByTextFieldSuccessLabelIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldSuccessLabelInt', Sort.desc); + }); + } + + QueryBuilder + thenByTextFieldSuccessSearchIconLeftInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldSuccessSearchIconLeftInt', Sort.asc); + }); + } + + QueryBuilder + thenByTextFieldSuccessSearchIconLeftIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldSuccessSearchIconLeftInt', Sort.desc); + }); + } + + QueryBuilder + thenByTextFieldSuccessSearchIconRightInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldSuccessSearchIconRightInt', Sort.asc); + }); + } + + QueryBuilder + thenByTextFieldSuccessSearchIconRightIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldSuccessSearchIconRightInt', Sort.desc); + }); + } + + QueryBuilder + thenByTextFieldSuccessTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldSuccessTextInt', Sort.asc); + }); + } + + QueryBuilder + thenByTextFieldSuccessTextIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textFieldSuccessTextInt', Sort.desc); + }); + } + + QueryBuilder thenByTextRestoreInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textRestoreInt', Sort.asc); + }); + } + + QueryBuilder + thenByTextRestoreIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textRestoreInt', Sort.desc); + }); + } + + QueryBuilder + thenByTextSelectedWordTableItemInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSelectedWordTableItemInt', Sort.asc); + }); + } + + QueryBuilder + thenByTextSelectedWordTableItemIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSelectedWordTableItemInt', Sort.desc); + }); + } + + QueryBuilder thenByTextSubtitle1Int() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSubtitle1Int', Sort.asc); + }); + } + + QueryBuilder + thenByTextSubtitle1IntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSubtitle1Int', Sort.desc); + }); + } + + QueryBuilder thenByTextSubtitle2Int() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSubtitle2Int', Sort.asc); + }); + } + + QueryBuilder + thenByTextSubtitle2IntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSubtitle2Int', Sort.desc); + }); + } + + QueryBuilder thenByTextSubtitle3Int() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSubtitle3Int', Sort.asc); + }); + } + + QueryBuilder + thenByTextSubtitle3IntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSubtitle3Int', Sort.desc); + }); + } + + QueryBuilder thenByTextSubtitle4Int() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSubtitle4Int', Sort.asc); + }); + } + + QueryBuilder + thenByTextSubtitle4IntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSubtitle4Int', Sort.desc); + }); + } + + QueryBuilder thenByTextSubtitle5Int() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSubtitle5Int', Sort.asc); + }); + } + + QueryBuilder + thenByTextSubtitle5IntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSubtitle5Int', Sort.desc); + }); + } + + QueryBuilder thenByTextSubtitle6Int() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSubtitle6Int', Sort.asc); + }); + } + + QueryBuilder + thenByTextSubtitle6IntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textSubtitle6Int', Sort.desc); + }); + } + + QueryBuilder thenByTextWhiteInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textWhiteInt', Sort.asc); + }); + } + + QueryBuilder thenByTextWhiteIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'textWhiteInt', Sort.desc); + }); + } + + QueryBuilder thenByThemeId() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'themeId', Sort.asc); + }); + } + + QueryBuilder thenByThemeIdDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'themeId', Sort.desc); + }); + } + + QueryBuilder thenByTokenSummaryBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'tokenSummaryBGInt', Sort.asc); + }); + } + + QueryBuilder + thenByTokenSummaryBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'tokenSummaryBGInt', Sort.desc); + }); + } + + QueryBuilder + thenByTokenSummaryButtonBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'tokenSummaryButtonBGInt', Sort.asc); + }); + } + + QueryBuilder + thenByTokenSummaryButtonBGIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'tokenSummaryButtonBGInt', Sort.desc); + }); + } + + QueryBuilder + thenByTokenSummaryIconInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'tokenSummaryIconInt', Sort.asc); + }); + } + + QueryBuilder + thenByTokenSummaryIconIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'tokenSummaryIconInt', Sort.desc); + }); + } + + QueryBuilder + thenByTokenSummaryTextPrimaryInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'tokenSummaryTextPrimaryInt', Sort.asc); + }); + } + + QueryBuilder + thenByTokenSummaryTextPrimaryIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'tokenSummaryTextPrimaryInt', Sort.desc); + }); + } + + QueryBuilder + thenByTokenSummaryTextSecondaryInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'tokenSummaryTextSecondaryInt', Sort.asc); + }); + } + + QueryBuilder + thenByTokenSummaryTextSecondaryIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'tokenSummaryTextSecondaryInt', Sort.desc); + }); + } + + QueryBuilder + thenByTopNavIconGreenInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'topNavIconGreenInt', Sort.asc); + }); + } + + QueryBuilder + thenByTopNavIconGreenIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'topNavIconGreenInt', Sort.desc); + }); + } + + QueryBuilder + thenByTopNavIconPrimaryInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'topNavIconPrimaryInt', Sort.asc); + }); + } + + QueryBuilder + thenByTopNavIconPrimaryIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'topNavIconPrimaryInt', Sort.desc); + }); + } + + QueryBuilder thenByTopNavIconRedInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'topNavIconRedInt', Sort.asc); + }); + } + + QueryBuilder + thenByTopNavIconRedIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'topNavIconRedInt', Sort.desc); + }); + } + + QueryBuilder + thenByTopNavIconYellowInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'topNavIconYellowInt', Sort.asc); + }); + } + + QueryBuilder + thenByTopNavIconYellowIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'topNavIconYellowInt', Sort.desc); + }); + } + + QueryBuilder thenByVersion() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'version', Sort.asc); + }); + } + + QueryBuilder thenByVersionDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'version', Sort.desc); + }); + } + + QueryBuilder + thenByWarningBackgroundInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'warningBackgroundInt', Sort.asc); + }); + } + + QueryBuilder + thenByWarningBackgroundIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'warningBackgroundInt', Sort.desc); + }); + } + + QueryBuilder + thenByWarningForegroundInt() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'warningForegroundInt', Sort.asc); + }); + } + + QueryBuilder + thenByWarningForegroundIntDesc() { + return QueryBuilder.apply(this, (query) { + return query.addSortBy(r'warningForegroundInt', Sort.desc); + }); + } +} + +extension StackThemeQueryWhereDistinct + on QueryBuilder { + QueryBuilder + distinctByAccentColorBlueInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'accentColorBlueInt'); + }); + } + + QueryBuilder + distinctByAccentColorDarkInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'accentColorDarkInt'); + }); + } + + QueryBuilder + distinctByAccentColorGreenInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'accentColorGreenInt'); + }); + } + + QueryBuilder + distinctByAccentColorOrangeInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'accentColorOrangeInt'); + }); + } + + QueryBuilder + distinctByAccentColorRedInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'accentColorRedInt'); + }); + } + + QueryBuilder + distinctByAccentColorYellowInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'accentColorYellowInt'); + }); + } + + QueryBuilder + distinctByBackgroundAppBarInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'backgroundAppBarInt'); + }); + } + + QueryBuilder distinctByBackgroundInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'backgroundInt'); + }); + } + + QueryBuilder distinctByBottomNavBackInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'bottomNavBackInt'); + }); + } + + QueryBuilder + distinctByBottomNavIconBackInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'bottomNavIconBackInt'); + }); + } + + QueryBuilder + distinctByBottomNavIconIconHighlightedInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'bottomNavIconIconHighlightedInt'); + }); + } + + QueryBuilder + distinctByBottomNavIconIconInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'bottomNavIconIconInt'); + }); + } + + QueryBuilder + distinctByBottomNavShadowInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'bottomNavShadowInt'); + }); + } + + QueryBuilder distinctByBottomNavTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'bottomNavTextInt'); + }); + } + + QueryBuilder distinctByBrightnessString( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'brightnessString', + caseSensitive: caseSensitive); + }); + } + + QueryBuilder + distinctByButtonBackBorderDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'buttonBackBorderDisabledInt'); + }); + } + + QueryBuilder + distinctByButtonBackBorderInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'buttonBackBorderInt'); + }); + } + + QueryBuilder + distinctByButtonBackBorderSecondaryDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'buttonBackBorderSecondaryDisabledInt'); + }); + } + + QueryBuilder + distinctByButtonBackBorderSecondaryInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'buttonBackBorderSecondaryInt'); + }); + } + + QueryBuilder + distinctByButtonBackPrimaryDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'buttonBackPrimaryDisabledInt'); + }); + } + + QueryBuilder + distinctByButtonBackPrimaryInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'buttonBackPrimaryInt'); + }); + } + + QueryBuilder + distinctByButtonBackSecondaryDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'buttonBackSecondaryDisabledInt'); + }); + } + + QueryBuilder + distinctByButtonBackSecondaryInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'buttonBackSecondaryInt'); + }); + } + + QueryBuilder + distinctByButtonTextBorderInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'buttonTextBorderInt'); + }); + } + + QueryBuilder + distinctByButtonTextBorderlessDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'buttonTextBorderlessDisabledInt'); + }); + } + + QueryBuilder + distinctByButtonTextBorderlessInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'buttonTextBorderlessInt'); + }); + } + + QueryBuilder + distinctByButtonTextDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'buttonTextDisabledInt'); + }); + } + + QueryBuilder + distinctByButtonTextPrimaryDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'buttonTextPrimaryDisabledInt'); + }); + } + + QueryBuilder + distinctByButtonTextPrimaryInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'buttonTextPrimaryInt'); + }); + } + + QueryBuilder + distinctByButtonTextSecondaryDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'buttonTextSecondaryDisabledInt'); + }); + } + + QueryBuilder + distinctByButtonTextSecondaryInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'buttonTextSecondaryInt'); + }); + } + + QueryBuilder + distinctByCheckboxBGCheckedInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'checkboxBGCheckedInt'); + }); + } + + QueryBuilder + distinctByCheckboxBGDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'checkboxBGDisabledInt'); + }); + } + + QueryBuilder + distinctByCheckboxBorderEmptyInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'checkboxBorderEmptyInt'); + }); + } + + QueryBuilder + distinctByCheckboxIconCheckedInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'checkboxIconCheckedInt'); + }); + } + + QueryBuilder + distinctByCheckboxIconDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'checkboxIconDisabledInt'); + }); + } + + QueryBuilder + distinctByCheckboxTextLabelInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'checkboxTextLabelInt'); + }); + } + + QueryBuilder + distinctByCoinColorsJsonString({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'coinColorsJsonString', + caseSensitive: caseSensitive); + }); + } + + QueryBuilder + distinctByCurrencyListItemBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'currencyListItemBGInt'); + }); + } + + QueryBuilder + distinctByCustomTextButtonDisabledTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'customTextButtonDisabledTextInt'); + }); + } + + QueryBuilder + distinctByCustomTextButtonEnabledTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'customTextButtonEnabledTextInt'); + }); + } + + QueryBuilder distinctByEthTagBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'ethTagBGInt'); + }); + } + + QueryBuilder distinctByEthTagTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'ethTagTextInt'); + }); + } + + QueryBuilder + distinctByEthWalletTagBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'ethWalletTagBGInt'); + }); + } + + QueryBuilder + distinctByEthWalletTagTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'ethWalletTagTextInt'); + }); + } + + QueryBuilder + distinctByFavoriteStarActiveInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'favoriteStarActiveInt'); + }); + } + + QueryBuilder + distinctByFavoriteStarInactiveInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'favoriteStarInactiveInt'); + }); + } + + QueryBuilder + distinctByGradientBackgroundString({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'gradientBackgroundString', + caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByHighlightInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'highlightInt'); + }); + } + + QueryBuilder + distinctByHomeViewButtonBarBoxShadowString({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'homeViewButtonBarBoxShadowString', + caseSensitive: caseSensitive); + }); + } + + QueryBuilder distinctByInfoItemBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'infoItemBGInt'); + }); + } + + QueryBuilder distinctByInfoItemIconsInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'infoItemIconsInt'); + }); + } + + QueryBuilder distinctByInfoItemLabelInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'infoItemLabelInt'); + }); + } + + QueryBuilder distinctByInfoItemTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'infoItemTextInt'); + }); + } + + QueryBuilder + distinctByLoadingOverlayTextColorInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'loadingOverlayTextColorInt'); + }); + } + + QueryBuilder + distinctByMyStackContactIconBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'myStackContactIconBGInt'); + }); + } + + QueryBuilder distinctByName( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'name', caseSensitive: caseSensitive); + }); + } + + QueryBuilder + distinctByNumberBackDefaultInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'numberBackDefaultInt'); + }); + } + + QueryBuilder + distinctByNumberTextDefaultInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'numberTextDefaultInt'); + }); + } + + QueryBuilder + distinctByNumpadBackDefaultInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'numpadBackDefaultInt'); + }); + } + + QueryBuilder + distinctByNumpadTextDefaultInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'numpadTextDefaultInt'); + }); + } + + QueryBuilder distinctByOverlayInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'overlayInt'); + }); + } + + QueryBuilder distinctByPopupBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'popupBGInt'); + }); + } + + QueryBuilder + distinctByRadioButtonBorderDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'radioButtonBorderDisabledInt'); + }); + } + + QueryBuilder + distinctByRadioButtonBorderEnabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'radioButtonBorderEnabledInt'); + }); + } + + QueryBuilder + distinctByRadioButtonIconBorderDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'radioButtonIconBorderDisabledInt'); + }); + } + + QueryBuilder + distinctByRadioButtonIconBorderInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'radioButtonIconBorderInt'); + }); + } + + QueryBuilder + distinctByRadioButtonIconCircleInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'radioButtonIconCircleInt'); + }); + } + + QueryBuilder + distinctByRadioButtonIconEnabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'radioButtonIconEnabledInt'); + }); + } + + QueryBuilder + distinctByRadioButtonLabelDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'radioButtonLabelDisabledInt'); + }); + } + + QueryBuilder + distinctByRadioButtonLabelEnabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'radioButtonLabelEnabledInt'); + }); + } + + QueryBuilder + distinctByRadioButtonTextDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'radioButtonTextDisabledInt'); + }); + } + + QueryBuilder + distinctByRadioButtonTextEnabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'radioButtonTextEnabledInt'); + }); + } + + QueryBuilder + distinctByRateTypeToggleColorOffInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'rateTypeToggleColorOffInt'); + }); + } + + QueryBuilder + distinctByRateTypeToggleColorOnInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'rateTypeToggleColorOnInt'); + }); + } + + QueryBuilder + distinctByRateTypeToggleDesktopColorOffInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'rateTypeToggleDesktopColorOffInt'); + }); + } + + QueryBuilder + distinctByRateTypeToggleDesktopColorOnInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'rateTypeToggleDesktopColorOnInt'); + }); + } + + QueryBuilder + distinctBySettingsIconBack2Int() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'settingsIconBack2Int'); + }); + } + + QueryBuilder + distinctBySettingsIconBackInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'settingsIconBackInt'); + }); + } + + QueryBuilder + distinctBySettingsIconElementInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'settingsIconElementInt'); + }); + } + + QueryBuilder + distinctBySettingsIconIconInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'settingsIconIconInt'); + }); + } + + QueryBuilder + distinctBySettingsItem2ActiveBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'settingsItem2ActiveBGInt'); + }); + } + + QueryBuilder + distinctBySettingsItem2ActiveSubInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'settingsItem2ActiveSubInt'); + }); + } + + QueryBuilder + distinctBySettingsItem2ActiveTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'settingsItem2ActiveTextInt'); + }); + } + + QueryBuilder distinctByShadowInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'shadowInt'); + }); + } + + QueryBuilder + distinctBySnackBarBackErrorInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'snackBarBackErrorInt'); + }); + } + + QueryBuilder + distinctBySnackBarBackInfoInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'snackBarBackInfoInt'); + }); + } + + QueryBuilder + distinctBySnackBarBackSuccessInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'snackBarBackSuccessInt'); + }); + } + + QueryBuilder + distinctBySnackBarTextErrorInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'snackBarTextErrorInt'); + }); + } + + QueryBuilder + distinctBySnackBarTextInfoInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'snackBarTextInfoInt'); + }); + } + + QueryBuilder + distinctBySnackBarTextSuccessInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'snackBarTextSuccessInt'); + }); + } + + QueryBuilder distinctBySplashInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'splashInt'); + }); + } + + QueryBuilder distinctByStackWalletBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'stackWalletBGInt'); + }); + } + + QueryBuilder + distinctByStackWalletBottomInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'stackWalletBottomInt'); + }); + } + + QueryBuilder + distinctByStackWalletMidInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'stackWalletMidInt'); + }); + } + + QueryBuilder + distinctByStandardBoxShadowString({bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'standardBoxShadowString', + caseSensitive: caseSensitive); + }); + } + + QueryBuilder + distinctByStepIndicatorBGCheckInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'stepIndicatorBGCheckInt'); + }); + } + + QueryBuilder + distinctByStepIndicatorBGInactiveInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'stepIndicatorBGInactiveInt'); + }); + } + + QueryBuilder + distinctByStepIndicatorBGLinesInactiveInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'stepIndicatorBGLinesInactiveInt'); + }); + } + + QueryBuilder + distinctByStepIndicatorBGLinesInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'stepIndicatorBGLinesInt'); + }); + } + + QueryBuilder + distinctByStepIndicatorBGNumberInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'stepIndicatorBGNumberInt'); + }); + } + + QueryBuilder + distinctByStepIndicatorIconInactiveInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'stepIndicatorIconInactiveInt'); + }); + } + + QueryBuilder + distinctByStepIndicatorIconNumberInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'stepIndicatorIconNumberInt'); + }); + } + + QueryBuilder + distinctByStepIndicatorIconTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'stepIndicatorIconTextInt'); + }); + } + + QueryBuilder + distinctBySwitchBGDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'switchBGDisabledInt'); + }); + } + + QueryBuilder distinctBySwitchBGOffInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'switchBGOffInt'); + }); + } + + QueryBuilder distinctBySwitchBGOnInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'switchBGOnInt'); + }); + } + + QueryBuilder + distinctBySwitchCircleDisabledInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'switchCircleDisabledInt'); + }); + } + + QueryBuilder + distinctBySwitchCircleOffInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'switchCircleOffInt'); + }); + } + + QueryBuilder + distinctBySwitchCircleOnInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'switchCircleOnInt'); + }); + } + + QueryBuilder + distinctByTextConfirmTotalAmountInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textConfirmTotalAmountInt'); + }); + } + + QueryBuilder distinctByTextDark2Int() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textDark2Int'); + }); + } + + QueryBuilder distinctByTextDark3Int() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textDark3Int'); + }); + } + + QueryBuilder distinctByTextDarkInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textDarkInt'); + }); + } + + QueryBuilder distinctByTextErrorInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textErrorInt'); + }); + } + + QueryBuilder + distinctByTextFavoriteCardInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textFavoriteCardInt'); + }); + } + + QueryBuilder + distinctByTextFieldActiveBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textFieldActiveBGInt'); + }); + } + + QueryBuilder + distinctByTextFieldActiveLabelInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textFieldActiveLabelInt'); + }); + } + + QueryBuilder + distinctByTextFieldActiveSearchIconLeftInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textFieldActiveSearchIconLeftInt'); + }); + } + + QueryBuilder + distinctByTextFieldActiveSearchIconRightInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textFieldActiveSearchIconRightInt'); + }); + } + + QueryBuilder + distinctByTextFieldActiveTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textFieldActiveTextInt'); + }); + } + + QueryBuilder + distinctByTextFieldDefaultBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textFieldDefaultBGInt'); + }); + } + + QueryBuilder + distinctByTextFieldDefaultSearchIconLeftInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textFieldDefaultSearchIconLeftInt'); + }); + } + + QueryBuilder + distinctByTextFieldDefaultSearchIconRightInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textFieldDefaultSearchIconRightInt'); + }); + } + + QueryBuilder + distinctByTextFieldDefaultTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textFieldDefaultTextInt'); + }); + } + + QueryBuilder + distinctByTextFieldErrorBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textFieldErrorBGInt'); + }); + } + + QueryBuilder + distinctByTextFieldErrorBorderInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textFieldErrorBorderInt'); + }); + } + + QueryBuilder + distinctByTextFieldErrorLabelInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textFieldErrorLabelInt'); + }); + } + + QueryBuilder + distinctByTextFieldErrorSearchIconLeftInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textFieldErrorSearchIconLeftInt'); + }); + } + + QueryBuilder + distinctByTextFieldErrorSearchIconRightInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textFieldErrorSearchIconRightInt'); + }); + } + + QueryBuilder + distinctByTextFieldErrorTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textFieldErrorTextInt'); + }); + } + + QueryBuilder + distinctByTextFieldSuccessBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textFieldSuccessBGInt'); + }); + } + + QueryBuilder + distinctByTextFieldSuccessBorderInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textFieldSuccessBorderInt'); + }); + } + + QueryBuilder + distinctByTextFieldSuccessLabelInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textFieldSuccessLabelInt'); + }); + } + + QueryBuilder + distinctByTextFieldSuccessSearchIconLeftInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textFieldSuccessSearchIconLeftInt'); + }); + } + + QueryBuilder + distinctByTextFieldSuccessSearchIconRightInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textFieldSuccessSearchIconRightInt'); + }); + } + + QueryBuilder + distinctByTextFieldSuccessTextInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textFieldSuccessTextInt'); + }); + } + + QueryBuilder distinctByTextRestoreInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textRestoreInt'); + }); + } + + QueryBuilder + distinctByTextSelectedWordTableItemInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textSelectedWordTableItemInt'); + }); + } + + QueryBuilder distinctByTextSubtitle1Int() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textSubtitle1Int'); + }); + } + + QueryBuilder distinctByTextSubtitle2Int() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textSubtitle2Int'); + }); + } + + QueryBuilder distinctByTextSubtitle3Int() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textSubtitle3Int'); + }); + } + + QueryBuilder distinctByTextSubtitle4Int() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textSubtitle4Int'); + }); + } + + QueryBuilder distinctByTextSubtitle5Int() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textSubtitle5Int'); + }); + } + + QueryBuilder distinctByTextSubtitle6Int() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textSubtitle6Int'); + }); + } + + QueryBuilder distinctByTextWhiteInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'textWhiteInt'); + }); + } + + QueryBuilder distinctByThemeId( + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'themeId', caseSensitive: caseSensitive); + }); + } + + QueryBuilder + distinctByTokenSummaryBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'tokenSummaryBGInt'); + }); + } + + QueryBuilder + distinctByTokenSummaryButtonBGInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'tokenSummaryButtonBGInt'); + }); + } + + QueryBuilder + distinctByTokenSummaryIconInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'tokenSummaryIconInt'); + }); + } + + QueryBuilder + distinctByTokenSummaryTextPrimaryInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'tokenSummaryTextPrimaryInt'); + }); + } + + QueryBuilder + distinctByTokenSummaryTextSecondaryInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'tokenSummaryTextSecondaryInt'); + }); + } + + QueryBuilder + distinctByTopNavIconGreenInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'topNavIconGreenInt'); + }); + } + + QueryBuilder + distinctByTopNavIconPrimaryInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'topNavIconPrimaryInt'); + }); + } + + QueryBuilder distinctByTopNavIconRedInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'topNavIconRedInt'); + }); + } + + QueryBuilder + distinctByTopNavIconYellowInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'topNavIconYellowInt'); + }); + } + + QueryBuilder distinctByVersion() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'version'); + }); + } + + QueryBuilder + distinctByWarningBackgroundInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'warningBackgroundInt'); + }); + } + + QueryBuilder + distinctByWarningForegroundInt() { + return QueryBuilder.apply(this, (query) { + return query.addDistinctBy(r'warningForegroundInt'); + }); + } +} + +extension StackThemeQueryProperty + on QueryBuilder { + QueryBuilder idProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'id'); + }); + } + + QueryBuilder accentColorBlueIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'accentColorBlueInt'); + }); + } + + QueryBuilder accentColorDarkIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'accentColorDarkInt'); + }); + } + + QueryBuilder + accentColorGreenIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'accentColorGreenInt'); + }); + } + + QueryBuilder + accentColorOrangeIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'accentColorOrangeInt'); + }); + } + + QueryBuilder accentColorRedIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'accentColorRedInt'); + }); + } + + QueryBuilder + accentColorYellowIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'accentColorYellowInt'); + }); + } + + QueryBuilder assetsV1Property() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'assets'); + }); + } + + QueryBuilder + assetsV2Property() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'assetsV2'); + }); + } + + QueryBuilder + backgroundAppBarIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'backgroundAppBarInt'); + }); + } + + QueryBuilder backgroundIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'backgroundInt'); + }); + } + + QueryBuilder bottomNavBackIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'bottomNavBackInt'); + }); + } + + QueryBuilder + bottomNavIconBackIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'bottomNavIconBackInt'); + }); + } + + QueryBuilder + bottomNavIconIconHighlightedIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'bottomNavIconIconHighlightedInt'); + }); + } + + QueryBuilder + bottomNavIconIconIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'bottomNavIconIconInt'); + }); + } + + QueryBuilder bottomNavShadowIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'bottomNavShadowInt'); + }); + } + + QueryBuilder bottomNavTextIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'bottomNavTextInt'); + }); + } + + QueryBuilder + brightnessStringProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'brightnessString'); + }); + } + + QueryBuilder + buttonBackBorderDisabledIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'buttonBackBorderDisabledInt'); + }); + } + + QueryBuilder + buttonBackBorderIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'buttonBackBorderInt'); + }); + } + + QueryBuilder + buttonBackBorderSecondaryDisabledIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'buttonBackBorderSecondaryDisabledInt'); + }); + } + + QueryBuilder + buttonBackBorderSecondaryIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'buttonBackBorderSecondaryInt'); + }); + } + + QueryBuilder + buttonBackPrimaryDisabledIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'buttonBackPrimaryDisabledInt'); + }); + } + + QueryBuilder + buttonBackPrimaryIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'buttonBackPrimaryInt'); + }); + } + + QueryBuilder + buttonBackSecondaryDisabledIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'buttonBackSecondaryDisabledInt'); + }); + } + + QueryBuilder + buttonBackSecondaryIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'buttonBackSecondaryInt'); + }); + } + + QueryBuilder + buttonTextBorderIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'buttonTextBorderInt'); + }); + } + + QueryBuilder + buttonTextBorderlessDisabledIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'buttonTextBorderlessDisabledInt'); + }); + } + + QueryBuilder + buttonTextBorderlessIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'buttonTextBorderlessInt'); + }); + } + + QueryBuilder + buttonTextDisabledIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'buttonTextDisabledInt'); + }); + } + + QueryBuilder + buttonTextPrimaryDisabledIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'buttonTextPrimaryDisabledInt'); + }); + } + + QueryBuilder + buttonTextPrimaryIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'buttonTextPrimaryInt'); + }); + } + + QueryBuilder + buttonTextSecondaryDisabledIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'buttonTextSecondaryDisabledInt'); + }); + } + + QueryBuilder + buttonTextSecondaryIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'buttonTextSecondaryInt'); + }); + } + + QueryBuilder + checkboxBGCheckedIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'checkboxBGCheckedInt'); + }); + } + + QueryBuilder + checkboxBGDisabledIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'checkboxBGDisabledInt'); + }); + } + + QueryBuilder + checkboxBorderEmptyIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'checkboxBorderEmptyInt'); + }); + } + + QueryBuilder + checkboxIconCheckedIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'checkboxIconCheckedInt'); + }); + } + + QueryBuilder + checkboxIconDisabledIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'checkboxIconDisabledInt'); + }); + } + + QueryBuilder + checkboxTextLabelIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'checkboxTextLabelInt'); + }); + } + + QueryBuilder + coinColorsJsonStringProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'coinColorsJsonString'); + }); + } + + QueryBuilder + currencyListItemBGIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'currencyListItemBGInt'); + }); + } + + QueryBuilder + customTextButtonDisabledTextIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'customTextButtonDisabledTextInt'); + }); + } + + QueryBuilder + customTextButtonEnabledTextIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'customTextButtonEnabledTextInt'); + }); + } + + QueryBuilder ethTagBGIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'ethTagBGInt'); + }); + } + + QueryBuilder ethTagTextIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'ethTagTextInt'); + }); + } + + QueryBuilder ethWalletTagBGIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'ethWalletTagBGInt'); + }); + } + + QueryBuilder + ethWalletTagTextIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'ethWalletTagTextInt'); + }); + } + + QueryBuilder + favoriteStarActiveIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'favoriteStarActiveInt'); + }); + } + + QueryBuilder + favoriteStarInactiveIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'favoriteStarInactiveInt'); + }); + } + + QueryBuilder + gradientBackgroundStringProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'gradientBackgroundString'); + }); + } + + QueryBuilder highlightIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'highlightInt'); + }); + } + + QueryBuilder + homeViewButtonBarBoxShadowStringProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'homeViewButtonBarBoxShadowString'); + }); + } + + QueryBuilder infoItemBGIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'infoItemBGInt'); + }); + } + + QueryBuilder infoItemIconsIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'infoItemIconsInt'); + }); + } + + QueryBuilder infoItemLabelIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'infoItemLabelInt'); + }); + } + + QueryBuilder infoItemTextIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'infoItemTextInt'); + }); + } + + QueryBuilder + loadingOverlayTextColorIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'loadingOverlayTextColorInt'); + }); + } + + QueryBuilder + myStackContactIconBGIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'myStackContactIconBGInt'); + }); + } + + QueryBuilder nameProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'name'); + }); + } + + QueryBuilder + numberBackDefaultIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'numberBackDefaultInt'); + }); + } + + QueryBuilder + numberTextDefaultIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'numberTextDefaultInt'); + }); + } + + QueryBuilder + numpadBackDefaultIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'numpadBackDefaultInt'); + }); + } + + QueryBuilder + numpadTextDefaultIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'numpadTextDefaultInt'); + }); + } + + QueryBuilder overlayIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'overlayInt'); + }); + } + + QueryBuilder popupBGIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'popupBGInt'); + }); + } + + QueryBuilder + radioButtonBorderDisabledIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'radioButtonBorderDisabledInt'); + }); + } + + QueryBuilder + radioButtonBorderEnabledIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'radioButtonBorderEnabledInt'); + }); + } + + QueryBuilder + radioButtonIconBorderDisabledIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'radioButtonIconBorderDisabledInt'); + }); + } + + QueryBuilder + radioButtonIconBorderIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'radioButtonIconBorderInt'); + }); + } + + QueryBuilder + radioButtonIconCircleIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'radioButtonIconCircleInt'); + }); + } + + QueryBuilder + radioButtonIconEnabledIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'radioButtonIconEnabledInt'); + }); + } + + QueryBuilder + radioButtonLabelDisabledIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'radioButtonLabelDisabledInt'); + }); + } + + QueryBuilder + radioButtonLabelEnabledIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'radioButtonLabelEnabledInt'); + }); + } + + QueryBuilder + radioButtonTextDisabledIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'radioButtonTextDisabledInt'); + }); + } + + QueryBuilder + radioButtonTextEnabledIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'radioButtonTextEnabledInt'); + }); + } + + QueryBuilder + rateTypeToggleColorOffIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'rateTypeToggleColorOffInt'); + }); + } + + QueryBuilder + rateTypeToggleColorOnIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'rateTypeToggleColorOnInt'); + }); + } + + QueryBuilder + rateTypeToggleDesktopColorOffIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'rateTypeToggleDesktopColorOffInt'); + }); + } + + QueryBuilder + rateTypeToggleDesktopColorOnIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'rateTypeToggleDesktopColorOnInt'); + }); + } + + QueryBuilder + settingsIconBack2IntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'settingsIconBack2Int'); + }); + } + + QueryBuilder + settingsIconBackIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'settingsIconBackInt'); + }); + } + + QueryBuilder + settingsIconElementIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'settingsIconElementInt'); + }); + } + + QueryBuilder + settingsIconIconIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'settingsIconIconInt'); + }); + } + + QueryBuilder + settingsItem2ActiveBGIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'settingsItem2ActiveBGInt'); + }); + } + + QueryBuilder + settingsItem2ActiveSubIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'settingsItem2ActiveSubInt'); + }); + } + + QueryBuilder + settingsItem2ActiveTextIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'settingsItem2ActiveTextInt'); + }); + } + + QueryBuilder shadowIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'shadowInt'); + }); + } + + QueryBuilder + snackBarBackErrorIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'snackBarBackErrorInt'); + }); + } + + QueryBuilder + snackBarBackInfoIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'snackBarBackInfoInt'); + }); + } + + QueryBuilder + snackBarBackSuccessIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'snackBarBackSuccessInt'); + }); + } + + QueryBuilder + snackBarTextErrorIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'snackBarTextErrorInt'); + }); + } + + QueryBuilder + snackBarTextInfoIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'snackBarTextInfoInt'); + }); + } + + QueryBuilder + snackBarTextSuccessIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'snackBarTextSuccessInt'); + }); + } + + QueryBuilder splashIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'splashInt'); + }); + } + + QueryBuilder stackWalletBGIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'stackWalletBGInt'); + }); + } + + QueryBuilder + stackWalletBottomIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'stackWalletBottomInt'); + }); + } + + QueryBuilder stackWalletMidIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'stackWalletMidInt'); + }); + } + + QueryBuilder + standardBoxShadowStringProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'standardBoxShadowString'); + }); + } + + QueryBuilder + stepIndicatorBGCheckIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'stepIndicatorBGCheckInt'); + }); + } + + QueryBuilder + stepIndicatorBGInactiveIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'stepIndicatorBGInactiveInt'); + }); + } + + QueryBuilder + stepIndicatorBGLinesInactiveIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'stepIndicatorBGLinesInactiveInt'); + }); + } + + QueryBuilder + stepIndicatorBGLinesIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'stepIndicatorBGLinesInt'); + }); + } + + QueryBuilder + stepIndicatorBGNumberIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'stepIndicatorBGNumberInt'); + }); + } + + QueryBuilder + stepIndicatorIconInactiveIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'stepIndicatorIconInactiveInt'); + }); + } + + QueryBuilder + stepIndicatorIconNumberIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'stepIndicatorIconNumberInt'); + }); + } + + QueryBuilder + stepIndicatorIconTextIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'stepIndicatorIconTextInt'); + }); + } + + QueryBuilder + switchBGDisabledIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'switchBGDisabledInt'); + }); + } + + QueryBuilder switchBGOffIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'switchBGOffInt'); + }); + } + + QueryBuilder switchBGOnIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'switchBGOnInt'); + }); + } + + QueryBuilder + switchCircleDisabledIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'switchCircleDisabledInt'); + }); + } + + QueryBuilder switchCircleOffIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'switchCircleOffInt'); + }); + } + + QueryBuilder switchCircleOnIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'switchCircleOnInt'); + }); + } + + QueryBuilder + textConfirmTotalAmountIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textConfirmTotalAmountInt'); + }); + } + + QueryBuilder textDark2IntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textDark2Int'); + }); + } + + QueryBuilder textDark3IntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textDark3Int'); + }); + } + + QueryBuilder textDarkIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textDarkInt'); + }); + } + + QueryBuilder textErrorIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textErrorInt'); + }); + } + + QueryBuilder + textFavoriteCardIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textFavoriteCardInt'); + }); + } + + QueryBuilder + textFieldActiveBGIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textFieldActiveBGInt'); + }); + } + + QueryBuilder + textFieldActiveLabelIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textFieldActiveLabelInt'); + }); + } + + QueryBuilder + textFieldActiveSearchIconLeftIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textFieldActiveSearchIconLeftInt'); + }); + } + + QueryBuilder + textFieldActiveSearchIconRightIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textFieldActiveSearchIconRightInt'); + }); + } + + QueryBuilder + textFieldActiveTextIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textFieldActiveTextInt'); + }); + } + + QueryBuilder + textFieldDefaultBGIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textFieldDefaultBGInt'); + }); + } + + QueryBuilder + textFieldDefaultSearchIconLeftIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textFieldDefaultSearchIconLeftInt'); + }); + } + + QueryBuilder + textFieldDefaultSearchIconRightIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textFieldDefaultSearchIconRightInt'); + }); + } + + QueryBuilder + textFieldDefaultTextIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textFieldDefaultTextInt'); + }); + } + + QueryBuilder + textFieldErrorBGIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textFieldErrorBGInt'); + }); + } + + QueryBuilder + textFieldErrorBorderIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textFieldErrorBorderInt'); + }); + } + + QueryBuilder + textFieldErrorLabelIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textFieldErrorLabelInt'); + }); + } + + QueryBuilder + textFieldErrorSearchIconLeftIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textFieldErrorSearchIconLeftInt'); + }); + } + + QueryBuilder + textFieldErrorSearchIconRightIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textFieldErrorSearchIconRightInt'); + }); + } + + QueryBuilder + textFieldErrorTextIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textFieldErrorTextInt'); + }); + } + + QueryBuilder + textFieldSuccessBGIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textFieldSuccessBGInt'); + }); + } + + QueryBuilder + textFieldSuccessBorderIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textFieldSuccessBorderInt'); + }); + } + + QueryBuilder + textFieldSuccessLabelIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textFieldSuccessLabelInt'); + }); + } + + QueryBuilder + textFieldSuccessSearchIconLeftIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textFieldSuccessSearchIconLeftInt'); + }); + } + + QueryBuilder + textFieldSuccessSearchIconRightIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textFieldSuccessSearchIconRightInt'); + }); + } + + QueryBuilder + textFieldSuccessTextIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textFieldSuccessTextInt'); + }); + } + + QueryBuilder textRestoreIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textRestoreInt'); + }); + } + + QueryBuilder + textSelectedWordTableItemIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textSelectedWordTableItemInt'); + }); + } + + QueryBuilder textSubtitle1IntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textSubtitle1Int'); + }); + } + + QueryBuilder textSubtitle2IntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textSubtitle2Int'); + }); + } + + QueryBuilder textSubtitle3IntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textSubtitle3Int'); + }); + } + + QueryBuilder textSubtitle4IntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textSubtitle4Int'); + }); + } + + QueryBuilder textSubtitle5IntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textSubtitle5Int'); + }); + } + + QueryBuilder textSubtitle6IntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textSubtitle6Int'); + }); + } + + QueryBuilder textWhiteIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'textWhiteInt'); + }); + } + + QueryBuilder themeIdProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'themeId'); + }); + } + + QueryBuilder tokenSummaryBGIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'tokenSummaryBGInt'); + }); + } + + QueryBuilder + tokenSummaryButtonBGIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'tokenSummaryButtonBGInt'); + }); + } + + QueryBuilder + tokenSummaryIconIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'tokenSummaryIconInt'); + }); + } + + QueryBuilder + tokenSummaryTextPrimaryIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'tokenSummaryTextPrimaryInt'); + }); + } + + QueryBuilder + tokenSummaryTextSecondaryIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'tokenSummaryTextSecondaryInt'); + }); + } + + QueryBuilder topNavIconGreenIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'topNavIconGreenInt'); + }); + } + + QueryBuilder + topNavIconPrimaryIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'topNavIconPrimaryInt'); + }); + } + + QueryBuilder topNavIconRedIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'topNavIconRedInt'); + }); + } + + QueryBuilder + topNavIconYellowIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'topNavIconYellowInt'); + }); + } + + QueryBuilder versionProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'version'); + }); + } + + QueryBuilder + warningBackgroundIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'warningBackgroundInt'); + }); + } + + QueryBuilder + warningForegroundIntProperty() { + return QueryBuilder.apply(this, (query) { + return query.addPropertyName(r'warningForegroundInt'); + }); + } +} + +// ************************************************************************** +// IsarEmbeddedGenerator +// ************************************************************************** + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters + +const ThemeAssetsSchema = Schema( + name: r'ThemeAssets', + id: -4567972595101029828, + properties: { + r'background': PropertySchema( + id: 0, + name: r'background', + type: IsarType.string, + ), + r'bellNew': PropertySchema( + id: 1, + name: r'bellNew', + type: IsarType.string, + ), + r'bitcoin': PropertySchema( + id: 2, + name: r'bitcoin', + type: IsarType.string, + ), + r'bitcoinImage': PropertySchema( + id: 3, + name: r'bitcoinImage', + type: IsarType.string, + ), + r'bitcoinImageSecondary': PropertySchema( + id: 4, + name: r'bitcoinImageSecondary', + type: IsarType.string, + ), + r'bitcoincash': PropertySchema( + id: 5, + name: r'bitcoincash', + type: IsarType.string, + ), + r'bitcoincashImage': PropertySchema( + id: 6, + name: r'bitcoincashImage', + type: IsarType.string, + ), + r'bitcoincashImageSecondary': PropertySchema( + id: 7, + name: r'bitcoincashImageSecondary', + type: IsarType.string, + ), + r'buy': PropertySchema( + id: 8, + name: r'buy', + type: IsarType.string, + ), + r'dogecoin': PropertySchema( + id: 9, + name: r'dogecoin', + type: IsarType.string, + ), + r'dogecoinImage': PropertySchema( + id: 10, + name: r'dogecoinImage', + type: IsarType.string, + ), + r'dogecoinImageSecondary': PropertySchema( + id: 11, + name: r'dogecoinImageSecondary', + type: IsarType.string, + ), + r'epicCash': PropertySchema( + id: 12, + name: r'epicCash', + type: IsarType.string, + ), + r'epicCashImage': PropertySchema( + id: 13, + name: r'epicCashImage', + type: IsarType.string, + ), + r'epicCashImageSecondary': PropertySchema( + id: 14, + name: r'epicCashImageSecondary', + type: IsarType.string, + ), + r'ethereum': PropertySchema( + id: 15, + name: r'ethereum', + type: IsarType.string, + ), + r'ethereumImage': PropertySchema( + id: 16, + name: r'ethereumImage', + type: IsarType.string, + ), + r'ethereumImageSecondary': PropertySchema( + id: 17, + name: r'ethereumImageSecondary', + type: IsarType.string, + ), + r'exchange': PropertySchema( + id: 18, + name: r'exchange', + type: IsarType.string, + ), + r'firo': PropertySchema( + id: 19, + name: r'firo', + type: IsarType.string, + ), + r'firoImage': PropertySchema( + id: 20, + name: r'firoImage', + type: IsarType.string, + ), + r'firoImageSecondary': PropertySchema( + id: 21, + name: r'firoImageSecondary', + type: IsarType.string, + ), + r'litecoin': PropertySchema( + id: 22, + name: r'litecoin', + type: IsarType.string, + ), + r'litecoinImage': PropertySchema( + id: 23, + name: r'litecoinImage', + type: IsarType.string, + ), + r'litecoinImageSecondary': PropertySchema( + id: 24, + name: r'litecoinImageSecondary', + type: IsarType.string, + ), + r'loadingGif': PropertySchema( + id: 25, + name: r'loadingGif', + type: IsarType.string, + ), + r'monero': PropertySchema( + id: 26, + name: r'monero', + type: IsarType.string, + ), + r'moneroImage': PropertySchema( + id: 27, + name: r'moneroImage', + type: IsarType.string, + ), + r'moneroImageSecondary': PropertySchema( + id: 28, + name: r'moneroImageSecondary', + type: IsarType.string, + ), + r'namecoin': PropertySchema( + id: 29, + name: r'namecoin', + type: IsarType.string, + ), + r'namecoinImage': PropertySchema( + id: 30, + name: r'namecoinImage', + type: IsarType.string, + ), + r'namecoinImageSecondary': PropertySchema( + id: 31, + name: r'namecoinImageSecondary', + type: IsarType.string, + ), + r'particl': PropertySchema( + id: 32, + name: r'particl', + type: IsarType.string, + ), + r'particlImage': PropertySchema( + id: 33, + name: r'particlImage', + type: IsarType.string, + ), + r'particlImageSecondary': PropertySchema( + id: 34, + name: r'particlImageSecondary', + type: IsarType.string, + ), + r'personaEasy': PropertySchema( + id: 35, + name: r'personaEasy', + type: IsarType.string, + ), + r'personaIncognito': PropertySchema( + id: 36, + name: r'personaIncognito', + type: IsarType.string, + ), + r'receive': PropertySchema( + id: 37, + name: r'receive', + type: IsarType.string, + ), + r'receiveCancelled': PropertySchema( + id: 38, + name: r'receiveCancelled', + type: IsarType.string, + ), + r'receivePending': PropertySchema( + id: 39, + name: r'receivePending', + type: IsarType.string, + ), + r'send': PropertySchema( + id: 40, + name: r'send', + type: IsarType.string, + ), + r'sendCancelled': PropertySchema( + id: 41, + name: r'sendCancelled', + type: IsarType.string, + ), + r'sendPending': PropertySchema( + id: 42, + name: r'sendPending', + type: IsarType.string, + ), + r'stack': PropertySchema( + id: 43, + name: r'stack', + type: IsarType.string, + ), + r'stackIcon': PropertySchema( + id: 44, + name: r'stackIcon', + type: IsarType.string, + ), + r'themePreview': PropertySchema( + id: 45, + name: r'themePreview', + type: IsarType.string, + ), + r'themeSelector': PropertySchema( + id: 46, + name: r'themeSelector', + type: IsarType.string, + ), + r'txExchange': PropertySchema( + id: 47, + name: r'txExchange', + type: IsarType.string, + ), + r'txExchangeFailed': PropertySchema( + id: 48, + name: r'txExchangeFailed', + type: IsarType.string, + ), + r'txExchangePending': PropertySchema( + id: 49, + name: r'txExchangePending', + type: IsarType.string, + ), + r'wownero': PropertySchema( + id: 50, + name: r'wownero', + type: IsarType.string, + ), + r'wowneroImage': PropertySchema( + id: 51, + name: r'wowneroImage', + type: IsarType.string, + ), + r'wowneroImageSecondary': PropertySchema( + id: 52, + name: r'wowneroImageSecondary', + type: IsarType.string, + ) + }, + estimateSize: _themeAssetsEstimateSize, + serialize: _themeAssetsSerialize, + deserialize: _themeAssetsDeserialize, + deserializeProp: _themeAssetsDeserializeProp, +); + +int _themeAssetsEstimateSize( + ThemeAssets object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + { + final value = object.background; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + bytesCount += 3 + object.bellNew.length * 3; + bytesCount += 3 + object.bitcoin.length * 3; + bytesCount += 3 + object.bitcoinImage.length * 3; + bytesCount += 3 + object.bitcoinImageSecondary.length * 3; + bytesCount += 3 + object.bitcoincash.length * 3; + bytesCount += 3 + object.bitcoincashImage.length * 3; + bytesCount += 3 + object.bitcoincashImageSecondary.length * 3; + bytesCount += 3 + object.buy.length * 3; + bytesCount += 3 + object.dogecoin.length * 3; + bytesCount += 3 + object.dogecoinImage.length * 3; + bytesCount += 3 + object.dogecoinImageSecondary.length * 3; + bytesCount += 3 + object.epicCash.length * 3; + bytesCount += 3 + object.epicCashImage.length * 3; + bytesCount += 3 + object.epicCashImageSecondary.length * 3; + bytesCount += 3 + object.ethereum.length * 3; + bytesCount += 3 + object.ethereumImage.length * 3; + bytesCount += 3 + object.ethereumImageSecondary.length * 3; + bytesCount += 3 + object.exchange.length * 3; + bytesCount += 3 + object.firo.length * 3; + bytesCount += 3 + object.firoImage.length * 3; + bytesCount += 3 + object.firoImageSecondary.length * 3; + bytesCount += 3 + object.litecoin.length * 3; + bytesCount += 3 + object.litecoinImage.length * 3; + bytesCount += 3 + object.litecoinImageSecondary.length * 3; + { + final value = object.loadingGif; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + bytesCount += 3 + object.monero.length * 3; + bytesCount += 3 + object.moneroImage.length * 3; + bytesCount += 3 + object.moneroImageSecondary.length * 3; + bytesCount += 3 + object.namecoin.length * 3; + bytesCount += 3 + object.namecoinImage.length * 3; + bytesCount += 3 + object.namecoinImageSecondary.length * 3; + bytesCount += 3 + object.particl.length * 3; + bytesCount += 3 + object.particlImage.length * 3; + bytesCount += 3 + object.particlImageSecondary.length * 3; + bytesCount += 3 + object.personaEasy.length * 3; + bytesCount += 3 + object.personaIncognito.length * 3; + bytesCount += 3 + object.receive.length * 3; + bytesCount += 3 + object.receiveCancelled.length * 3; + bytesCount += 3 + object.receivePending.length * 3; + bytesCount += 3 + object.send.length * 3; + bytesCount += 3 + object.sendCancelled.length * 3; + bytesCount += 3 + object.sendPending.length * 3; + bytesCount += 3 + object.stack.length * 3; + bytesCount += 3 + object.stackIcon.length * 3; + bytesCount += 3 + object.themePreview.length * 3; + bytesCount += 3 + object.themeSelector.length * 3; + bytesCount += 3 + object.txExchange.length * 3; + bytesCount += 3 + object.txExchangeFailed.length * 3; + bytesCount += 3 + object.txExchangePending.length * 3; + bytesCount += 3 + object.wownero.length * 3; + bytesCount += 3 + object.wowneroImage.length * 3; + bytesCount += 3 + object.wowneroImageSecondary.length * 3; + return bytesCount; +} + +void _themeAssetsSerialize( + ThemeAssets object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeString(offsets[0], object.background); + writer.writeString(offsets[1], object.bellNew); + writer.writeString(offsets[2], object.bitcoin); + writer.writeString(offsets[3], object.bitcoinImage); + writer.writeString(offsets[4], object.bitcoinImageSecondary); + writer.writeString(offsets[5], object.bitcoincash); + writer.writeString(offsets[6], object.bitcoincashImage); + writer.writeString(offsets[7], object.bitcoincashImageSecondary); + writer.writeString(offsets[8], object.buy); + writer.writeString(offsets[9], object.dogecoin); + writer.writeString(offsets[10], object.dogecoinImage); + writer.writeString(offsets[11], object.dogecoinImageSecondary); + writer.writeString(offsets[12], object.epicCash); + writer.writeString(offsets[13], object.epicCashImage); + writer.writeString(offsets[14], object.epicCashImageSecondary); + writer.writeString(offsets[15], object.ethereum); + writer.writeString(offsets[16], object.ethereumImage); + writer.writeString(offsets[17], object.ethereumImageSecondary); + writer.writeString(offsets[18], object.exchange); + writer.writeString(offsets[19], object.firo); + writer.writeString(offsets[20], object.firoImage); + writer.writeString(offsets[21], object.firoImageSecondary); + writer.writeString(offsets[22], object.litecoin); + writer.writeString(offsets[23], object.litecoinImage); + writer.writeString(offsets[24], object.litecoinImageSecondary); + writer.writeString(offsets[25], object.loadingGif); + writer.writeString(offsets[26], object.monero); + writer.writeString(offsets[27], object.moneroImage); + writer.writeString(offsets[28], object.moneroImageSecondary); + writer.writeString(offsets[29], object.namecoin); + writer.writeString(offsets[30], object.namecoinImage); + writer.writeString(offsets[31], object.namecoinImageSecondary); + writer.writeString(offsets[32], object.particl); + writer.writeString(offsets[33], object.particlImage); + writer.writeString(offsets[34], object.particlImageSecondary); + writer.writeString(offsets[35], object.personaEasy); + writer.writeString(offsets[36], object.personaIncognito); + writer.writeString(offsets[37], object.receive); + writer.writeString(offsets[38], object.receiveCancelled); + writer.writeString(offsets[39], object.receivePending); + writer.writeString(offsets[40], object.send); + writer.writeString(offsets[41], object.sendCancelled); + writer.writeString(offsets[42], object.sendPending); + writer.writeString(offsets[43], object.stack); + writer.writeString(offsets[44], object.stackIcon); + writer.writeString(offsets[45], object.themePreview); + writer.writeString(offsets[46], object.themeSelector); + writer.writeString(offsets[47], object.txExchange); + writer.writeString(offsets[48], object.txExchangeFailed); + writer.writeString(offsets[49], object.txExchangePending); + writer.writeString(offsets[50], object.wownero); + writer.writeString(offsets[51], object.wowneroImage); + writer.writeString(offsets[52], object.wowneroImageSecondary); +} + +ThemeAssets _themeAssetsDeserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = ThemeAssets(); + object.background = reader.readStringOrNull(offsets[0]); + object.bellNew = reader.readString(offsets[1]); + object.bitcoin = reader.readString(offsets[2]); + object.bitcoinImage = reader.readString(offsets[3]); + object.bitcoinImageSecondary = reader.readString(offsets[4]); + object.bitcoincash = reader.readString(offsets[5]); + object.bitcoincashImage = reader.readString(offsets[6]); + object.bitcoincashImageSecondary = reader.readString(offsets[7]); + object.buy = reader.readString(offsets[8]); + object.dogecoin = reader.readString(offsets[9]); + object.dogecoinImage = reader.readString(offsets[10]); + object.dogecoinImageSecondary = reader.readString(offsets[11]); + object.epicCash = reader.readString(offsets[12]); + object.epicCashImage = reader.readString(offsets[13]); + object.epicCashImageSecondary = reader.readString(offsets[14]); + object.ethereum = reader.readString(offsets[15]); + object.ethereumImage = reader.readString(offsets[16]); + object.ethereumImageSecondary = reader.readString(offsets[17]); + object.exchange = reader.readString(offsets[18]); + object.firo = reader.readString(offsets[19]); + object.firoImage = reader.readString(offsets[20]); + object.firoImageSecondary = reader.readString(offsets[21]); + object.litecoin = reader.readString(offsets[22]); + object.litecoinImage = reader.readString(offsets[23]); + object.litecoinImageSecondary = reader.readString(offsets[24]); + object.loadingGif = reader.readStringOrNull(offsets[25]); + object.monero = reader.readString(offsets[26]); + object.moneroImage = reader.readString(offsets[27]); + object.moneroImageSecondary = reader.readString(offsets[28]); + object.namecoin = reader.readString(offsets[29]); + object.namecoinImage = reader.readString(offsets[30]); + object.namecoinImageSecondary = reader.readString(offsets[31]); + object.particl = reader.readString(offsets[32]); + object.particlImage = reader.readString(offsets[33]); + object.particlImageSecondary = reader.readString(offsets[34]); + object.personaEasy = reader.readString(offsets[35]); + object.personaIncognito = reader.readString(offsets[36]); + object.receive = reader.readString(offsets[37]); + object.receiveCancelled = reader.readString(offsets[38]); + object.receivePending = reader.readString(offsets[39]); + object.send = reader.readString(offsets[40]); + object.sendCancelled = reader.readString(offsets[41]); + object.sendPending = reader.readString(offsets[42]); + object.stack = reader.readString(offsets[43]); + object.stackIcon = reader.readString(offsets[44]); + object.themePreview = reader.readString(offsets[45]); + object.themeSelector = reader.readString(offsets[46]); + object.txExchange = reader.readString(offsets[47]); + object.txExchangeFailed = reader.readString(offsets[48]); + object.txExchangePending = reader.readString(offsets[49]); + object.wownero = reader.readString(offsets[50]); + object.wowneroImage = reader.readString(offsets[51]); + object.wowneroImageSecondary = reader.readString(offsets[52]); + return object; +} + +P _themeAssetsDeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readStringOrNull(offset)) as P; + case 1: + return (reader.readString(offset)) as P; + case 2: + return (reader.readString(offset)) as P; + case 3: + return (reader.readString(offset)) as P; + case 4: + return (reader.readString(offset)) as P; + case 5: + return (reader.readString(offset)) as P; + case 6: + return (reader.readString(offset)) as P; + case 7: + return (reader.readString(offset)) as P; + case 8: + return (reader.readString(offset)) as P; + case 9: + return (reader.readString(offset)) as P; + case 10: + return (reader.readString(offset)) as P; + case 11: + return (reader.readString(offset)) as P; + case 12: + return (reader.readString(offset)) as P; + case 13: + return (reader.readString(offset)) as P; + case 14: + return (reader.readString(offset)) as P; + case 15: + return (reader.readString(offset)) as P; + case 16: + return (reader.readString(offset)) as P; + case 17: + return (reader.readString(offset)) as P; + case 18: + return (reader.readString(offset)) as P; + case 19: + return (reader.readString(offset)) as P; + case 20: + return (reader.readString(offset)) as P; + case 21: + return (reader.readString(offset)) as P; + case 22: + return (reader.readString(offset)) as P; + case 23: + return (reader.readString(offset)) as P; + case 24: + return (reader.readString(offset)) as P; + case 25: + return (reader.readStringOrNull(offset)) as P; + case 26: + return (reader.readString(offset)) as P; + case 27: + return (reader.readString(offset)) as P; + case 28: + return (reader.readString(offset)) as P; + case 29: + return (reader.readString(offset)) as P; + case 30: + return (reader.readString(offset)) as P; + case 31: + return (reader.readString(offset)) as P; + case 32: + return (reader.readString(offset)) as P; + case 33: + return (reader.readString(offset)) as P; + case 34: + return (reader.readString(offset)) as P; + case 35: + return (reader.readString(offset)) as P; + case 36: + return (reader.readString(offset)) as P; + case 37: + return (reader.readString(offset)) as P; + case 38: + return (reader.readString(offset)) as P; + case 39: + return (reader.readString(offset)) as P; + case 40: + return (reader.readString(offset)) as P; + case 41: + return (reader.readString(offset)) as P; + case 42: + return (reader.readString(offset)) as P; + case 43: + return (reader.readString(offset)) as P; + case 44: + return (reader.readString(offset)) as P; + case 45: + return (reader.readString(offset)) as P; + case 46: + return (reader.readString(offset)) as P; + case 47: + return (reader.readString(offset)) as P; + case 48: + return (reader.readString(offset)) as P; + case 49: + return (reader.readString(offset)) as P; + case 50: + return (reader.readString(offset)) as P; + case 51: + return (reader.readString(offset)) as P; + case 52: + return (reader.readString(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +extension ThemeAssetsQueryFilter + on QueryBuilder { + QueryBuilder + backgroundIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'background', + )); + }); + } + + QueryBuilder + backgroundIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'background', + )); + }); + } + + QueryBuilder + backgroundEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'background', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + backgroundGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'background', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + backgroundLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'background', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + backgroundBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'background', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + backgroundStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'background', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + backgroundEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'background', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + backgroundContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'background', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + backgroundMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'background', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + backgroundIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'background', + value: '', + )); + }); + } + + QueryBuilder + backgroundIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'background', + value: '', + )); + }); + } + + QueryBuilder bellNewEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'bellNew', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bellNewGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'bellNew', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder bellNewLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'bellNew', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder bellNewBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'bellNew', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bellNewStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'bellNew', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder bellNewEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'bellNew', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder bellNewContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'bellNew', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder bellNewMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'bellNew', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bellNewIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'bellNew', + value: '', + )); + }); + } + + QueryBuilder + bellNewIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'bellNew', + value: '', + )); + }); + } + + QueryBuilder bitcoinEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'bitcoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoinGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'bitcoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder bitcoinLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'bitcoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder bitcoinBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'bitcoin', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoinStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'bitcoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder bitcoinEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'bitcoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder bitcoinContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'bitcoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder bitcoinMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'bitcoin', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoinIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'bitcoin', + value: '', + )); + }); + } + + QueryBuilder + bitcoinIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'bitcoin', + value: '', + )); + }); + } + + QueryBuilder + bitcoinImageEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'bitcoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoinImageGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'bitcoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoinImageLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'bitcoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoinImageBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'bitcoinImage', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoinImageStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'bitcoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoinImageEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'bitcoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoinImageContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'bitcoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoinImageMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'bitcoinImage', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoinImageIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'bitcoinImage', + value: '', + )); + }); + } + + QueryBuilder + bitcoinImageIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'bitcoinImage', + value: '', + )); + }); + } + + QueryBuilder + bitcoinImageSecondaryEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'bitcoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoinImageSecondaryGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'bitcoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoinImageSecondaryLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'bitcoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoinImageSecondaryBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'bitcoinImageSecondary', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoinImageSecondaryStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'bitcoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoinImageSecondaryEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'bitcoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoinImageSecondaryContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'bitcoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoinImageSecondaryMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'bitcoinImageSecondary', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoinImageSecondaryIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'bitcoinImageSecondary', + value: '', + )); + }); + } + + QueryBuilder + bitcoinImageSecondaryIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'bitcoinImageSecondary', + value: '', + )); + }); + } + + QueryBuilder + bitcoincashEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'bitcoincash', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoincashGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'bitcoincash', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoincashLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'bitcoincash', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoincashBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'bitcoincash', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoincashStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'bitcoincash', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoincashEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'bitcoincash', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoincashContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'bitcoincash', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoincashMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'bitcoincash', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoincashIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'bitcoincash', + value: '', + )); + }); + } + + QueryBuilder + bitcoincashIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'bitcoincash', + value: '', + )); + }); + } + + QueryBuilder + bitcoincashImageEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'bitcoincashImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoincashImageGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'bitcoincashImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoincashImageLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'bitcoincashImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoincashImageBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'bitcoincashImage', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoincashImageStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'bitcoincashImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoincashImageEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'bitcoincashImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoincashImageContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'bitcoincashImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoincashImageMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'bitcoincashImage', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoincashImageIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'bitcoincashImage', + value: '', + )); + }); + } + + QueryBuilder + bitcoincashImageIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'bitcoincashImage', + value: '', + )); + }); + } + + QueryBuilder + bitcoincashImageSecondaryEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'bitcoincashImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoincashImageSecondaryGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'bitcoincashImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoincashImageSecondaryLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'bitcoincashImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoincashImageSecondaryBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'bitcoincashImageSecondary', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoincashImageSecondaryStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'bitcoincashImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoincashImageSecondaryEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'bitcoincashImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoincashImageSecondaryContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'bitcoincashImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoincashImageSecondaryMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'bitcoincashImageSecondary', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bitcoincashImageSecondaryIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'bitcoincashImageSecondary', + value: '', + )); + }); + } + + QueryBuilder + bitcoincashImageSecondaryIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'bitcoincashImageSecondary', + value: '', + )); + }); + } + + QueryBuilder buyEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'buy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder buyGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'buy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder buyLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'buy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder buyBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'buy', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder buyStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'buy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder buyEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'buy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder buyContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'buy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder buyMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'buy', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder buyIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'buy', + value: '', + )); + }); + } + + QueryBuilder + buyIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'buy', + value: '', + )); + }); + } + + QueryBuilder dogecoinEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'dogecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + dogecoinGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'dogecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + dogecoinLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'dogecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder dogecoinBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'dogecoin', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + dogecoinStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'dogecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + dogecoinEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'dogecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + dogecoinContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'dogecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder dogecoinMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'dogecoin', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + dogecoinIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'dogecoin', + value: '', + )); + }); + } + + QueryBuilder + dogecoinIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'dogecoin', + value: '', + )); + }); + } + + QueryBuilder + dogecoinImageEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'dogecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + dogecoinImageGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'dogecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + dogecoinImageLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'dogecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + dogecoinImageBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'dogecoinImage', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + dogecoinImageStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'dogecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + dogecoinImageEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'dogecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + dogecoinImageContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'dogecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + dogecoinImageMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'dogecoinImage', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + dogecoinImageIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'dogecoinImage', + value: '', + )); + }); + } + + QueryBuilder + dogecoinImageIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'dogecoinImage', + value: '', + )); + }); + } + + QueryBuilder + dogecoinImageSecondaryEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'dogecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + dogecoinImageSecondaryGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'dogecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + dogecoinImageSecondaryLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'dogecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + dogecoinImageSecondaryBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'dogecoinImageSecondary', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + dogecoinImageSecondaryStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'dogecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + dogecoinImageSecondaryEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'dogecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + dogecoinImageSecondaryContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'dogecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + dogecoinImageSecondaryMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'dogecoinImageSecondary', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + dogecoinImageSecondaryIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'dogecoinImageSecondary', + value: '', + )); + }); + } + + QueryBuilder + dogecoinImageSecondaryIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'dogecoinImageSecondary', + value: '', + )); + }); + } + + QueryBuilder epicCashEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'epicCash', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + epicCashGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'epicCash', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + epicCashLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'epicCash', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder epicCashBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'epicCash', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + epicCashStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'epicCash', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + epicCashEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'epicCash', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + epicCashContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'epicCash', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder epicCashMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'epicCash', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + epicCashIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'epicCash', + value: '', + )); + }); + } + + QueryBuilder + epicCashIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'epicCash', + value: '', + )); + }); + } + + QueryBuilder + epicCashImageEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'epicCashImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + epicCashImageGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'epicCashImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + epicCashImageLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'epicCashImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + epicCashImageBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'epicCashImage', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + epicCashImageStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'epicCashImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + epicCashImageEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'epicCashImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + epicCashImageContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'epicCashImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + epicCashImageMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'epicCashImage', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + epicCashImageIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'epicCashImage', + value: '', + )); + }); + } + + QueryBuilder + epicCashImageIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'epicCashImage', + value: '', + )); + }); + } + + QueryBuilder + epicCashImageSecondaryEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'epicCashImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + epicCashImageSecondaryGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'epicCashImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + epicCashImageSecondaryLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'epicCashImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + epicCashImageSecondaryBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'epicCashImageSecondary', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + epicCashImageSecondaryStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'epicCashImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + epicCashImageSecondaryEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'epicCashImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + epicCashImageSecondaryContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'epicCashImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + epicCashImageSecondaryMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'epicCashImageSecondary', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + epicCashImageSecondaryIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'epicCashImageSecondary', + value: '', + )); + }); + } + + QueryBuilder + epicCashImageSecondaryIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'epicCashImageSecondary', + value: '', + )); + }); + } + + QueryBuilder ethereumEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'ethereum', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ethereumGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'ethereum', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ethereumLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'ethereum', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder ethereumBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'ethereum', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ethereumStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'ethereum', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ethereumEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'ethereum', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ethereumContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'ethereum', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder ethereumMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'ethereum', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ethereumIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'ethereum', + value: '', + )); + }); + } + + QueryBuilder + ethereumIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'ethereum', + value: '', + )); + }); + } + + QueryBuilder + ethereumImageEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'ethereumImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ethereumImageGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'ethereumImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ethereumImageLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'ethereumImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ethereumImageBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'ethereumImage', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ethereumImageStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'ethereumImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ethereumImageEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'ethereumImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ethereumImageContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'ethereumImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ethereumImageMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'ethereumImage', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ethereumImageIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'ethereumImage', + value: '', + )); + }); + } + + QueryBuilder + ethereumImageIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'ethereumImage', + value: '', + )); + }); + } + + QueryBuilder + ethereumImageSecondaryEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'ethereumImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ethereumImageSecondaryGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'ethereumImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ethereumImageSecondaryLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'ethereumImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ethereumImageSecondaryBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'ethereumImageSecondary', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ethereumImageSecondaryStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'ethereumImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ethereumImageSecondaryEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'ethereumImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ethereumImageSecondaryContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'ethereumImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ethereumImageSecondaryMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'ethereumImageSecondary', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + ethereumImageSecondaryIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'ethereumImageSecondary', + value: '', + )); + }); + } + + QueryBuilder + ethereumImageSecondaryIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'ethereumImageSecondary', + value: '', + )); + }); + } + + QueryBuilder exchangeEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'exchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + exchangeGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'exchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + exchangeLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'exchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder exchangeBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'exchange', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + exchangeStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'exchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + exchangeEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'exchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + exchangeContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'exchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder exchangeMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'exchange', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + exchangeIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'exchange', + value: '', + )); + }); + } + + QueryBuilder + exchangeIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'exchange', + value: '', + )); + }); + } + + QueryBuilder firoEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'firo', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder firoGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'firo', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder firoLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'firo', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder firoBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'firo', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder firoStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'firo', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder firoEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'firo', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder firoContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'firo', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder firoMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'firo', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder firoIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'firo', + value: '', + )); + }); + } + + QueryBuilder + firoIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'firo', + value: '', + )); + }); + } + + QueryBuilder + firoImageEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'firoImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + firoImageGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'firoImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + firoImageLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'firoImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + firoImageBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'firoImage', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + firoImageStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'firoImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + firoImageEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'firoImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + firoImageContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'firoImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + firoImageMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'firoImage', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + firoImageIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'firoImage', + value: '', + )); + }); + } + + QueryBuilder + firoImageIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'firoImage', + value: '', + )); + }); + } + + QueryBuilder + firoImageSecondaryEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'firoImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + firoImageSecondaryGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'firoImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + firoImageSecondaryLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'firoImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + firoImageSecondaryBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'firoImageSecondary', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + firoImageSecondaryStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'firoImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + firoImageSecondaryEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'firoImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + firoImageSecondaryContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'firoImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + firoImageSecondaryMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'firoImageSecondary', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + firoImageSecondaryIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'firoImageSecondary', + value: '', + )); + }); + } + + QueryBuilder + firoImageSecondaryIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'firoImageSecondary', + value: '', + )); + }); + } + + QueryBuilder litecoinEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'litecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + litecoinGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'litecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + litecoinLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'litecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder litecoinBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'litecoin', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + litecoinStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'litecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + litecoinEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'litecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + litecoinContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'litecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder litecoinMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'litecoin', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + litecoinIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'litecoin', + value: '', + )); + }); + } + + QueryBuilder + litecoinIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'litecoin', + value: '', + )); + }); + } + + QueryBuilder + litecoinImageEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'litecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + litecoinImageGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'litecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + litecoinImageLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'litecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + litecoinImageBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'litecoinImage', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + litecoinImageStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'litecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + litecoinImageEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'litecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + litecoinImageContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'litecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + litecoinImageMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'litecoinImage', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + litecoinImageIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'litecoinImage', + value: '', + )); + }); + } + + QueryBuilder + litecoinImageIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'litecoinImage', + value: '', + )); + }); + } + + QueryBuilder + litecoinImageSecondaryEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'litecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + litecoinImageSecondaryGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'litecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + litecoinImageSecondaryLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'litecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + litecoinImageSecondaryBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'litecoinImageSecondary', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + litecoinImageSecondaryStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'litecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + litecoinImageSecondaryEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'litecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + litecoinImageSecondaryContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'litecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + litecoinImageSecondaryMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'litecoinImageSecondary', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + litecoinImageSecondaryIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'litecoinImageSecondary', + value: '', + )); + }); + } + + QueryBuilder + litecoinImageSecondaryIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'litecoinImageSecondary', + value: '', + )); + }); + } + + QueryBuilder + loadingGifIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'loadingGif', + )); + }); + } + + QueryBuilder + loadingGifIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'loadingGif', + )); + }); + } + + QueryBuilder + loadingGifEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'loadingGif', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + loadingGifGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'loadingGif', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + loadingGifLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'loadingGif', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + loadingGifBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'loadingGif', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + loadingGifStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'loadingGif', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + loadingGifEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'loadingGif', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + loadingGifContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'loadingGif', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + loadingGifMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'loadingGif', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + loadingGifIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'loadingGif', + value: '', + )); + }); + } + + QueryBuilder + loadingGifIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'loadingGif', + value: '', + )); + }); + } + + QueryBuilder moneroEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'monero', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + moneroGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'monero', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder moneroLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'monero', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder moneroBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'monero', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + moneroStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'monero', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder moneroEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'monero', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder moneroContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'monero', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder moneroMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'monero', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + moneroIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'monero', + value: '', + )); + }); + } + + QueryBuilder + moneroIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'monero', + value: '', + )); + }); + } + + QueryBuilder + moneroImageEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'moneroImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + moneroImageGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'moneroImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + moneroImageLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'moneroImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + moneroImageBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'moneroImage', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + moneroImageStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'moneroImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + moneroImageEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'moneroImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + moneroImageContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'moneroImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + moneroImageMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'moneroImage', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + moneroImageIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'moneroImage', + value: '', + )); + }); + } + + QueryBuilder + moneroImageIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'moneroImage', + value: '', + )); + }); + } + + QueryBuilder + moneroImageSecondaryEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'moneroImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + moneroImageSecondaryGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'moneroImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + moneroImageSecondaryLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'moneroImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + moneroImageSecondaryBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'moneroImageSecondary', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + moneroImageSecondaryStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'moneroImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + moneroImageSecondaryEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'moneroImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + moneroImageSecondaryContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'moneroImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + moneroImageSecondaryMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'moneroImageSecondary', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + moneroImageSecondaryIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'moneroImageSecondary', + value: '', + )); + }); + } + + QueryBuilder + moneroImageSecondaryIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'moneroImageSecondary', + value: '', + )); + }); + } + + QueryBuilder namecoinEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'namecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + namecoinGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'namecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + namecoinLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'namecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder namecoinBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'namecoin', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + namecoinStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'namecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + namecoinEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'namecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + namecoinContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'namecoin', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder namecoinMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'namecoin', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + namecoinIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'namecoin', + value: '', + )); + }); + } + + QueryBuilder + namecoinIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'namecoin', + value: '', + )); + }); + } + + QueryBuilder + namecoinImageEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'namecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + namecoinImageGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'namecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + namecoinImageLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'namecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + namecoinImageBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'namecoinImage', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + namecoinImageStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'namecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + namecoinImageEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'namecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + namecoinImageContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'namecoinImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + namecoinImageMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'namecoinImage', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + namecoinImageIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'namecoinImage', + value: '', + )); + }); + } + + QueryBuilder + namecoinImageIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'namecoinImage', + value: '', + )); + }); + } + + QueryBuilder + namecoinImageSecondaryEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'namecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + namecoinImageSecondaryGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'namecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + namecoinImageSecondaryLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'namecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + namecoinImageSecondaryBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'namecoinImageSecondary', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + namecoinImageSecondaryStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'namecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + namecoinImageSecondaryEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'namecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + namecoinImageSecondaryContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'namecoinImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + namecoinImageSecondaryMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'namecoinImageSecondary', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + namecoinImageSecondaryIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'namecoinImageSecondary', + value: '', + )); + }); + } + + QueryBuilder + namecoinImageSecondaryIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'namecoinImageSecondary', + value: '', + )); + }); + } + + QueryBuilder particlEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'particl', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + particlGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'particl', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder particlLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'particl', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder particlBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'particl', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + particlStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'particl', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder particlEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'particl', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder particlContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'particl', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder particlMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'particl', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + particlIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'particl', + value: '', + )); + }); + } + + QueryBuilder + particlIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'particl', + value: '', + )); + }); + } + + QueryBuilder + particlImageEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'particlImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + particlImageGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'particlImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + particlImageLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'particlImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + particlImageBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'particlImage', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + particlImageStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'particlImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + particlImageEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'particlImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + particlImageContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'particlImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + particlImageMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'particlImage', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + particlImageIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'particlImage', + value: '', + )); + }); + } + + QueryBuilder + particlImageIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'particlImage', + value: '', + )); + }); + } + + QueryBuilder + particlImageSecondaryEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'particlImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + particlImageSecondaryGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'particlImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + particlImageSecondaryLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'particlImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + particlImageSecondaryBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'particlImageSecondary', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + particlImageSecondaryStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'particlImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + particlImageSecondaryEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'particlImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + particlImageSecondaryContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'particlImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + particlImageSecondaryMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'particlImageSecondary', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + particlImageSecondaryIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'particlImageSecondary', + value: '', + )); + }); + } + + QueryBuilder + particlImageSecondaryIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'particlImageSecondary', + value: '', + )); + }); + } + + QueryBuilder + personaEasyEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'personaEasy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaEasyGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'personaEasy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaEasyLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'personaEasy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaEasyBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'personaEasy', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaEasyStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'personaEasy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaEasyEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'personaEasy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaEasyContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'personaEasy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaEasyMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'personaEasy', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaEasyIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'personaEasy', + value: '', + )); + }); + } + + QueryBuilder + personaEasyIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'personaEasy', + value: '', + )); + }); + } + + QueryBuilder + personaIncognitoEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'personaIncognito', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaIncognitoGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'personaIncognito', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaIncognitoLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'personaIncognito', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaIncognitoBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'personaIncognito', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaIncognitoStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'personaIncognito', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaIncognitoEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'personaIncognito', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaIncognitoContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'personaIncognito', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaIncognitoMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'personaIncognito', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaIncognitoIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'personaIncognito', + value: '', + )); + }); + } + + QueryBuilder + personaIncognitoIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'personaIncognito', + value: '', + )); + }); + } + + QueryBuilder receiveEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'receive', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'receive', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder receiveLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'receive', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder receiveBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'receive', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'receive', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder receiveEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'receive', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder receiveContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'receive', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder receiveMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'receive', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'receive', + value: '', + )); + }); + } + + QueryBuilder + receiveIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'receive', + value: '', + )); + }); + } + + QueryBuilder + receiveCancelledEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'receiveCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveCancelledGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'receiveCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveCancelledLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'receiveCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveCancelledBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'receiveCancelled', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveCancelledStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'receiveCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveCancelledEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'receiveCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveCancelledContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'receiveCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveCancelledMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'receiveCancelled', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveCancelledIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'receiveCancelled', + value: '', + )); + }); + } + + QueryBuilder + receiveCancelledIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'receiveCancelled', + value: '', + )); + }); + } + + QueryBuilder + receivePendingEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'receivePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receivePendingGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'receivePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receivePendingLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'receivePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receivePendingBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'receivePending', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receivePendingStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'receivePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receivePendingEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'receivePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receivePendingContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'receivePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receivePendingMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'receivePending', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receivePendingIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'receivePending', + value: '', + )); + }); + } + + QueryBuilder + receivePendingIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'receivePending', + value: '', + )); + }); + } + + QueryBuilder sendEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'send', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder sendGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'send', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder sendLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'send', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder sendBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'send', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder sendStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'send', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder sendEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'send', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder sendContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'send', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder sendMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'send', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder sendIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'send', + value: '', + )); + }); + } + + QueryBuilder + sendIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'send', + value: '', + )); + }); + } + + QueryBuilder + sendCancelledEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'sendCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendCancelledGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'sendCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendCancelledLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'sendCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendCancelledBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'sendCancelled', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendCancelledStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'sendCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendCancelledEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'sendCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendCancelledContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'sendCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendCancelledMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'sendCancelled', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendCancelledIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'sendCancelled', + value: '', + )); + }); + } + + QueryBuilder + sendCancelledIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'sendCancelled', + value: '', + )); + }); + } + + QueryBuilder + sendPendingEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'sendPending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendPendingGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'sendPending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendPendingLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'sendPending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendPendingBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'sendPending', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendPendingStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'sendPending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendPendingEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'sendPending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendPendingContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'sendPending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendPendingMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'sendPending', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendPendingIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'sendPending', + value: '', + )); + }); + } + + QueryBuilder + sendPendingIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'sendPending', + value: '', + )); + }); + } + + QueryBuilder stackEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'stack', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'stack', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder stackLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'stack', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder stackBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'stack', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder stackStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'stack', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder stackEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'stack', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder stackContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'stack', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder stackMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'stack', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder stackIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'stack', + value: '', + )); + }); + } + + QueryBuilder + stackIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'stack', + value: '', + )); + }); + } + + QueryBuilder + stackIconEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'stackIcon', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackIconGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'stackIcon', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackIconLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'stackIcon', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackIconBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'stackIcon', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackIconStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'stackIcon', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackIconEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'stackIcon', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackIconContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'stackIcon', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackIconMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'stackIcon', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackIconIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'stackIcon', + value: '', + )); + }); + } + + QueryBuilder + stackIconIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'stackIcon', + value: '', + )); + }); + } + + QueryBuilder + themePreviewEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'themePreview', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themePreviewGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'themePreview', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themePreviewLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'themePreview', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themePreviewBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'themePreview', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themePreviewStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'themePreview', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themePreviewEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'themePreview', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themePreviewContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'themePreview', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themePreviewMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'themePreview', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themePreviewIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'themePreview', + value: '', + )); + }); + } + + QueryBuilder + themePreviewIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'themePreview', + value: '', + )); + }); + } + + QueryBuilder + themeSelectorEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'themeSelector', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themeSelectorGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'themeSelector', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themeSelectorLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'themeSelector', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themeSelectorBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'themeSelector', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themeSelectorStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'themeSelector', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themeSelectorEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'themeSelector', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themeSelectorContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'themeSelector', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themeSelectorMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'themeSelector', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themeSelectorIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'themeSelector', + value: '', + )); + }); + } + + QueryBuilder + themeSelectorIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'themeSelector', + value: '', + )); + }); + } + + QueryBuilder + txExchangeEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'txExchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'txExchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'txExchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'txExchange', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'txExchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'txExchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'txExchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'txExchange', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'txExchange', + value: '', + )); + }); + } + + QueryBuilder + txExchangeIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'txExchange', + value: '', + )); + }); + } + + QueryBuilder + txExchangeFailedEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'txExchangeFailed', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeFailedGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'txExchangeFailed', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeFailedLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'txExchangeFailed', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeFailedBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'txExchangeFailed', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeFailedStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'txExchangeFailed', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeFailedEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'txExchangeFailed', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeFailedContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'txExchangeFailed', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeFailedMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'txExchangeFailed', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeFailedIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'txExchangeFailed', + value: '', + )); + }); + } + + QueryBuilder + txExchangeFailedIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'txExchangeFailed', + value: '', + )); + }); + } + + QueryBuilder + txExchangePendingEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'txExchangePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangePendingGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'txExchangePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangePendingLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'txExchangePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangePendingBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'txExchangePending', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangePendingStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'txExchangePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangePendingEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'txExchangePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangePendingContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'txExchangePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangePendingMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'txExchangePending', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangePendingIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'txExchangePending', + value: '', + )); + }); + } + + QueryBuilder + txExchangePendingIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'txExchangePending', + value: '', + )); + }); + } + + QueryBuilder wowneroEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'wownero', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + wowneroGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'wownero', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder wowneroLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'wownero', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder wowneroBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'wownero', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + wowneroStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'wownero', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder wowneroEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'wownero', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder wowneroContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'wownero', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder wowneroMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'wownero', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + wowneroIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'wownero', + value: '', + )); + }); + } + + QueryBuilder + wowneroIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'wownero', + value: '', + )); + }); + } + + QueryBuilder + wowneroImageEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'wowneroImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + wowneroImageGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'wowneroImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + wowneroImageLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'wowneroImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + wowneroImageBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'wowneroImage', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + wowneroImageStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'wowneroImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + wowneroImageEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'wowneroImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + wowneroImageContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'wowneroImage', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + wowneroImageMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'wowneroImage', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + wowneroImageIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'wowneroImage', + value: '', + )); + }); + } + + QueryBuilder + wowneroImageIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'wowneroImage', + value: '', + )); + }); + } + + QueryBuilder + wowneroImageSecondaryEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'wowneroImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + wowneroImageSecondaryGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'wowneroImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + wowneroImageSecondaryLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'wowneroImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + wowneroImageSecondaryBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'wowneroImageSecondary', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + wowneroImageSecondaryStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'wowneroImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + wowneroImageSecondaryEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'wowneroImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + wowneroImageSecondaryContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'wowneroImageSecondary', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + wowneroImageSecondaryMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'wowneroImageSecondary', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + wowneroImageSecondaryIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'wowneroImageSecondary', + value: '', + )); + }); + } + + QueryBuilder + wowneroImageSecondaryIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'wowneroImageSecondary', + value: '', + )); + }); + } +} + +extension ThemeAssetsQueryObject + on QueryBuilder {} + +// coverage:ignore-file +// ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters + +const ThemeAssetsV2Schema = Schema( + name: r'ThemeAssetsV2', + id: -373522695224267013, + properties: { + r'background': PropertySchema( + id: 0, + name: r'background', + type: IsarType.string, + ), + r'bellNew': PropertySchema( + id: 1, + name: r'bellNew', + type: IsarType.string, + ), + r'buy': PropertySchema( + id: 2, + name: r'buy', + type: IsarType.string, + ), + r'coinIconsString': PropertySchema( + id: 3, + name: r'coinIconsString', + type: IsarType.string, + ), + r'coinImagesString': PropertySchema( + id: 4, + name: r'coinImagesString', + type: IsarType.string, + ), + r'coinPlaceholder': PropertySchema( + id: 5, + name: r'coinPlaceholder', + type: IsarType.string, + ), + r'coinSecondaryImagesString': PropertySchema( + id: 6, + name: r'coinSecondaryImagesString', + type: IsarType.string, + ), + r'exchange': PropertySchema( + id: 7, + name: r'exchange', + type: IsarType.string, + ), + r'loadingGif': PropertySchema( + id: 8, + name: r'loadingGif', + type: IsarType.string, + ), + r'personaEasy': PropertySchema( + id: 9, + name: r'personaEasy', + type: IsarType.string, + ), + r'personaIncognito': PropertySchema( + id: 10, + name: r'personaIncognito', + type: IsarType.string, + ), + r'receive': PropertySchema( + id: 11, + name: r'receive', + type: IsarType.string, + ), + r'receiveCancelled': PropertySchema( + id: 12, + name: r'receiveCancelled', + type: IsarType.string, + ), + r'receivePending': PropertySchema( + id: 13, + name: r'receivePending', + type: IsarType.string, + ), + r'send': PropertySchema( + id: 14, + name: r'send', + type: IsarType.string, + ), + r'sendCancelled': PropertySchema( + id: 15, + name: r'sendCancelled', + type: IsarType.string, + ), + r'sendPending': PropertySchema( + id: 16, + name: r'sendPending', + type: IsarType.string, + ), + r'stack': PropertySchema( + id: 17, + name: r'stack', + type: IsarType.string, + ), + r'stackIcon': PropertySchema( + id: 18, + name: r'stackIcon', + type: IsarType.string, + ), + r'themePreview': PropertySchema( + id: 19, + name: r'themePreview', + type: IsarType.string, + ), + r'themeSelector': PropertySchema( + id: 20, + name: r'themeSelector', + type: IsarType.string, + ), + r'txExchange': PropertySchema( + id: 21, + name: r'txExchange', + type: IsarType.string, + ), + r'txExchangeFailed': PropertySchema( + id: 22, + name: r'txExchangeFailed', + type: IsarType.string, + ), + r'txExchangePending': PropertySchema( + id: 23, + name: r'txExchangePending', + type: IsarType.string, + ) + }, + estimateSize: _themeAssetsV2EstimateSize, + serialize: _themeAssetsV2Serialize, + deserialize: _themeAssetsV2Deserialize, + deserializeProp: _themeAssetsV2DeserializeProp, +); + +int _themeAssetsV2EstimateSize( + ThemeAssetsV2 object, + List offsets, + Map> allOffsets, +) { + var bytesCount = offsets.last; + { + final value = object.background; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + bytesCount += 3 + object.bellNew.length * 3; + bytesCount += 3 + object.buy.length * 3; + bytesCount += 3 + object.coinIconsString.length * 3; + bytesCount += 3 + object.coinImagesString.length * 3; + bytesCount += 3 + object.coinPlaceholder.length * 3; + bytesCount += 3 + object.coinSecondaryImagesString.length * 3; + bytesCount += 3 + object.exchange.length * 3; + { + final value = object.loadingGif; + if (value != null) { + bytesCount += 3 + value.length * 3; + } + } + bytesCount += 3 + object.personaEasy.length * 3; + bytesCount += 3 + object.personaIncognito.length * 3; + bytesCount += 3 + object.receive.length * 3; + bytesCount += 3 + object.receiveCancelled.length * 3; + bytesCount += 3 + object.receivePending.length * 3; + bytesCount += 3 + object.send.length * 3; + bytesCount += 3 + object.sendCancelled.length * 3; + bytesCount += 3 + object.sendPending.length * 3; + bytesCount += 3 + object.stack.length * 3; + bytesCount += 3 + object.stackIcon.length * 3; + bytesCount += 3 + object.themePreview.length * 3; + bytesCount += 3 + object.themeSelector.length * 3; + bytesCount += 3 + object.txExchange.length * 3; + bytesCount += 3 + object.txExchangeFailed.length * 3; + bytesCount += 3 + object.txExchangePending.length * 3; + return bytesCount; +} + +void _themeAssetsV2Serialize( + ThemeAssetsV2 object, + IsarWriter writer, + List offsets, + Map> allOffsets, +) { + writer.writeString(offsets[0], object.background); + writer.writeString(offsets[1], object.bellNew); + writer.writeString(offsets[2], object.buy); + writer.writeString(offsets[3], object.coinIconsString); + writer.writeString(offsets[4], object.coinImagesString); + writer.writeString(offsets[5], object.coinPlaceholder); + writer.writeString(offsets[6], object.coinSecondaryImagesString); + writer.writeString(offsets[7], object.exchange); + writer.writeString(offsets[8], object.loadingGif); + writer.writeString(offsets[9], object.personaEasy); + writer.writeString(offsets[10], object.personaIncognito); + writer.writeString(offsets[11], object.receive); + writer.writeString(offsets[12], object.receiveCancelled); + writer.writeString(offsets[13], object.receivePending); + writer.writeString(offsets[14], object.send); + writer.writeString(offsets[15], object.sendCancelled); + writer.writeString(offsets[16], object.sendPending); + writer.writeString(offsets[17], object.stack); + writer.writeString(offsets[18], object.stackIcon); + writer.writeString(offsets[19], object.themePreview); + writer.writeString(offsets[20], object.themeSelector); + writer.writeString(offsets[21], object.txExchange); + writer.writeString(offsets[22], object.txExchangeFailed); + writer.writeString(offsets[23], object.txExchangePending); +} + +ThemeAssetsV2 _themeAssetsV2Deserialize( + Id id, + IsarReader reader, + List offsets, + Map> allOffsets, +) { + final object = ThemeAssetsV2(); + object.background = reader.readStringOrNull(offsets[0]); + object.bellNew = reader.readString(offsets[1]); + object.buy = reader.readString(offsets[2]); + object.coinIconsString = reader.readString(offsets[3]); + object.coinImagesString = reader.readString(offsets[4]); + object.coinPlaceholder = reader.readString(offsets[5]); + object.coinSecondaryImagesString = reader.readString(offsets[6]); + object.exchange = reader.readString(offsets[7]); + object.loadingGif = reader.readStringOrNull(offsets[8]); + object.personaEasy = reader.readString(offsets[9]); + object.personaIncognito = reader.readString(offsets[10]); + object.receive = reader.readString(offsets[11]); + object.receiveCancelled = reader.readString(offsets[12]); + object.receivePending = reader.readString(offsets[13]); + object.send = reader.readString(offsets[14]); + object.sendCancelled = reader.readString(offsets[15]); + object.sendPending = reader.readString(offsets[16]); + object.stack = reader.readString(offsets[17]); + object.stackIcon = reader.readString(offsets[18]); + object.themePreview = reader.readString(offsets[19]); + object.themeSelector = reader.readString(offsets[20]); + object.txExchange = reader.readString(offsets[21]); + object.txExchangeFailed = reader.readString(offsets[22]); + object.txExchangePending = reader.readString(offsets[23]); + return object; +} + +P _themeAssetsV2DeserializeProp

( + IsarReader reader, + int propertyId, + int offset, + Map> allOffsets, +) { + switch (propertyId) { + case 0: + return (reader.readStringOrNull(offset)) as P; + case 1: + return (reader.readString(offset)) as P; + case 2: + return (reader.readString(offset)) as P; + case 3: + return (reader.readString(offset)) as P; + case 4: + return (reader.readString(offset)) as P; + case 5: + return (reader.readString(offset)) as P; + case 6: + return (reader.readString(offset)) as P; + case 7: + return (reader.readString(offset)) as P; + case 8: + return (reader.readStringOrNull(offset)) as P; + case 9: + return (reader.readString(offset)) as P; + case 10: + return (reader.readString(offset)) as P; + case 11: + return (reader.readString(offset)) as P; + case 12: + return (reader.readString(offset)) as P; + case 13: + return (reader.readString(offset)) as P; + case 14: + return (reader.readString(offset)) as P; + case 15: + return (reader.readString(offset)) as P; + case 16: + return (reader.readString(offset)) as P; + case 17: + return (reader.readString(offset)) as P; + case 18: + return (reader.readString(offset)) as P; + case 19: + return (reader.readString(offset)) as P; + case 20: + return (reader.readString(offset)) as P; + case 21: + return (reader.readString(offset)) as P; + case 22: + return (reader.readString(offset)) as P; + case 23: + return (reader.readString(offset)) as P; + default: + throw IsarError('Unknown property with id $propertyId'); + } +} + +extension ThemeAssetsV2QueryFilter + on QueryBuilder { + QueryBuilder + backgroundIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'background', + )); + }); + } + + QueryBuilder + backgroundIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'background', + )); + }); + } + + QueryBuilder + backgroundEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'background', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + backgroundGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'background', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + backgroundLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'background', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + backgroundBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'background', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + backgroundStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'background', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + backgroundEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'background', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + backgroundContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'background', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + backgroundMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'background', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + backgroundIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'background', + value: '', + )); + }); + } + + QueryBuilder + backgroundIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'background', + value: '', + )); + }); + } + + QueryBuilder + bellNewEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'bellNew', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bellNewGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'bellNew', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bellNewLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'bellNew', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bellNewBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'bellNew', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bellNewStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'bellNew', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bellNewEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'bellNew', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bellNewContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'bellNew', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bellNewMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'bellNew', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + bellNewIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'bellNew', + value: '', + )); + }); + } + + QueryBuilder + bellNewIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'bellNew', + value: '', + )); + }); + } + + QueryBuilder buyEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'buy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + buyGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'buy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder buyLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'buy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder buyBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'buy', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + buyStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'buy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder buyEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'buy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder buyContains( + String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'buy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder buyMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'buy', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + buyIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'buy', + value: '', + )); + }); + } + + QueryBuilder + buyIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'buy', + value: '', + )); + }); + } + + QueryBuilder + coinIconsStringEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'coinIconsString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinIconsStringGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'coinIconsString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinIconsStringLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'coinIconsString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinIconsStringBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'coinIconsString', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinIconsStringStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'coinIconsString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinIconsStringEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'coinIconsString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinIconsStringContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'coinIconsString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinIconsStringMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'coinIconsString', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinIconsStringIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'coinIconsString', + value: '', + )); + }); + } + + QueryBuilder + coinIconsStringIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'coinIconsString', + value: '', + )); + }); + } + + QueryBuilder + coinImagesStringEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'coinImagesString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinImagesStringGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'coinImagesString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinImagesStringLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'coinImagesString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinImagesStringBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'coinImagesString', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinImagesStringStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'coinImagesString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinImagesStringEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'coinImagesString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinImagesStringContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'coinImagesString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinImagesStringMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'coinImagesString', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinImagesStringIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'coinImagesString', + value: '', + )); + }); + } + + QueryBuilder + coinImagesStringIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'coinImagesString', + value: '', + )); + }); + } + + QueryBuilder + coinPlaceholderEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'coinPlaceholder', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinPlaceholderGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'coinPlaceholder', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinPlaceholderLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'coinPlaceholder', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinPlaceholderBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'coinPlaceholder', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinPlaceholderStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'coinPlaceholder', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinPlaceholderEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'coinPlaceholder', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinPlaceholderContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'coinPlaceholder', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinPlaceholderMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'coinPlaceholder', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinPlaceholderIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'coinPlaceholder', + value: '', + )); + }); + } + + QueryBuilder + coinPlaceholderIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'coinPlaceholder', + value: '', + )); + }); + } + + QueryBuilder + coinSecondaryImagesStringEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'coinSecondaryImagesString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinSecondaryImagesStringGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'coinSecondaryImagesString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinSecondaryImagesStringLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'coinSecondaryImagesString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinSecondaryImagesStringBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'coinSecondaryImagesString', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinSecondaryImagesStringStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'coinSecondaryImagesString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinSecondaryImagesStringEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'coinSecondaryImagesString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinSecondaryImagesStringContains(String value, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'coinSecondaryImagesString', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinSecondaryImagesStringMatches(String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'coinSecondaryImagesString', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + coinSecondaryImagesStringIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'coinSecondaryImagesString', + value: '', + )); + }); + } + + QueryBuilder + coinSecondaryImagesStringIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'coinSecondaryImagesString', + value: '', + )); + }); + } + + QueryBuilder + exchangeEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'exchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + exchangeGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'exchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + exchangeLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'exchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + exchangeBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'exchange', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + exchangeStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'exchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + exchangeEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'exchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + exchangeContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'exchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + exchangeMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'exchange', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + exchangeIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'exchange', + value: '', + )); + }); + } + + QueryBuilder + exchangeIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'exchange', + value: '', + )); + }); + } + + QueryBuilder + loadingGifIsNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNull( + property: r'loadingGif', + )); + }); + } + + QueryBuilder + loadingGifIsNotNull() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(const FilterCondition.isNotNull( + property: r'loadingGif', + )); + }); + } + + QueryBuilder + loadingGifEqualTo( + String? value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'loadingGif', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + loadingGifGreaterThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'loadingGif', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + loadingGifLessThan( + String? value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'loadingGif', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + loadingGifBetween( + String? lower, + String? upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'loadingGif', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + loadingGifStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'loadingGif', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + loadingGifEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'loadingGif', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + loadingGifContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'loadingGif', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + loadingGifMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'loadingGif', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + loadingGifIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'loadingGif', + value: '', + )); + }); + } + + QueryBuilder + loadingGifIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'loadingGif', + value: '', + )); + }); + } + + QueryBuilder + personaEasyEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'personaEasy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaEasyGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'personaEasy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaEasyLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'personaEasy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaEasyBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'personaEasy', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaEasyStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'personaEasy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaEasyEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'personaEasy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaEasyContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'personaEasy', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaEasyMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'personaEasy', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaEasyIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'personaEasy', + value: '', + )); + }); + } + + QueryBuilder + personaEasyIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'personaEasy', + value: '', + )); + }); + } + + QueryBuilder + personaIncognitoEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'personaIncognito', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaIncognitoGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'personaIncognito', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaIncognitoLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'personaIncognito', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaIncognitoBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'personaIncognito', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaIncognitoStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'personaIncognito', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaIncognitoEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'personaIncognito', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaIncognitoContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'personaIncognito', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaIncognitoMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'personaIncognito', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + personaIncognitoIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'personaIncognito', + value: '', + )); + }); + } + + QueryBuilder + personaIncognitoIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'personaIncognito', + value: '', + )); + }); + } + + QueryBuilder + receiveEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'receive', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'receive', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'receive', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'receive', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'receive', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'receive', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'receive', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'receive', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'receive', + value: '', + )); + }); + } + + QueryBuilder + receiveIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'receive', + value: '', + )); + }); + } + + QueryBuilder + receiveCancelledEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'receiveCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveCancelledGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'receiveCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveCancelledLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'receiveCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveCancelledBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'receiveCancelled', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveCancelledStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'receiveCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveCancelledEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'receiveCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveCancelledContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'receiveCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveCancelledMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'receiveCancelled', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receiveCancelledIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'receiveCancelled', + value: '', + )); + }); + } + + QueryBuilder + receiveCancelledIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'receiveCancelled', + value: '', + )); + }); + } + + QueryBuilder + receivePendingEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'receivePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receivePendingGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'receivePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receivePendingLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'receivePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receivePendingBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'receivePending', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receivePendingStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'receivePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receivePendingEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'receivePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receivePendingContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'receivePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receivePendingMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'receivePending', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + receivePendingIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'receivePending', + value: '', + )); + }); + } + + QueryBuilder + receivePendingIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'receivePending', + value: '', + )); + }); + } + + QueryBuilder sendEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'send', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'send', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'send', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder sendBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'send', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'send', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'send', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'send', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder sendMatches( + String pattern, + {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'send', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'send', + value: '', + )); + }); + } + + QueryBuilder + sendIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'send', + value: '', + )); + }); + } + + QueryBuilder + sendCancelledEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'sendCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendCancelledGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'sendCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendCancelledLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'sendCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendCancelledBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'sendCancelled', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendCancelledStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'sendCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendCancelledEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'sendCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendCancelledContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'sendCancelled', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendCancelledMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'sendCancelled', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendCancelledIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'sendCancelled', + value: '', + )); + }); + } + + QueryBuilder + sendCancelledIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'sendCancelled', + value: '', + )); + }); + } + + QueryBuilder + sendPendingEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'sendPending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendPendingGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'sendPending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendPendingLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'sendPending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendPendingBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'sendPending', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendPendingStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'sendPending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendPendingEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'sendPending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendPendingContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'sendPending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendPendingMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'sendPending', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + sendPendingIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'sendPending', + value: '', + )); + }); + } + + QueryBuilder + sendPendingIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'sendPending', + value: '', + )); + }); + } + + QueryBuilder + stackEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'stack', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'stack', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'stack', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'stack', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'stack', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'stack', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'stack', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'stack', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'stack', + value: '', + )); + }); + } + + QueryBuilder + stackIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'stack', + value: '', + )); + }); + } + + QueryBuilder + stackIconEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'stackIcon', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackIconGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'stackIcon', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackIconLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'stackIcon', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackIconBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'stackIcon', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackIconStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'stackIcon', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackIconEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'stackIcon', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackIconContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'stackIcon', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackIconMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'stackIcon', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + stackIconIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'stackIcon', + value: '', + )); + }); + } + + QueryBuilder + stackIconIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'stackIcon', + value: '', + )); + }); + } + + QueryBuilder + themePreviewEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'themePreview', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themePreviewGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'themePreview', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themePreviewLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'themePreview', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themePreviewBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'themePreview', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themePreviewStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'themePreview', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themePreviewEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'themePreview', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themePreviewContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'themePreview', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themePreviewMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'themePreview', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themePreviewIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'themePreview', + value: '', + )); + }); + } + + QueryBuilder + themePreviewIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'themePreview', + value: '', + )); + }); + } + + QueryBuilder + themeSelectorEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'themeSelector', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themeSelectorGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'themeSelector', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themeSelectorLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'themeSelector', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themeSelectorBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'themeSelector', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themeSelectorStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'themeSelector', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themeSelectorEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'themeSelector', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themeSelectorContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'themeSelector', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themeSelectorMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'themeSelector', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + themeSelectorIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'themeSelector', + value: '', + )); + }); + } + + QueryBuilder + themeSelectorIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'themeSelector', + value: '', + )); + }); + } + + QueryBuilder + txExchangeEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'txExchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'txExchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'txExchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'txExchange', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'txExchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'txExchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'txExchange', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'txExchange', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'txExchange', + value: '', + )); + }); + } + + QueryBuilder + txExchangeIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'txExchange', + value: '', + )); + }); + } + + QueryBuilder + txExchangeFailedEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'txExchangeFailed', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeFailedGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'txExchangeFailed', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeFailedLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'txExchangeFailed', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeFailedBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'txExchangeFailed', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeFailedStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'txExchangeFailed', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeFailedEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'txExchangeFailed', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeFailedContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'txExchangeFailed', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeFailedMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'txExchangeFailed', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangeFailedIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'txExchangeFailed', + value: '', + )); + }); + } + + QueryBuilder + txExchangeFailedIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'txExchangeFailed', + value: '', + )); + }); + } + + QueryBuilder + txExchangePendingEqualTo( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'txExchangePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangePendingGreaterThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + include: include, + property: r'txExchangePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangePendingLessThan( + String value, { + bool include = false, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.lessThan( + include: include, + property: r'txExchangePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangePendingBetween( + String lower, + String upper, { + bool includeLower = true, + bool includeUpper = true, + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.between( + property: r'txExchangePending', + lower: lower, + includeLower: includeLower, + upper: upper, + includeUpper: includeUpper, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangePendingStartsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.startsWith( + property: r'txExchangePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangePendingEndsWith( + String value, { + bool caseSensitive = true, + }) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.endsWith( + property: r'txExchangePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangePendingContains(String value, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.contains( + property: r'txExchangePending', + value: value, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangePendingMatches(String pattern, {bool caseSensitive = true}) { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.matches( + property: r'txExchangePending', + wildcard: pattern, + caseSensitive: caseSensitive, + )); + }); + } + + QueryBuilder + txExchangePendingIsEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.equalTo( + property: r'txExchangePending', + value: '', + )); + }); + } + + QueryBuilder + txExchangePendingIsNotEmpty() { + return QueryBuilder.apply(this, (query) { + return query.addFilterCondition(FilterCondition.greaterThan( + property: r'txExchangePending', + value: '', + )); + }); + } +} + +extension ThemeAssetsV2QueryObject + on QueryBuilder {} diff --git a/lib/models/node_model.dart b/lib/models/node_model.dart index af5f8cbc1..2aeaa893b 100644 --- a/lib/models/node_model.dart +++ b/lib/models/node_model.dart @@ -26,6 +26,8 @@ class NodeModel { final bool isFailover; // @HiveField(9) final bool isDown; + // @HiveField(10) + final bool? trusted; NodeModel({ required this.host, @@ -38,6 +40,7 @@ class NodeModel { required this.isFailover, required this.isDown, this.loginName, + this.trusted, }); NodeModel copyWith({ @@ -50,6 +53,7 @@ class NodeModel { String? coinName, bool? isFailover, bool? isDown, + bool? trusted, }) { return NodeModel( host: host ?? this.host, @@ -62,6 +66,7 @@ class NodeModel { coinName: coinName ?? this.coinName, isFailover: isFailover ?? this.isFailover, isDown: isDown ?? this.isDown, + trusted: trusted ?? this.trusted, ); } @@ -82,6 +87,7 @@ class NodeModel { map['coinName'] = coinName; map['isFailover'] = isFailover; map['isDown'] = isDown; + map['trusted'] = trusted; return map; } diff --git a/lib/models/paymint/transactions_model.dart b/lib/models/paymint/transactions_model.dart index 06a21e16a..cee48b8eb 100644 --- a/lib/models/paymint/transactions_model.dart +++ b/lib/models/paymint/transactions_model.dart @@ -96,7 +96,8 @@ class TransactionChunk { .toList(); return TransactionChunk( - timestamp: json['timestamp'] as int, transactions: txList); + timestamp: int.parse(json['timestamp'].toString()), + transactions: txList); } @override @@ -192,13 +193,13 @@ class Transaction { return Transaction( txid: json['txid'] as String, confirmedStatus: json['confirmed_status'] as bool, - timestamp: json['timestamp'] as int, + timestamp: int.parse(json['timestamp'].toString()), txType: json['txType'] as String, amount: json['amount'] as int, aliens: json['aliens'] as List, worthNow: json['worthNow'] as String? ?? "", worthAtBlockTimestamp: json['worthAtBlockTimestamp'] as String? ?? "", - fees: json['fees'] as int, + fees: int.parse(json['fees'].toString()), inputSize: json['inputSize'] as int, outputSize: json['outputSize'] as int, inputs: inputList, @@ -362,12 +363,16 @@ class Input { class Output { // @HiveField(0) final String? scriptpubkey; + // @HiveField(1) final String? scriptpubkeyAsm; + // @HiveField(2) final String? scriptpubkeyType; + // @HiveField(3) final String scriptpubkeyAddress; + // @HiveField(4) final int value; @@ -380,9 +385,7 @@ class Output { factory Output.fromJson(Map json) { // TODO determine if any of this code is needed. - // Particl has different tx types that need to be detected and handled here - if (json.containsKey('scriptPubKey') as bool) { - // output is transparent + try { final address = json["scriptPubKey"]["addresses"] == null ? json['scriptPubKey']['type'] as String : json["scriptPubKey"]["addresses"][0] as String; @@ -391,23 +394,14 @@ class Output { scriptpubkeyAsm: json['scriptPubKey']['asm'] as String?, scriptpubkeyType: json['scriptPubKey']['type'] as String?, scriptpubkeyAddress: address, - value: (Decimal.parse(json["value"].toString()) * + value: (Decimal.parse( + (json["value"] ?? 0).toString()) * Decimal.fromInt(Constants.satsPerCoin(Coin .firo))) // dirty hack but we need 8 decimal places here to keep consistent data structure .toBigInt() .toInt(), ); - } /* else if (json.containsKey('ct_fee') as bool) { - // or type: data - // output is blinded (CT) - } else if (json.containsKey('rangeproof') as bool) { - // or valueCommitment or type: anon - // output is private (RingCT) - } */ - else { - // TODO detect staking - // TODO handle CT, RingCT, and staking accordingly - // print("transaction not supported: ${json}"); + } catch (s, e) { return Output( // Return output object with null values; allows wallet history to be built scriptpubkey: "", diff --git a/lib/models/paynym/created_paynym.dart b/lib/models/paynym/created_paynym.dart new file mode 100644 index 000000000..8716d7e10 --- /dev/null +++ b/lib/models/paynym/created_paynym.dart @@ -0,0 +1,35 @@ +class CreatedPaynym { + final bool claimed; + final String? nymAvatar; + final String? nymId; + final String? nymName; + final String? token; + + CreatedPaynym( + this.claimed, + this.nymAvatar, + this.nymId, + this.nymName, + this.token, + ); + + CreatedPaynym.fromMap(Map map) + : claimed = map["claimed"] as bool, + nymAvatar = map["nymAvatar"] as String?, + nymId = map["nymID"] as String?, + nymName = map["nymName"] as String?, + token = map["token"] as String?; + + Map toMap() => { + "claimed": claimed, + "nymAvatar": nymAvatar, + "nymId": nymId, + "nymName": nymName, + "token": token, + }; + + @override + String toString() { + return toMap().toString(); + } +} diff --git a/lib/models/paynym/paynym_account.dart b/lib/models/paynym/paynym_account.dart new file mode 100644 index 000000000..133edc25d --- /dev/null +++ b/lib/models/paynym/paynym_account.dart @@ -0,0 +1,76 @@ +import 'package:stackwallet/models/paynym/paynym_account_lite.dart'; +import 'package:stackwallet/models/paynym/paynym_code.dart'; + +class PaynymAccount { + final String nymID; + final String nymName; + final bool segwit; + + final List codes; + + /// list of nymId + final List followers; + + /// list of nymId + final List following; + + PaynymCode get nonSegwitPaymentCode => + codes.firstWhere((element) => !element.segwit); + + PaynymAccount( + this.nymID, + this.nymName, + this.segwit, + this.codes, + this.followers, + this.following, + ); + + PaynymAccount.fromMap(Map map) + : nymID = map["nymID"] as String, + nymName = map["nymName"] as String, + segwit = map["segwit"] as bool, + codes = (map["codes"] as List) + .map((e) => PaynymCode.fromMap(Map.from(e as Map))) + .toList(), + followers = (map["followers"] as List) + .map((e) => + PaynymAccountLite.fromMap(Map.from(e as Map))) + .toList(), + following = (map["following"] as List) + .map((e) => + PaynymAccountLite.fromMap(Map.from(e as Map))) + .toList(); + + PaynymAccount copyWith({ + String? nymID, + String? nymName, + bool? segwit, + List? codes, + List? followers, + List? following, + }) { + return PaynymAccount( + nymID ?? this.nymID, + nymName ?? this.nymName, + segwit ?? this.segwit, + codes ?? this.codes, + followers ?? this.followers, + following ?? this.following, + ); + } + + Map toMap() => { + "nymID": nymID, + "nymName": nymName, + "segwit": segwit, + "codes": codes.map((e) => e.toMap()), + "followers": followers.map((e) => e.toMap()), + "following": followers.map((e) => e.toMap()), + }; + + @override + String toString() { + return toMap().toString(); + } +} diff --git a/lib/models/paynym/paynym_account_lite.dart b/lib/models/paynym/paynym_account_lite.dart new file mode 100644 index 000000000..e1510febc --- /dev/null +++ b/lib/models/paynym/paynym_account_lite.dart @@ -0,0 +1,31 @@ +class PaynymAccountLite { + final String nymId; + final String nymName; + final String code; + final bool segwit; + + PaynymAccountLite( + this.nymId, + this.nymName, + this.code, + this.segwit, + ); + + PaynymAccountLite.fromMap(Map map) + : nymId = map["nymId"] as String, + nymName = map["nymName"] as String, + code = map["code"] as String, + segwit = map["segwit"] as bool; + + Map toMap() => { + "nymId": nymId, + "nymName": nymName, + "code": code, + "segwit": segwit, + }; + + @override + String toString() { + return toMap().toString(); + } +} diff --git a/lib/models/paynym/paynym_claim.dart b/lib/models/paynym/paynym_claim.dart new file mode 100644 index 000000000..275063e4a --- /dev/null +++ b/lib/models/paynym/paynym_claim.dart @@ -0,0 +1,20 @@ +class PaynymClaim { + final String claimed; + final String token; + + PaynymClaim(this.claimed, this.token); + + PaynymClaim.fromMap(Map map) + : claimed = map["claimed"] as String, + token = map["token"] as String; + + Map toMap() => { + "claimed": claimed, + "token": token, + }; + + @override + String toString() { + return toMap().toString(); + } +} diff --git a/lib/models/paynym/paynym_code.dart b/lib/models/paynym/paynym_code.dart new file mode 100644 index 000000000..d01366d20 --- /dev/null +++ b/lib/models/paynym/paynym_code.dart @@ -0,0 +1,27 @@ +class PaynymCode { + final bool claimed; + final bool segwit; + final String code; + + PaynymCode( + this.claimed, + this.segwit, + this.code, + ); + + PaynymCode.fromMap(Map map) + : claimed = map["claimed"] as bool, + segwit = map["segwit"] as bool, + code = map["code"] as String; + + Map toMap() => { + "claimed": claimed, + "segwit": segwit, + "code": code, + }; + + @override + String toString() { + return toMap().toString(); + } +} diff --git a/lib/models/paynym/paynym_follow.dart b/lib/models/paynym/paynym_follow.dart new file mode 100644 index 000000000..56bcb8fa9 --- /dev/null +++ b/lib/models/paynym/paynym_follow.dart @@ -0,0 +1,23 @@ +class PaynymFollow { + final String follower; + final String following; + final String token; + + PaynymFollow(this.follower, this.following, this.token); + + PaynymFollow.fromMap(Map map) + : follower = map["follower"] as String, + following = map["following"] as String, + token = map["token"] as String; + + Map toMap() => { + "follower": follower, + "following": following, + "token": token, + }; + + @override + String toString() { + return toMap().toString(); + } +} \ No newline at end of file diff --git a/lib/models/paynym/paynym_response.dart b/lib/models/paynym/paynym_response.dart new file mode 100644 index 000000000..3617d12cc --- /dev/null +++ b/lib/models/paynym/paynym_response.dart @@ -0,0 +1,12 @@ +class PaynymResponse { + final T? value; + final int statusCode; + final String message; + + PaynymResponse(this.value, this.statusCode, this.message); + + @override + String toString() { + return "PaynymResponse: value=$value, statusCode=$statusCode, message=$message"; + } +} diff --git a/lib/models/paynym/paynym_unfollow.dart b/lib/models/paynym/paynym_unfollow.dart new file mode 100644 index 000000000..4aa1ca975 --- /dev/null +++ b/lib/models/paynym/paynym_unfollow.dart @@ -0,0 +1,23 @@ +class PaynymUnfollow { + final String follower; + final String unfollowing; + final String token; + + PaynymUnfollow(this.follower, this.unfollowing, this.token); + + PaynymUnfollow.fromMap(Map map) + : follower = map["follower"] as String, + unfollowing = map["unfollowing"] as String, + token = map["token"] as String; + + Map toMap() => { + "follower": follower, + "unfollowing": unfollowing, + "token": token, + }; + + @override + String toString() { + return toMap().toString(); + } +} diff --git a/lib/models/signing_data.dart b/lib/models/signing_data.dart new file mode 100644 index 000000000..bb933976c --- /dev/null +++ b/lib/models/signing_data.dart @@ -0,0 +1,21 @@ +import 'dart:typed_data'; + +import 'package:bitcoindart/bitcoindart.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart'; + +class SigningData { + SigningData({ + required this.derivePathType, + required this.utxo, + this.output, + this.keyPair, + this.redeemScript, + }); + + final DerivePathType derivePathType; + final UTXO utxo; + Uint8List? output; + ECPair? keyPair; + Uint8List? redeemScript; +} diff --git a/lib/models/stack_restoring_ui_state.dart b/lib/models/stack_restoring_ui_state.dart index 21760b95a..312b0989f 100644 --- a/lib/models/stack_restoring_ui_state.dart +++ b/lib/models/stack_restoring_ui_state.dart @@ -125,6 +125,7 @@ class StackRestoringUIState extends ChangeNotifier { Manager? manager, String? address, String? mnemonic, + String? mnemonicPassphrase, int? height, }) { _walletStates[walletId]!.restoringState = restoringStatus; @@ -134,6 +135,8 @@ class StackRestoringUIState extends ChangeNotifier { address ?? _walletStates[walletId]!.address; _walletStates[walletId]!.mnemonic = mnemonic ?? _walletStates[walletId]!.mnemonic; + _walletStates[walletId]!.mnemonicPassphrase = + mnemonicPassphrase ?? _walletStates[walletId]!.mnemonicPassphrase; _walletStates[walletId]!.height = height ?? _walletStates[walletId]!.height; notifyListeners(); } diff --git a/lib/models/transaction_filter.dart b/lib/models/transaction_filter.dart index 7ef5f0bff..7ea18aac0 100644 --- a/lib/models/transaction_filter.dart +++ b/lib/models/transaction_filter.dart @@ -1,10 +1,12 @@ +import 'package:stackwallet/utilities/amount/amount.dart'; + class TransactionFilter { final bool sent; final bool received; final bool trade; final DateTime? from; final DateTime? to; - final int? amount; + final Amount? amount; final String keyword; TransactionFilter({ @@ -23,7 +25,7 @@ class TransactionFilter { bool? trade, DateTime? from, DateTime? to, - int? amount, + Amount? amount, String? keyword, }) { return TransactionFilter( diff --git a/lib/models/tx_info.dart b/lib/models/tx_info.dart new file mode 100644 index 000000000..798cb5adc --- /dev/null +++ b/lib/models/tx_info.dart @@ -0,0 +1,45 @@ +import 'package:stackwallet/models/isar/models/blockchain_data/utxo.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; + +// TODO use something like this instead of Map transactionObject + +class TxInfo { + final String hex; + final List recipients; + final Amount fee; + final int vSize; + final List? usedUTXOs; + + TxInfo({ + required this.hex, + required this.recipients, + required this.fee, + required this.vSize, + required this.usedUTXOs, + }); + + TxInfo copyWith({ + String? hex, + List? recipients, + Amount? fee, + int? vSize, + List? usedUTXOs, + }) => + TxInfo( + hex: hex ?? this.hex, + fee: fee ?? this.fee, + vSize: vSize ?? this.vSize, + usedUTXOs: usedUTXOs ?? this.usedUTXOs, + recipients: recipients ?? this.recipients, + ); +} + +class TxRecipient { + final String address; + final Amount amount; + + TxRecipient({ + required this.address, + required this.amount, + }); +} diff --git a/lib/models/type_adaptors/epicbox_config_model.g.dart b/lib/models/type_adaptors/epicbox_config_model.g.dart new file mode 100644 index 000000000..70d066370 --- /dev/null +++ b/lib/models/type_adaptors/epicbox_config_model.g.dart @@ -0,0 +1,56 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of '../epicbox_config_model.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class EpicBoxConfigModelAdapter extends TypeAdapter { + @override + final int typeId = 72; + + @override + EpicBoxConfigModel read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return EpicBoxConfigModel( + host: fields[1] as String, + port: fields[2] as int, + protocolInsecure: fields[3] as bool, + addressIndex: fields[4] as int, + // name: fields[5] as String, + // id: fields[6] as String, + ); + } + + @override + void write(BinaryWriter writer, EpicBoxConfigModel obj) { + writer + ..writeByte(4) + ..writeByte(0) + ..write(obj.host) + ..writeByte(1) + ..write(obj.port) + ..writeByte(2) + ..write(obj.protocolInsecure) + ..writeByte(3) + ..write(obj.addressIndex); + // ..writeByte(4) + // ..write(obj.id) + // ..writeByte(5) + // ..write(obj.name) + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is EpicBoxConfigModelAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/models/type_adaptors/epicbox_server_model.g.dart b/lib/models/type_adaptors/epicbox_server_model.g.dart new file mode 100644 index 000000000..cc741bf83 --- /dev/null +++ b/lib/models/type_adaptors/epicbox_server_model.g.dart @@ -0,0 +1,62 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of '../epicbox_server_model.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class EpicBoxServerModelAdapter extends TypeAdapter { + @override + final int typeId = 71; + + @override + EpicBoxServerModel read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return EpicBoxServerModel( + host: fields[1] as String, + port: fields[2] as int, + name: fields[3] as String, + id: fields[0] as String, + useSSL: fields[4] as bool, + enabled: fields[5] as bool, + isFailover: fields[6] as bool, + isDown: fields[7] as bool, + ); + } + + @override + void write(BinaryWriter writer, EpicBoxServerModel obj) { + writer + ..writeByte(8) + ..writeByte(0) + ..write(obj.id) + ..writeByte(1) + ..write(obj.host) + ..writeByte(2) + ..write(obj.port) + ..writeByte(3) + ..write(obj.name) + ..writeByte(4) + ..write(obj.useSSL) + ..writeByte(5) + ..write(obj.enabled) + ..writeByte(6) + ..write(obj.isFailover) + ..writeByte(7) + ..write(obj.isDown); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is EpicBoxServerModelAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/models/type_adaptors/node_model.g.dart b/lib/models/type_adaptors/node_model.g.dart index 580c36a75..597468c56 100644 --- a/lib/models/type_adaptors/node_model.g.dart +++ b/lib/models/type_adaptors/node_model.g.dart @@ -26,14 +26,15 @@ class NodeModelAdapter extends TypeAdapter { loginName: fields[5] as String?, coinName: fields[7] as String, isFailover: fields[8] as bool, - isDown: fields[8] as bool, + isDown: fields[9] as bool, + trusted: fields[10] as bool?, ); } @override void write(BinaryWriter writer, NodeModel obj) { writer - ..writeByte(10) + ..writeByte(11) ..writeByte(0) ..write(obj.id) ..writeByte(1) @@ -53,7 +54,9 @@ class NodeModelAdapter extends TypeAdapter { ..writeByte(8) ..write(obj.isFailover) ..writeByte(9) - ..write(obj.isDown); + ..write(obj.isDown) + ..writeByte(10) + ..write(obj.trusted); } @override diff --git a/lib/models/wallet_restore_state.dart b/lib/models/wallet_restore_state.dart index c8b96e1c3..a92753617 100644 --- a/lib/models/wallet_restore_state.dart +++ b/lib/models/wallet_restore_state.dart @@ -11,6 +11,7 @@ class WalletRestoreState extends ChangeNotifier { Manager? manager; String? address; String? mnemonic; + String? mnemonicPassphrase; int? height; StackRestoringStatus get restoringState => _restoringStatus; @@ -27,6 +28,7 @@ class WalletRestoreState extends ChangeNotifier { this.manager, this.address, this.mnemonic, + this.mnemonicPassphrase, this.height, }) { _restoringStatus = restoringStatus; @@ -45,6 +47,7 @@ class WalletRestoreState extends ChangeNotifier { manager: manager, address: this.address, mnemonic: mnemonic, + mnemonicPassphrase: mnemonicPassphrase, height: this.height, ); } diff --git a/lib/notifications/notification_card.dart b/lib/notifications/notification_card.dart index 2a181499c..08fbd2a3c 100644 --- a/lib/notifications/notification_card.dart +++ b/lib/notifications/notification_card.dart @@ -1,15 +1,22 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; import 'package:stackwallet/models/notification_model.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; -class NotificationCard extends StatelessWidget { +class NotificationCard extends ConsumerWidget { const NotificationCard({ Key? key, required this.notification, @@ -25,8 +32,17 @@ class NotificationCard extends StatelessWidget { static const double mobileIconSize = 24; static const double desktopIconSize = 30; + String coinIconPath(IThemeAssets assets, WidgetRef ref) { + try { + final coin = coinFromPrettyName(notification.coinName); + return ref.read(coinIconProvider(coin)); + } catch (_) { + return notification.iconAssetName; + } + } + @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final isDesktop = Util.isDesktop; return Stack( @@ -41,8 +57,14 @@ class NotificationCard extends StatelessWidget { child: Row( children: [ notification.changeNowId == null - ? SvgPicture.asset( - notification.iconAssetName, + ? SvgPicture.file( + File( + coinIconPath( + ref.watch( + themeAssetsProvider, + ), + ref), + ), width: isDesktop ? desktopIconSize : mobileIconSize, height: isDesktop ? desktopIconSize : mobileIconSize, ) @@ -53,8 +75,14 @@ class NotificationCard extends StatelessWidget { color: Colors.transparent, borderRadius: BorderRadius.circular(24), ), - child: SvgPicture.asset( - notification.iconAssetName, + child: SvgPicture.file( + File( + coinIconPath( + ref.watch( + themeAssetsProvider, + ), + ref), + ), color: Theme.of(context) .extension()! .accentColorDark, diff --git a/lib/notifications/show_flush_bar.dart b/lib/notifications/show_flush_bar.dart index 47cea682a..8d97f8bfb 100644 --- a/lib/notifications/show_flush_bar.dart +++ b/lib/notifications/show_flush_bar.dart @@ -2,9 +2,9 @@ import 'package:another_flushbar/flushbar.dart'; import 'package:another_flushbar/flushbar_route.dart' as flushRoute; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; export 'package:stackwallet/utilities/enums/flush_bar_type.dart'; diff --git a/lib/pages/add_wallet_views/add_token_view/add_custom_token_view.dart b/lib/pages/add_wallet_views/add_token_view/add_custom_token_view.dart new file mode 100644 index 000000000..f28b1870e --- /dev/null +++ b/lib/pages/add_wallet_views/add_token_view/add_custom_token_view.dart @@ -0,0 +1,293 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; +import 'package:stackwallet/services/ethereum/ethereum_api.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/show_loading.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; + +class AddCustomTokenView extends ConsumerStatefulWidget { + const AddCustomTokenView({ + Key? key, + }) : super(key: key); + + static const routeName = "/addCustomToken"; + + @override + ConsumerState createState() => _AddCustomTokenViewState(); +} + +class _AddCustomTokenViewState extends ConsumerState { + final isDesktop = Util.isDesktop; + + final contractController = TextEditingController(); + final nameController = TextEditingController(); + final symbolController = TextEditingController(); + final decimalsController = TextEditingController(); + + bool enableSubFields = false; + bool addTokenButtonEnabled = false; + + EthContract? currentToken; + + @override + Widget build(BuildContext context) { + return ConditionalParent( + condition: !isDesktop, + builder: (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + body: Padding( + padding: const EdgeInsets.only( + top: 10, + left: 16, + right: 16, + bottom: 16, + ), + child: child, + ), + ), + ), + child: ConditionalParent( + condition: isDesktop, + builder: (child) => Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Add custom ETH token", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Flexible( + child: Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + top: 16, + ), + child: child, + ), + ), + ], + ), + child: Column( + children: [ + if (!isDesktop) + Text( + "Add custom ETH token", + style: STextStyles.pageTitleH1(context), + ), + if (!isDesktop) + const SizedBox( + height: 16, + ), + TextField( + autocorrect: !isDesktop, + enableSuggestions: !isDesktop, + controller: contractController, + style: STextStyles.field(context), + decoration: InputDecoration( + hintText: "Contract address", + hintStyle: STextStyles.fieldLabel(context), + ), + ), + SizedBox( + height: isDesktop ? 16 : 8, + ), + PrimaryButton( + label: "Search", + onPressed: () async { + final response = await showLoading( + whileFuture: EthereumAPI.getTokenContractInfoByAddress( + contractController.text), + context: context, + message: "Looking up contract", + ); + currentToken = response.value; + if (currentToken != null) { + nameController.text = currentToken!.name; + symbolController.text = currentToken!.symbol; + decimalsController.text = currentToken!.decimals.toString(); + } else { + nameController.text = ""; + symbolController.text = ""; + decimalsController.text = ""; + if (mounted) { + unawaited( + showDialog( + context: context, + builder: (context) => StackOkDialog( + title: "Failed to look up token", + message: response.exception?.message, + ), + ), + ); + } + } + setState(() { + addTokenButtonEnabled = currentToken != null; + }); + }, + ), + SizedBox( + height: isDesktop ? 16 : 8, + ), + TextField( + enabled: enableSubFields, + autocorrect: !isDesktop, + enableSuggestions: !isDesktop, + controller: nameController, + style: STextStyles.field(context), + decoration: InputDecoration( + hintText: "Token name", + hintStyle: STextStyles.fieldLabel(context), + ), + ), + SizedBox( + height: isDesktop ? 16 : 8, + ), + if (isDesktop) + Row( + children: [ + Expanded( + child: TextField( + enabled: enableSubFields, + autocorrect: !isDesktop, + enableSuggestions: !isDesktop, + controller: symbolController, + style: STextStyles.field(context), + decoration: InputDecoration( + hintText: "Ticker", + hintStyle: STextStyles.fieldLabel(context), + ), + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: TextField( + enabled: enableSubFields, + autocorrect: !isDesktop, + enableSuggestions: !isDesktop, + controller: decimalsController, + style: STextStyles.field(context), + inputFormatters: [ + TextInputFormatter.withFunction((oldValue, newValue) => + RegExp(r'^([0-9]*)$').hasMatch(newValue.text) + ? newValue + : oldValue), + ], + keyboardType: const TextInputType.numberWithOptions( + signed: false, + decimal: false, + ), + decoration: InputDecoration( + hintText: "Decimals", + hintStyle: STextStyles.fieldLabel(context), + ), + ), + ), + ], + ), + if (!isDesktop) + TextField( + enabled: enableSubFields, + autocorrect: !isDesktop, + enableSuggestions: !isDesktop, + controller: symbolController, + style: STextStyles.field(context), + decoration: InputDecoration( + hintText: "Ticker", + hintStyle: STextStyles.fieldLabel(context), + ), + ), + if (!isDesktop) + const SizedBox( + height: 8, + ), + if (!isDesktop) + TextField( + enabled: enableSubFields, + autocorrect: !isDesktop, + enableSuggestions: !isDesktop, + controller: decimalsController, + style: STextStyles.field(context), + inputFormatters: [ + TextInputFormatter.withFunction((oldValue, newValue) => + RegExp(r'^([0-9]*)$').hasMatch(newValue.text) + ? newValue + : oldValue), + ], + keyboardType: const TextInputType.numberWithOptions( + signed: false, + decimal: false, + ), + decoration: InputDecoration( + hintText: "Decimals", + hintStyle: STextStyles.fieldLabel(context), + ), + ), + const SizedBox( + height: 16, + ), + const Spacer(), + Row( + children: [ + if (isDesktop) + Expanded( + child: SecondaryButton( + label: "Cancel", + onPressed: Navigator.of(context).pop, + ), + ), + if (isDesktop) + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Add token", + enabled: addTokenButtonEnabled, + onPressed: () { + Navigator.of(context).pop(currentToken!); + }, + ), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart b/lib/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart new file mode 100644 index 000000000..a2cf7e881 --- /dev/null +++ b/lib/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart @@ -0,0 +1,566 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages/add_wallet_views/add_token_view/add_custom_token_view.dart'; +import 'package:stackwallet/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list.dart'; +import 'package:stackwallet/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list_element.dart'; +import 'package:stackwallet/pages/add_wallet_views/add_token_view/sub_widgets/add_token_text.dart'; +import 'package:stackwallet/pages/home_view/home_view.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/default_eth_tokens.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; + +class EditWalletTokensView extends ConsumerStatefulWidget { + const EditWalletTokensView({ + Key? key, + required this.walletId, + this.contractsToMarkSelected, + this.isDesktopPopup = false, + }) : super(key: key); + + final String walletId; + final List? contractsToMarkSelected; + final bool isDesktopPopup; + + static const routeName = "/editWalletTokens"; + + @override + ConsumerState createState() => + _EditWalletTokensViewState(); +} + +class _EditWalletTokensViewState extends ConsumerState { + late final TextEditingController _searchFieldController; + late final FocusNode _searchFocusNode; + + String _searchTerm = ""; + + final List tokenEntities = []; + + final bool isDesktop = Util.isDesktop; + + List filter( + String text, + List entities, + ) { + final _entities = [...entities]; + if (text.isNotEmpty) { + final lowercaseTerm = text.toLowerCase(); + _entities.retainWhere( + (e) => + e.token.name.toLowerCase().contains(lowercaseTerm) || + e.token.symbol.toLowerCase().contains(lowercaseTerm) || + e.token.address.toLowerCase().contains(lowercaseTerm), + ); + } + + return _entities; + } + + Future onNextPressed() async { + final selectedTokens = tokenEntities + .where((e) => e.selected) + .map((e) => e.token.address) + .toList(); + + final ethWallet = ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId) + .wallet as EthereumWallet; + + await ethWallet.updateTokenContracts(selectedTokens); + if (mounted) { + if (widget.contractsToMarkSelected == null) { + Navigator.of(context).pop(42); + } else { + if (isDesktop) { + Navigator.of(context).popUntil( + ModalRoute.withName(DesktopHomeView.routeName), + ); + } else { + await Navigator.of(context).pushNamedAndRemoveUntil( + HomeView.routeName, + (route) => false, + ); + } + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "${ethWallet.walletName} tokens saved", + context: context, + ), + ); + } + } + } + } + + Future _addToken() async { + EthContract? contract; + + if (isDesktop) { + contract = await showDialog( + context: context, + builder: (context) => const DesktopDialog( + maxWidth: 580, + maxHeight: 500, + child: AddCustomTokenView(), + ), + ); + } else { + final result = await Navigator.of(context).pushNamed( + AddCustomTokenView.routeName, + ); + contract = result as EthContract?; + } + + if (contract != null) { + await MainDB.instance.putEthContract(contract); + if (mounted) { + setState(() { + if (tokenEntities + .where((e) => e.token.address == contract!.address) + .isEmpty) { + tokenEntities + .add(AddTokenListElementData(contract!)..selected = true); + tokenEntities.sort((a, b) => a.token.name.compareTo(b.token.name)); + } + }); + } + } + } + + @override + void initState() { + _searchFieldController = TextEditingController(); + _searchFocusNode = FocusNode(); + + final contracts = + MainDB.instance.getEthContracts().sortByName().findAllSync(); + + if (contracts.isEmpty) { + contracts.addAll(DefaultTokens.list); + MainDB.instance.putEthContracts(contracts); + } + + tokenEntities.addAll(contracts.map((e) => AddTokenListElementData(e))); + + final walletContracts = (ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId) + .wallet as EthereumWallet) + .getWalletTokenContractAddresses(); + + final shouldMarkAsSelectedContracts = [ + ...walletContracts, + ...(widget.contractsToMarkSelected ?? []), + ]; + + for (final e in tokenEntities) { + e.selected = shouldMarkAsSelectedContracts.contains(e.token.address); + } + + super.initState(); + } + + @override + void dispose() { + _searchFieldController.dispose(); + _searchFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + final walletName = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(widget.walletId).walletName)); + + if (isDesktop) { + return ConditionalParent( + condition: !widget.isDesktopPopup, + builder: (child) => DesktopScaffold( + appBar: DesktopAppBar( + isCompactHeight: false, + useSpacers: false, + leading: const AppBarBackButton(), + overlayCenter: Text( + walletName, + style: STextStyles.desktopSubtitleH2(context), + ), + trailing: widget.contractsToMarkSelected == null + ? Padding( + padding: const EdgeInsets.only( + right: 24, + ), + child: SizedBox( + height: 56, + child: TextButton( + style: Theme.of(context) + .extension()! + .getSmallSecondaryEnabledButtonStyle(context), + onPressed: _addToken, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 30, + ), + child: Text( + "Add custom token", + style: + STextStyles.desktopButtonSmallSecondaryEnabled( + context), + ), + ), + ), + ), + ) + : null, + ), + body: SizedBox( + width: 480, + child: Column( + children: [ + const AddTokenText( + isDesktop: true, + ), + const SizedBox( + height: 16, + ), + Expanded( + child: RoundedWhiteContainer( + radiusMultiplier: 2, + padding: const EdgeInsets.only( + left: 20, + top: 20, + right: 20, + bottom: 0, + ), + child: child, + ), + ), + const SizedBox( + height: 26, + ), + SizedBox( + height: 70, + width: 480, + child: PrimaryButton( + label: widget.contractsToMarkSelected != null + ? "Save" + : "Next", + onPressed: onNextPressed, + ), + ), + const SizedBox( + height: 32, + ), + ], + ), + ), + ), + child: ConditionalParent( + condition: widget.isDesktopPopup, + builder: (child) => DesktopDialog( + maxHeight: 670, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Edit tokens", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 16, + ), + child: child, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + ), + child: Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Add custom token", + buttonHeight: ButtonHeight.l, + onPressed: _addToken, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Done", + buttonHeight: ButtonHeight.l, + onPressed: onNextPressed, + ), + ), + ], + ), + ), + const SizedBox( + height: 32, + ), + ], + ), + ), + child: Column( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: _searchFieldController, + focusNode: _searchFocusNode, + onChanged: (value) { + setState(() { + _searchTerm = value; + }); + }, + style: STextStyles.desktopTextMedium(context).copyWith( + height: 2, + ), + decoration: standardInputDecoration( + "Search", + _searchFocusNode, + context, + ).copyWith( + contentPadding: const EdgeInsets.symmetric( + vertical: 10, + ), + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + // vertical: 20, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: 24, + height: 24, + color: Theme.of(context) + .extension()! + .textFieldDefaultSearchIconLeft, + ), + ), + suffixIcon: _searchFieldController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 10), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon( + width: 24, + height: 24, + ), + onTap: () async { + setState(() { + _searchFieldController.text = ""; + _searchTerm = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), + const SizedBox( + height: 12, + ), + Expanded( + child: AddTokenList( + walletId: widget.walletId, + items: filter(_searchTerm, tokenEntities), + addFunction: isDesktop ? null : _addToken, + ), + ), + const SizedBox( + height: 12, + ), + ], + ), + ), + ); + } else { + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + actions: [ + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 20, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + size: 36, + shadows: const [], + color: + Theme.of(context).extension()!.background, + icon: SvgPicture.asset( + Assets.svg.circlePlusFilled, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, + width: 20, + height: 20, + ), + onPressed: _addToken, + ), + ), + ), + ], + ), + body: Container( + color: Theme.of(context).extension()!.background, + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + AddTokenText( + isDesktop: false, + walletName: walletName, + ), + const SizedBox( + height: 16, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autofocus: isDesktop, + autocorrect: !isDesktop, + enableSuggestions: !isDesktop, + controller: _searchFieldController, + focusNode: _searchFocusNode, + onChanged: (value) => setState(() => _searchTerm = value), + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Search", + _searchFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 16, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: 16, + height: 16, + ), + ), + suffixIcon: _searchFieldController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchFieldController.text = ""; + _searchTerm = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), + const SizedBox( + height: 10, + ), + Expanded( + child: AddTokenList( + walletId: widget.walletId, + items: filter(_searchTerm, tokenEntities), + addFunction: _addToken, + ), + ), + const SizedBox( + height: 16, + ), + PrimaryButton( + label: widget.contractsToMarkSelected != null + ? "Save" + : "Next", + onPressed: onNextPressed, + ), + ], + ), + ), + ), + ), + ); + } + } +} diff --git a/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_custom_token_selector.dart b/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_custom_token_selector.dart new file mode 100644 index 000000000..715636e8f --- /dev/null +++ b/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_custom_token_selector.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; + +class AddCustomTokenSelector extends StatelessWidget { + const AddCustomTokenSelector({ + Key? key, + required this.addFunction, + }) : super(key: key); + + final VoidCallback addFunction; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: MaterialButton( + key: const Key("coinSelectItemButtonKey_add_custom"), + padding: Util.isDesktop + ? const EdgeInsets.only(left: 24) + : const EdgeInsets.all(12), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: addFunction, + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: Util.isDesktop ? 70 : 0, + ), + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.circlePlusFilled, + color: Theme.of(context).extension()!.textDark, + width: 26, + height: 26, + ), + const SizedBox( + width: 12, + ), + Text( + "Add custom token", + style: Util.isDesktop + ? STextStyles.desktopTextMedium(context) + : STextStyles.w600_14(context), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list.dart b/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list.dart new file mode 100644 index 000000000..a8532f307 --- /dev/null +++ b/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/pages/add_wallet_views/add_token_view/sub_widgets/add_custom_token_selector.dart'; +import 'package:stackwallet/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list_element.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; + +class AddTokenList extends StatelessWidget { + const AddTokenList({ + Key? key, + required this.walletId, + required this.items, + required this.addFunction, + }) : super(key: key); + + final String walletId; + final List items; + final VoidCallback? addFunction; + + @override + Widget build(BuildContext context) { + return ListView.builder( + shrinkWrap: true, + primary: false, + itemCount: items.length, + itemBuilder: (ctx, index) { + return ConditionalParent( + condition: index == items.length - 1 && addFunction != null, + builder: (child) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + child, + AddCustomTokenSelector( + addFunction: addFunction!, + ), + ], + ), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: AddTokenListElement( + data: items[index], + ), + ), + ); + }, + ); + } +} diff --git a/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list_element.dart b/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list_element.dart new file mode 100644 index 000000000..5a10c0178 --- /dev/null +++ b/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_list_element.dart @@ -0,0 +1,129 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/models/isar/exchange_cache/currency.dart'; +import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; +import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class AddTokenListElementData { + AddTokenListElementData(this.token); + + final EthContract token; + bool selected = false; +} + +class AddTokenListElement extends StatefulWidget { + const AddTokenListElement({Key? key, required this.data}) : super(key: key); + + final AddTokenListElementData data; + + @override + State createState() => _AddTokenListElementState(); +} + +class _AddTokenListElementState extends State { + final bool isDesktop = Util.isDesktop; + + @override + Widget build(BuildContext context) { + final currency = ExchangeDataLoadingService.instance.isar.currencies + .where() + .exchangeNameEqualTo(ChangeNowExchange.exchangeName) + .filter() + .tokenContractEqualTo( + widget.data.token.address, + caseSensitive: false, + ) + .and() + .imageIsNotEmpty() + .findFirstSync(); + + final String mainLabel = widget.data.token.name; + final double iconSize = isDesktop ? 32 : 24; + + return RoundedWhiteContainer( + padding: EdgeInsets.all(isDesktop ? 16 : 12), + borderColor: isDesktop + ? Theme.of(context).extension()!.backgroundAppBar + : null, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + currency != null + ? SvgPicture.network( + currency.image, + width: iconSize, + height: iconSize, + ) + : SvgPicture.asset( + widget.data.token.symbol == "BNB" + ? Assets.svg.bnbIcon + : Assets.svg.ethereum, + width: iconSize, + height: iconSize, + ), + const SizedBox( + width: 12, + ), + ConditionalParent( + condition: isDesktop, + builder: (child) => Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + child, + const SizedBox( + height: 2, + ), + Text( + widget.data.token.symbol, + style: STextStyles.desktopTextExtraExtraSmall(context), + overflow: TextOverflow.ellipsis, + ), + ], + ), + child: Text( + isDesktop + ? mainLabel + : "$mainLabel (${widget.data.token.symbol})", + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.w600_14(context), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox( + width: 4, + ), + isDesktop + ? Checkbox( + value: widget.data.selected, + onChanged: (newValue) => + setState(() => widget.data.selected = newValue!), + ) + : SizedBox( + height: 20, + width: 40, + child: DraggableSwitchButton( + isOn: widget.data.selected, + onValueChanged: (newValue) { + widget.data.selected = newValue; + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_text.dart b/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_text.dart new file mode 100644 index 000000000..115d8be5c --- /dev/null +++ b/lib/pages/add_wallet_views/add_token_view/sub_widgets/add_token_text.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; + +class AddTokenText extends StatelessWidget { + const AddTokenText({ + Key? key, + required this.isDesktop, + this.walletName, + }) : super(key: key); + + final String? walletName; + final bool isDesktop; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (walletName != null) + Text( + walletName!, + textAlign: TextAlign.center, + style: isDesktop + ? STextStyles.sectionLabelMedium12(context) // todo: fixme + : STextStyles.sectionLabelMedium12(context), + ), + if (walletName != null) + const SizedBox( + height: 4, + ), + Text( + "Edit Tokens", + textAlign: TextAlign.center, + style: isDesktop + ? STextStyles.desktopH2(context) + : STextStyles.pageTitleH1(context), + ), + const SizedBox( + height: 8, + ), + Text( + "You can also do it later in your wallet", + textAlign: TextAlign.center, + style: isDesktop + ? STextStyles.desktopSubtitleH2(context) + : STextStyles.subtitle(context), + ), + ], + ); + } +} diff --git a/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart b/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart index b9b04cec8..c8cd467e8 100644 --- a/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart +++ b/lib/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart @@ -1,47 +1,147 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/add_wallet_list_entity/add_wallet_list_entity.dart'; +import 'package:stackwallet/models/add_wallet_list_entity/sub_classes/coin_entity.dart'; +import 'package:stackwallet/models/add_wallet_list_entity/sub_classes/eth_token_entity.dart'; +import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; +import 'package:stackwallet/pages/add_wallet_views/add_token_view/add_custom_token_view.dart'; +import 'package:stackwallet/pages/add_wallet_views/add_token_view/sub_widgets/add_custom_token_selector.dart'; import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/sub_widgets/add_wallet_text.dart'; -import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/sub_widgets/mobile_coin_list.dart'; +import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/sub_widgets/expanding_sub_list_item.dart'; import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/sub_widgets/next_button.dart'; -import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/sub_widgets/searchable_coin_list.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/default_eth_tokens.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; +import 'package:stackwallet/widgets/expandable.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; -class AddWalletView extends StatefulWidget { +class AddWalletView extends ConsumerStatefulWidget { const AddWalletView({Key? key}) : super(key: key); static const routeName = "/addWallet"; @override - State createState() => _AddWalletViewState(); + ConsumerState createState() => _AddWalletViewState(); } -class _AddWalletViewState extends State { +class _AddWalletViewState extends ConsumerState { late final TextEditingController _searchFieldController; late final FocusNode _searchFocusNode; String _searchTerm = ""; - final List coins = [...Coin.values]; + final List _coinsTestnet = [ + ...Coin.values.sublist(Coin.values.length - kTestNetCoinCount - 1), + ]; + final List _coins = [ + ...Coin.values.sublist(0, Coin.values.length - kTestNetCoinCount - 1) + ]; + final List coinEntities = []; + final List tokenEntities = []; + + final bool isDesktop = Util.isDesktop; + + List filter( + String text, + List entities, + ) { + final _entities = [...entities]; + if (text.isNotEmpty) { + final lowercaseTerm = text.toLowerCase(); + _entities.retainWhere( + (e) => + e.ticker.toLowerCase().contains(lowercaseTerm) || + e.name.toLowerCase().contains(lowercaseTerm) || + e.coin.name.toLowerCase().contains(lowercaseTerm) || + (e is EthTokenEntity && + e.token.address.toLowerCase().contains(lowercaseTerm)), + ); + } + + return _entities; + } + + Future _addToken() async { + EthContract? contract; + if (isDesktop) { + contract = await showDialog( + context: context, + builder: (context) => const DesktopDialog( + maxWidth: 580, + maxHeight: 500, + child: AddCustomTokenView(), + ), + ); + } else { + contract = await Navigator.of(context).pushNamed( + AddCustomTokenView.routeName, + ); + } + + if (contract != null) { + await MainDB.instance.putEthContract(contract); + if (mounted) { + setState(() { + if (tokenEntities + .where((e) => e.token.address == contract!.address) + .isEmpty) { + tokenEntities.add(EthTokenEntity(contract!)); + tokenEntities.sort((a, b) => a.token.name.compareTo(b.token.name)); + } + }); + } + } + } @override void initState() { _searchFieldController = TextEditingController(); _searchFocusNode = FocusNode(); - coins.remove(Coin.firoTestNet); + _coinsTestnet.remove(Coin.firoTestNet); + if (Platform.isWindows) { + _coins.remove(Coin.monero); + _coins.remove(Coin.wownero); + } + + coinEntities.addAll(_coins.map((e) => CoinEntity(e))); + + if (ref.read(prefsChangeNotifierProvider).showTestNetCoins) { + coinEntities.addAll(_coinsTestnet.map((e) => CoinEntity(e))); + } + + final contracts = + MainDB.instance.getEthContracts().sortByName().findAllSync(); + + if (contracts.isEmpty) { + contracts.addAll(DefaultTokens.list); + MainDB.instance.putEthContracts(contracts); + } + + tokenEntities.addAll(contracts.map((e) => EthTokenEntity(e))); + + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.refresh(addWalletSelectedEntityStateProvider); + }); + super.initState(); } @@ -56,7 +156,7 @@ class _AddWalletViewState extends State { Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - if (Util.isDesktop) { + if (isDesktop) { return DesktopScaffold( appBar: const DesktopAppBar( isCompactHeight: false, @@ -154,13 +254,35 @@ class _AddWalletViewState extends State { ), ), ), + const SizedBox( + height: 8, + ), Expanded( - child: SearchableCoinList( - coins: coins, - isDesktop: true, - searchTerm: _searchTerm, + child: SingleChildScrollView( + child: Column( + children: [ + ExpandingSubListItem( + title: "Coins", + entities: filter(_searchTerm, coinEntities), + initialState: ExpandableState.expanded, + animationDurationMultiplier: 0.5, + ), + ExpandingSubListItem( + title: "Tokens", + entities: filter(_searchTerm, tokenEntities), + initialState: ExpandableState.expanded, + animationDurationMultiplier: 0.5, + trailing: AddCustomTokenSelector( + addFunction: _addToken, + ), + ), + ], + ), ), ), + const SizedBox( + height: 20, + ), ], ), ), @@ -207,9 +329,83 @@ class _AddWalletViewState extends State { const SizedBox( height: 16, ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: Semantics( + label: + "Search Text Field. Inputs Text To Search In Wallets.", + excludeSemantics: true, + child: TextField( + autofocus: isDesktop, + autocorrect: !isDesktop, + enableSuggestions: !isDesktop, + controller: _searchFieldController, + focusNode: _searchFocusNode, + onChanged: (value) => + setState(() => _searchTerm = value), + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Search", + _searchFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 16, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: 16, + height: 16, + ), + ), + suffixIcon: _searchFieldController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchFieldController.text = + ""; + _searchTerm = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + )), + const SizedBox( + height: 10, + ), Expanded( - child: MobileCoinList( - coins: coins, + child: SingleChildScrollView( + child: Column( + children: [ + ExpandingSubListItem( + title: "Coins", + entities: filter(_searchTerm, coinEntities), + initialState: ExpandableState.expanded, + ), + ExpandingSubListItem( + title: "Tokens", + entities: filter(_searchTerm, tokenEntities), + initialState: ExpandableState.expanded, + ), + ], + ), ), ), const SizedBox( diff --git a/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/add_wallet_entity_list.dart b/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/add_wallet_entity_list.dart new file mode 100644 index 000000000..c503daa88 --- /dev/null +++ b/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/add_wallet_entity_list.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/models/add_wallet_list_entity/add_wallet_list_entity.dart'; +import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/sub_widgets/coin_select_item.dart'; + +class AddWalletEntityList extends StatelessWidget { + const AddWalletEntityList({ + Key? key, + required this.entities, + this.trailing, + }) : super(key: key); + + final List entities; + final Widget? trailing; + + @override + Widget build(BuildContext context) { + return ListView.builder( + shrinkWrap: true, + primary: false, + itemCount: trailing != null ? entities.length + 1 : entities.length, + itemBuilder: (ctx, index) { + if (trailing != null && index == entities.length) { + return Padding( + padding: const EdgeInsets.all(4), + child: trailing, + ); + } else { + return Padding( + padding: const EdgeInsets.all(4), + child: CoinSelectItem( + entity: entities[index], + ), + ); + } + }, + ); + } +} diff --git a/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/coin_select_item.dart b/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/coin_select_item.dart index 87ba95e64..7e85337c4 100644 --- a/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/coin_select_item.dart +++ b/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/coin_select_item.dart @@ -1,41 +1,63 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/models/add_wallet_list_entity/add_wallet_list_entity.dart'; +import 'package:stackwallet/models/add_wallet_list_entity/sub_classes/eth_token_entity.dart'; +import 'package:stackwallet/models/isar/exchange_cache/currency.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; +import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; class CoinSelectItem extends ConsumerWidget { const CoinSelectItem({ Key? key, - required this.coin, + required this.entity, }) : super(key: key); - final Coin coin; + final AddWalletListEntity entity; @override Widget build(BuildContext context, WidgetRef ref) { - debugPrint("BUILD: CoinSelectItem for ${coin.name}"); - final selectedCoin = ref.watch(addWalletSelectedCoinStateProvider); + debugPrint("BUILD: CoinSelectItem for ${entity.name}"); + final selectedEntity = ref.watch(addWalletSelectedEntityStateProvider); final isDesktop = Util.isDesktop; + String? tokenImageUri; + if (entity is EthTokenEntity) { + final currency = ExchangeDataLoadingService.instance.isar.currencies + .where() + .exchangeNameEqualTo(ChangeNowExchange.exchangeName) + .filter() + .tokenContractEqualTo( + (entity as EthTokenEntity).token.address, + caseSensitive: false, + ) + .and() + .imageIsNotEmpty() + .findFirstSync(); + tokenImageUri = currency?.image; + } + return Container( decoration: BoxDecoration( - // color: selectedCoin == coin ? CFColors.selection : CFColors.white, - color: selectedCoin == coin + color: selectedEntity == entity ? Theme.of(context).extension()!.textFieldActiveBG : Theme.of(context).extension()!.popupBG, borderRadius: BorderRadius.circular(Constants.size.circularBorderRadius), ), child: MaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - key: Key("coinSelectItemButtonKey_${coin.name}"), + key: Key("coinSelectItemButtonKey_${entity.name}${entity.ticker}"), padding: isDesktop ? const EdgeInsets.only(left: 24) : const EdgeInsets.all(12), @@ -50,24 +72,32 @@ class CoinSelectItem extends ConsumerWidget { ), child: Row( children: [ - SvgPicture.asset( - Assets.svg.iconFor(coin: coin), - width: 26, - height: 26, - ), + tokenImageUri != null + ? SvgPicture.network( + tokenImageUri, + width: 26, + height: 26, + ) + : SvgPicture.file( + File( + ref.watch(coinIconProvider(entity.coin)), + ), + width: 26, + height: 26, + ), SizedBox( width: isDesktop ? 12 : 10, ), Text( - coin.prettyName, + "${entity.name} (${entity.ticker})", style: isDesktop ? STextStyles.desktopTextMedium(context) : STextStyles.subtitle600(context).copyWith( fontSize: 14, ), ), - if (isDesktop && selectedCoin == coin) const Spacer(), - if (isDesktop && selectedCoin == coin) + if (isDesktop && selectedEntity == entity) const Spacer(), + if (isDesktop && selectedEntity == entity) Padding( padding: const EdgeInsets.only( right: 18, @@ -86,8 +116,9 @@ class CoinSelectItem extends ConsumerWidget { ], ), ), - onPressed: () => - ref.read(addWalletSelectedCoinStateProvider.state).state = coin, + onPressed: () { + ref.read(addWalletSelectedEntityStateProvider.state).state = entity; + }, ), ); } diff --git a/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/expanding_sub_list_item.dart b/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/expanding_sub_list_item.dart new file mode 100644 index 000000000..db6a6ae31 --- /dev/null +++ b/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/expanding_sub_list_item.dart @@ -0,0 +1,123 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/add_wallet_list_entity/add_wallet_list_entity.dart'; +import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/sub_widgets/add_wallet_entity_list.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/animated_widgets/rotate_icon.dart'; +import 'package:stackwallet/widgets/expandable.dart'; + +class ExpandingSubListItem extends StatefulWidget { + const ExpandingSubListItem({ + Key? key, + required this.title, + required this.entities, + this.trailing, + required this.initialState, + double? animationDurationMultiplier, + this.curve = Curves.easeInOutCubicEmphasized, + }) : animationDurationMultiplier = + animationDurationMultiplier ?? entities.length * 0.11, + super(key: key); + + final String title; + final List entities; + final Widget? trailing; + final ExpandableState initialState; + final double animationDurationMultiplier; + final Curve curve; + + @override + State createState() => _ExpandingSubListItemState(); +} + +class _ExpandingSubListItemState extends State { + final isDesktop = Util.isDesktop; + + late final ExpandableController _controller; + late final RotateIconController _rotateIconController; + + @override + void initState() { + _controller = ExpandableController(); + _rotateIconController = RotateIconController(); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (widget.initialState == ExpandableState.expanded) { + _controller.toggle?.call(); + } + }); + super.initState(); + } + + @override + void dispose() { + _controller.toggle = null; + _rotateIconController.forward = null; + _rotateIconController.reverse = null; + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Expandable( + animationDurationMultiplier: widget.animationDurationMultiplier, + curve: widget.curve, + controller: _controller, + onExpandWillChange: (state) { + if (state == ExpandableState.expanded) { + _rotateIconController.forward?.call(); + } else { + _rotateIconController.reverse?.call(); + } + }, + header: Container( + color: Colors.transparent, + child: Padding( + padding: const EdgeInsets.only( + top: 8.0, + bottom: 8.0, + right: 10, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + widget.title, + style: isDesktop + ? STextStyles.desktopTextMedium(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark3, + ) + : STextStyles.smallMed12(context), + textAlign: TextAlign.left, + ), + RotateIcon( + icon: SvgPicture.asset( + Assets.svg.chevronDown, + width: isDesktop ? 20 : 12, + height: isDesktop ? 10 : 6, + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconRight, + ), + curve: widget.curve, + animationDurationMultiplier: widget.animationDurationMultiplier, + controller: _rotateIconController, + ), + ], + ), + ), + ), + body: SingleChildScrollView( + primary: false, + child: AddWalletEntityList( + entities: widget.entities, + trailing: widget.trailing, + ), + ), + ); + } +} diff --git a/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/mobile_coin_list.dart b/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/mobile_coin_list.dart deleted file mode 100644 index fd950963c..000000000 --- a/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/mobile_coin_list.dart +++ /dev/null @@ -1,38 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/sub_widgets/coin_select_item.dart'; -import 'package:stackwallet/providers/global/prefs_provider.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; - -class MobileCoinList extends StatelessWidget { - const MobileCoinList({ - Key? key, - required this.coins, - }) : super(key: key); - - final List coins; - - @override - Widget build(BuildContext context) { - return Consumer( - builder: (_, ref, __) { - bool showTestNet = ref.watch( - prefsChangeNotifierProvider.select((value) => value.showTestNetCoins), - ); - - return ListView.builder( - itemCount: - showTestNet ? coins.length : coins.length - (kTestNetCoinCount), - itemBuilder: (ctx, index) { - return Padding( - padding: const EdgeInsets.all(4), - child: CoinSelectItem( - coin: coins[index], - ), - ); - }, - ); - }, - ); - } -} diff --git a/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/next_button.dart b/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/next_button.dart index 9cc91cb45..5a45ea39a 100644 --- a/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/next_button.dart +++ b/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/next_button.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/add_wallet_list_entity/sub_classes/eth_token_entity.dart'; import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart'; +import 'package:stackwallet/pages/add_wallet_views/select_wallet_for_token_view.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; class AddWalletNextButton extends ConsumerWidget { const AddWalletNextButton({ @@ -17,7 +19,7 @@ class AddWalletNextButton extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { debugPrint("BUILD: NextButton"); final selectedCoin = - ref.watch(addWalletSelectedCoinStateProvider.state).state; + ref.watch(addWalletSelectedEntityStateProvider.state).state; final enabled = selectedCoin != null; @@ -25,23 +27,25 @@ class AddWalletNextButton extends ConsumerWidget { onPressed: !enabled ? null : () { - final selectedCoin = - ref.read(addWalletSelectedCoinStateProvider.state).state; - - //todo: check if print needed - // debugPrint("Next pressed with ${selectedCoin!.name} selected!"); - Navigator.of(context).pushNamed( - CreateOrRestoreWalletView.routeName, - arguments: selectedCoin, - ); + if (selectedCoin is EthTokenEntity) { + Navigator.of(context).pushNamed( + SelectWalletForTokenView.routeName, + arguments: selectedCoin, + ); + } else { + Navigator.of(context).pushNamed( + CreateOrRestoreWalletView.routeName, + arguments: selectedCoin, + ); + } }, style: enabled ? Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context) + .getPrimaryEnabledButtonStyle(context) : Theme.of(context) .extension()! - .getPrimaryDisabledButtonColor(context), + .getPrimaryDisabledButtonStyle(context), child: Text( "Next", style: isDesktop diff --git a/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/searchable_coin_list.dart b/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/searchable_coin_list.dart deleted file mode 100644 index 38181b9e1..000000000 --- a/lib/pages/add_wallet_views/add_wallet_view/sub_widgets/searchable_coin_list.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/sub_widgets/coin_select_item.dart'; -import 'package:stackwallet/providers/global/prefs_provider.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; - -class SearchableCoinList extends ConsumerWidget { - const SearchableCoinList({ - Key? key, - required this.coins, - required this.isDesktop, - required this.searchTerm, - }) : super(key: key); - - final List coins; - final bool isDesktop; - final String searchTerm; - - List filterCoins(String text, bool showTestNetCoins) { - final _coins = [...coins]; - if (text.isNotEmpty) { - final lowercaseTerm = text.toLowerCase(); - _coins.retainWhere((e) => - e.ticker.toLowerCase().contains(lowercaseTerm) || - e.prettyName.toLowerCase().contains(lowercaseTerm) || - e.name.toLowerCase().contains(lowercaseTerm)); - } - if (!showTestNetCoins) { - _coins.removeWhere( - (e) => e.name.endsWith("TestNet") || e == Coin.bitcoincashTestnet); - } - // remove firo testnet regardless - _coins.remove(Coin.firoTestNet); - - // Kidgloves for Wownero on desktop - if(isDesktop) { - _coins.remove(Coin.wownero); - } - - return _coins; - } - - @override - Widget build(BuildContext context, WidgetRef ref) { - bool showTestNet = ref.watch( - prefsChangeNotifierProvider.select((value) => value.showTestNetCoins), - ); - - final _coins = filterCoins(searchTerm, showTestNet); - - return ListView.builder( - itemCount: _coins.length, - itemBuilder: (ctx, index) { - return Padding( - padding: const EdgeInsets.all(4), - child: CoinSelectItem( - coin: _coins[index], - ), - ); - }, - ); - } -} diff --git a/lib/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart b/lib/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart index 206e5e787..e19cdb1cb 100644 --- a/lib/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart +++ b/lib/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:stackwallet/models/add_wallet_list_entity/add_wallet_list_entity.dart'; import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/sub_widgets/coin_image.dart'; import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/sub_widgets/create_or_restore_wallet_subtitle.dart'; import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/sub_widgets/create_or_restore_wallet_title.dart'; import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/sub_widgets/create_wallet_button_group.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; @@ -15,12 +15,12 @@ import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; class CreateOrRestoreWalletView extends StatelessWidget { const CreateOrRestoreWalletView({ Key? key, - required this.coin, + required this.entity, }) : super(key: key); static const routeName = "/createOrRestoreWallet"; - final Coin coin; + final AddWalletListEntity entity; @override Widget build(BuildContext context) { @@ -44,7 +44,7 @@ class CreateOrRestoreWalletView extends StatelessWidget { flex: 10, ), CreateRestoreWalletTitle( - coin: coin, + coin: entity.coin, isDesktop: isDesktop, ), const SizedBox( @@ -60,14 +60,17 @@ class CreateOrRestoreWalletView extends StatelessWidget { height: 32, ), CoinImage( - coin: coin, - isDesktop: isDesktop, + coin: entity.coin, + width: + isDesktop ? 324 : MediaQuery.of(context).size.width / 1.6, + height: + isDesktop ? null : MediaQuery.of(context).size.width / 1.6, ), const SizedBox( height: 32, ), CreateWalletButtonGroup( - coin: coin, + coin: entity.coin, isDesktop: isDesktop, ), const Spacer( @@ -89,41 +92,61 @@ class CreateOrRestoreWalletView extends StatelessWidget { }, ), ), - body: Container( - color: Theme.of(context).extension()!.background, - child: Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Padding( - padding: const EdgeInsets.all(31), - child: CoinImage( - coin: coin, - isDesktop: isDesktop, + body: SafeArea( + child: Container( + color: Theme.of(context).extension()!.background, + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Spacer( + flex: 2, + ), + CoinImage( + coin: entity.coin, + width: isDesktop + ? 324 + : MediaQuery.of(context).size.width / 1.6, + height: isDesktop + ? null + : MediaQuery.of(context).size.width / 1.6, + ), + const Spacer( + flex: 2, + ), + CreateRestoreWalletTitle( + coin: entity.coin, + isDesktop: isDesktop, + ), + const SizedBox( + height: 8, + ), + CreateRestoreWalletSubTitle( + isDesktop: isDesktop, + ), + const Spacer( + flex: 5, + ), + CreateWalletButtonGroup( + coin: entity.coin, + isDesktop: isDesktop, + ), + ], + ), + ), + ), ), - ), - const Spacer( - flex: 2, - ), - CreateRestoreWalletTitle( - coin: coin, - isDesktop: isDesktop, - ), - const SizedBox( - height: 8, - ), - CreateRestoreWalletSubTitle( - isDesktop: isDesktop, - ), - const Spacer( - flex: 5, - ), - CreateWalletButtonGroup( - coin: coin, - isDesktop: isDesktop, - ), - ], + ); + }, ), ), ), diff --git a/lib/pages/add_wallet_views/create_or_restore_wallet_view/sub_widgets/coin_image.dart b/lib/pages/add_wallet_views/create_or_restore_wallet_view/sub_widgets/coin_image.dart index c96cc14ad..0cf8cadb1 100644 --- a/lib/pages/add_wallet_views/create_or_restore_wallet_view/sub_widgets/coin_image.dart +++ b/lib/pages/add_wallet_views/create_or_restore_wallet_view/sub_widgets/coin_image.dart @@ -1,24 +1,44 @@ -import 'package:flutter/material.dart'; -import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'dart:io'; -class CoinImage extends StatelessWidget { +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/themes/coin_image_provider.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/util.dart'; + +class CoinImage extends ConsumerWidget { const CoinImage({ Key? key, required this.coin, - required this.isDesktop, + this.width, + this.height, }) : super(key: key); final Coin coin; - final bool isDesktop; + final double? width; + final double? height; @override - Widget build(BuildContext context) { - return Image( - image: AssetImage( - Assets.png.imageFor(coin: coin), - ), - width: isDesktop ? 324 : MediaQuery.of(context).size.width / 3, - ); + Widget build(BuildContext context, WidgetRef ref) { + final assetPath = ref.watch(coinImageProvider(coin)); + + final isDesktop = Util.isDesktop; + + if (!assetPath.endsWith(".svg")) { + return SizedBox( + width: isDesktop ? width : MediaQuery.of(context).size.width, + height: isDesktop ? height : MediaQuery.of(context).size.width, + child: Image.file( + File(assetPath), + ), + ); + } else { + return SvgPicture.file( + File(assetPath), + width: width, + height: height, + ); + } } } diff --git a/lib/pages/add_wallet_views/create_or_restore_wallet_view/sub_widgets/create_wallet_button_group.dart b/lib/pages/add_wallet_views/create_or_restore_wallet_view/sub_widgets/create_wallet_button_group.dart index 4a7661892..da355962e 100644 --- a/lib/pages/add_wallet_views/create_or_restore_wallet_view/sub_widgets/create_wallet_button_group.dart +++ b/lib/pages/add_wallet_views/create_or_restore_wallet_view/sub_widgets/create_wallet_button_group.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:stackwallet/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/enums/add_wallet_type_enum.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:tuple/tuple.dart'; class CreateWalletButtonGroup extends StatelessWidget { @@ -30,7 +30,7 @@ class CreateWalletButtonGroup extends StatelessWidget { child: TextButton( style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), onPressed: () { Navigator.of(context).pushNamed( NameYourWalletView.routeName, @@ -59,7 +59,7 @@ class CreateWalletButtonGroup extends StatelessWidget { child: TextButton( style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), onPressed: () { Navigator.of(context).pushNamed( NameYourWalletView.routeName, diff --git a/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart b/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart index 71faf92e4..0948189d8 100644 --- a/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart +++ b/lib/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart @@ -3,18 +3,19 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/sub_widgets/coin_image.dart'; import 'package:stackwallet/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart'; import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart'; import 'package:stackwallet/providers/global/wallets_service_provider.dart'; import 'package:stackwallet/providers/ui/verify_recovery_phrase/mnemonic_word_count_state_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/add_wallet_type_enum.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/name_generator.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; @@ -165,11 +166,10 @@ class _NameYourWalletViewState extends ConsumerState { flex: 1, ), if (!isDesktop) - Image( - image: AssetImage( - Assets.png.imageFor(coin: coin), - ), + CoinImage( + coin: coin, height: 100, + width: 100, ), SizedBox( height: isDesktop ? 0 : 16, @@ -238,13 +238,23 @@ class _NameYourWalletViewState extends ConsumerState { TextFieldIconButton( key: const Key("genRandomWalletNameButtonKey"), child: _showDiceIcon - ? DiceIcon( - width: isDesktop ? 20 : 17, - height: isDesktop ? 20 : 17, + ? Semantics( + label: + "Generate Random Wallet Name Button. Generates A Random Name For Wallet.", + excludeSemantics: true, + child: DiceIcon( + width: isDesktop ? 20 : 17, + height: isDesktop ? 20 : 17, + ), ) - : XIcon( - width: isDesktop ? 21 : 18, - height: isDesktop ? 21 : 18, + : Semantics( + label: + "Generate Random Wallet Name Button. Generates A Random Name For Wallet.", + excludeSemantics: true, + child: XIcon( + width: isDesktop ? 21 : 18, + height: isDesktop ? 21 : 18, + ), ), onTap: () async { if (_showDiceIcon) { @@ -364,10 +374,10 @@ class _NameYourWalletViewState extends ConsumerState { style: _nextEnabled ? Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context) + .getPrimaryEnabledButtonStyle(context) : Theme.of(context) .extension()! - .getPrimaryDisabledButtonColor(context), + .getPrimaryDisabledButtonStyle(context), child: Text( "Next", style: isDesktop diff --git a/lib/pages/add_wallet_views/new_wallet_recovery_phrase_view/new_wallet_recovery_phrase_view.dart b/lib/pages/add_wallet_views/new_wallet_recovery_phrase_view/new_wallet_recovery_phrase_view.dart index 6fca0774c..f56b1d52c 100644 --- a/lib/pages/add_wallet_views/new_wallet_recovery_phrase_view/new_wallet_recovery_phrase_view.dart +++ b/lib/pages/add_wallet_views/new_wallet_recovery_phrase_view/new_wallet_recovery_phrase_view.dart @@ -13,11 +13,11 @@ import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/coins/manager.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; @@ -140,6 +140,8 @@ class _NewWalletRecoveryPhraseViewState child: AspectRatio( aspectRatio: 1, child: AppBarIconButton( + semanticsLabel: + "Copy Button. Copies The Recovery Phrase To Clipboard.", color: Theme.of(context) .extension()! .background, @@ -298,7 +300,7 @@ class _NewWalletRecoveryPhraseViewState }, style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), child: Text( "I saved my recovery phrase", style: isDesktop diff --git a/lib/pages/add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table_item.dart b/lib/pages/add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table_item.dart index ec103dfc6..a8b1b5a21 100644 --- a/lib/pages/add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table_item.dart +++ b/lib/pages/add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table_item.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; class MnemonicTableItem extends StatelessWidget { diff --git a/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart b/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart index b159fb9aa..77b796abd 100644 --- a/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart +++ b/lib/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart @@ -11,14 +11,15 @@ import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/coins/coin_service.dart'; import 'package:stackwallet/services/coins/manager.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/default_nodes.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; @@ -86,6 +87,8 @@ class _NewWalletRecoveryPhraseWarningViewState right: 10, ), child: AppBarIconButton( + semanticsLabel: + "Question Button. Opens A Dialog For Recovery Phrase Explanation.", icon: SvgPicture.asset( Assets.svg.circleQuestion, width: 20, @@ -105,8 +108,25 @@ class _NewWalletRecoveryPhraseWarningViewState ) ], ), - body: Padding( - padding: EdgeInsets.all(isDesktop ? 0 : 16), + body: ConditionalParent( + condition: !isDesktop, + builder: (child) => LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(16), + child: child, + ), + ), + ), + ); + }, + ), child: Column( crossAxisAlignment: isDesktop ? CrossAxisAlignment.center @@ -315,9 +335,11 @@ class _NewWalletRecoveryPhraseWarningViewState const SizedBox( width: 20, ), - Text( - "Do not show them to anyone.", - style: STextStyles.navBarTitle(context), + Expanded( + child: Text( + "Do not show them to anyone.", + style: STextStyles.navBarTitle(context), + ), ), ], ), @@ -327,6 +349,10 @@ class _NewWalletRecoveryPhraseWarningViewState ), ), if (!isDesktop) const Spacer(), + if (!isDesktop) + const SizedBox( + height: 16, + ), if (isDesktop) const SizedBox( height: 32, @@ -488,10 +514,10 @@ class _NewWalletRecoveryPhraseWarningViewState style: ref.read(checkBoxStateProvider.state).state ? Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context) + .getPrimaryEnabledButtonStyle(context) : Theme.of(context) .extension()! - .getPrimaryDisabledButtonColor(context), + .getPrimaryDisabledButtonStyle(context), child: Text( "View recovery phrase", style: isDesktop diff --git a/lib/pages/add_wallet_views/restore_wallet_view/confirm_recovery_dialog.dart b/lib/pages/add_wallet_views/restore_wallet_view/confirm_recovery_dialog.dart index 8c50b75b4..f09e95683 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/confirm_recovery_dialog.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/confirm_recovery_dialog.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart index dac2af028..05aaeffa4 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart @@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_rounded_date_picker/flutter_rounded_date_picker.dart'; import 'package:flutter_svg/svg.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/sub_widgets/coin_image.dart'; import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/restore_options_view/sub_widgets/mobile_mnemonic_length_selector.dart'; import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/restore_options_view/sub_widgets/restore_from_date_picker.dart'; import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/restore_options_view/sub_widgets/restore_options_next_button.dart'; @@ -11,21 +12,22 @@ import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/restore_o import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart'; import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/sub_widgets/mnemonic_word_count_select_sheet.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart'; -import 'package:stackwallet/providers/ui/color_theme_provider.dart'; import 'package:stackwallet/providers/ui/verify_recovery_phrase/mnemonic_word_count_state_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; -import 'package:stackwallet/widgets/rounded_date_picker/flutter_rounded_date_picker_widget.dart' - as datePicker; +import 'package:stackwallet/widgets/expandable.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:tuple/tuple.dart'; class RestoreOptionsView extends ConsumerStatefulWidget { @@ -51,20 +53,29 @@ class _RestoreOptionsViewState extends ConsumerState { late TextEditingController _dateController; late FocusNode textFieldFocusNode; + late final FocusNode passwordFocusNode; + late final TextEditingController passwordController; final bool _nextEnabled = true; DateTime _restoreFromDate = DateTime.fromMillisecondsSinceEpoch(0); late final Color baseColor; + bool hidePassword = true; + bool _expandedAdavnced = false; + + bool get supportsMnemonicPassphrase => + !(coin == Coin.monero || coin == Coin.wownero || coin == Coin.epicCash); @override void initState() { - baseColor = ref.read(colorThemeProvider.state).state.textSubtitle2; + baseColor = ref.read(themeProvider.state).state.textSubtitle2; walletName = widget.walletName; coin = widget.coin; isDesktop = Util.isDesktop; _dateController = TextEditingController(); textFieldFocusNode = FocusNode(); + passwordController = TextEditingController(); + passwordFocusNode = FocusNode(); super.initState(); } @@ -73,6 +84,8 @@ class _RestoreOptionsViewState extends ConsumerState { void dispose() { _dateController.dispose(); textFieldFocusNode.dispose(); + passwordController.dispose(); + passwordFocusNode.dispose(); super.dispose(); } @@ -134,11 +147,12 @@ class _RestoreOptionsViewState extends ConsumerState { if (mounted) { await Navigator.of(context).pushNamed( RestoreWalletView.routeName, - arguments: Tuple4( + arguments: Tuple5( walletName, coin, ref.read(mnemonicWordCountStateProvider.state).state, _restoreFromDate, + passwordController.text, ), ); } @@ -154,10 +168,44 @@ class _RestoreOptionsViewState extends ConsumerState { await Future.delayed(const Duration(milliseconds: 125)); } - final date = await datePicker.showRoundedDatePicker( + final date = await showRoundedDatePicker( context: context, initialDate: DateTime.now(), - height: height * 0.5, + height: height / 3.0, + theme: ThemeData( + primarySwatch: Util.createMaterialColor(fetchedColor), + ), + //TODO pick a better initial date + // 2007 chosen as that is just before bitcoin launched + firstDate: DateTime(2007), + lastDate: DateTime.now(), + borderRadius: Constants.size.circularBorderRadius * 2, + + textPositiveButton: "SELECT", + + styleDatePicker: _buildDatePickerStyle(), + styleYearPicker: _buildYearPickerStyle(), + ); + if (date != null) { + _restoreFromDate = date; + _dateController.text = Format.formatDate(date); + } + } + + Future chooseDesktopDate() async { + final height = MediaQuery.of(context).size.height; + final fetchedColor = + Theme.of(context).extension()!.accentColorDark; + // check and hide keyboard + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 125)); + } + + final date = await showRoundedDatePicker( + context: context, + initialDate: DateTime.now(), + height: height / 3.0, theme: ThemeData( primarySwatch: Util.createMaterialColor(fetchedColor), ), @@ -235,11 +283,10 @@ class _RestoreOptionsViewState extends ConsumerState { flex: isDesktop ? 10 : 1, ), if (!isDesktop) - Image( - image: AssetImage( - Assets.png.imageFor(coin: coin), - ), + CoinImage( + coin: coin, height: 100, + width: 100, ), SizedBox( height: isDesktop ? 0 : 16, @@ -283,15 +330,22 @@ class _RestoreOptionsViewState extends ConsumerState { (coin == Coin.wownero && ref.watch(mnemonicWordCountStateProvider.state).state == 25)) - - // if (!isDesktop) - RestoreFromDatePicker( - onTap: chooseDate, - controller: _dateController, - ), - - // if (isDesktop) - // // TODO desktop date picker + if (!isDesktop) + RestoreFromDatePicker( + onTap: chooseDate, + controller: _dateController, + ), + if (coin == Coin.monero || + coin == Coin.epicCash || + (coin == Coin.wownero && + ref.watch(mnemonicWordCountStateProvider.state).state == + 25)) + if (isDesktop) + // TODO desktop date picker + RestoreFromDatePicker( + onTap: chooseDesktopDate, + controller: _dateController, + ), if (coin == Coin.monero || coin == Coin.epicCash || (coin == Coin.wownero && @@ -400,6 +454,144 @@ class _RestoreOptionsViewState extends ConsumerState { MobileMnemonicLengthSelector( chooseMnemonicLength: chooseMnemonicLength, ), + if (supportsMnemonicPassphrase) + SizedBox( + height: isDesktop ? 24 : 16, + ), + if (supportsMnemonicPassphrase) + Expandable( + onExpandChanged: (state) { + setState(() { + _expandedAdavnced = state == ExpandableState.expanded; + }); + }, + header: Container( + color: Colors.transparent, + child: Padding( + padding: const EdgeInsets.only( + top: 8.0, + bottom: 8.0, + right: 10, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Advanced", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark3, + ) + : STextStyles.smallMed12(context), + textAlign: TextAlign.left, + ), + SvgPicture.asset( + _expandedAdavnced + ? Assets.svg.chevronUp + : Assets.svg.chevronDown, + width: 12, + height: 6, + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconRight, + ), + ], + ), + ), + ), + body: Container( + color: Colors.transparent, + child: Column( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("mnemonicPassphraseFieldKey1"), + focusNode: passwordFocusNode, + controller: passwordController, + style: isDesktop + ? STextStyles.desktopTextMedium(context) + .copyWith( + height: 2, + ) + : STextStyles.field(context), + obscureText: hidePassword, + enableSuggestions: false, + autocorrect: false, + decoration: standardInputDecoration( + "Recovery phrase password", + passwordFocusNode, + context, + ).copyWith( + suffixIcon: UnconstrainedBox( + child: ConditionalParent( + condition: isDesktop, + builder: (child) => SizedBox( + height: 70, + child: child, + ), + child: Row( + children: [ + SizedBox( + width: isDesktop ? 24 : 16, + ), + GestureDetector( + key: const Key( + "mnemonicPassphraseFieldShowPasswordButtonKey"), + onTap: () async { + setState(() { + hidePassword = !hidePassword; + }); + }, + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension()! + .textDark3, + width: isDesktop ? 24 : 16, + height: isDesktop ? 24 : 16, + ), + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + ), + ), + ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + child: Center( + child: Text( + "If the recovery phrase you are about to restore was created with an optional passphrase you can enter it here.", + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ) + : STextStyles.itemSubtitle(context), + ), + ), + ), + ], + ), + ), + ), if (!isDesktop) const Spacer( flex: 3, @@ -412,7 +604,6 @@ class _RestoreOptionsViewState extends ConsumerState { isDesktop: isDesktop, onPressed: _nextEnabled ? nextPressed : null, ), - if (isDesktop) const Spacer( flex: 15, diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/sub_widgets/mobile_mnemonic_length_selector.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/sub_widgets/mobile_mnemonic_length_selector.dart index 4f5b76fab..d0a0baa57 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/sub_widgets/mobile_mnemonic_length_selector.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/sub_widgets/mobile_mnemonic_length_selector.dart @@ -2,11 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/providers/ui/verify_recovery_phrase/mnemonic_word_count_state_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; - import 'package:stackwallet/utilities/util.dart'; class MobileMnemonicLengthSelector extends ConsumerWidget { diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/sub_widgets/restore_from_date_picker.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/sub_widgets/restore_from_date_picker.dart index 803e9b03b..2e36d25f3 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/sub_widgets/restore_from_date_picker.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/sub_widgets/restore_from_date_picker.dart @@ -1,9 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; - import 'package:stackwallet/utilities/util.dart'; class RestoreFromDatePicker extends StatefulWidget { diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/sub_widgets/restore_options_next_button.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/sub_widgets/restore_options_next_button.dart index 502502f94..9a09fef50 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/sub_widgets/restore_options_next_button.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/sub_widgets/restore_options_next_button.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; class RestoreOptionsNextButton extends StatelessWidget { const RestoreOptionsNextButton({ @@ -23,10 +23,10 @@ class RestoreOptionsNextButton extends StatelessWidget { style: onPressed != null ? Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context) + .getPrimaryEnabledButtonStyle(context) : Theme.of(context) .extension()! - .getPrimaryDisabledButtonColor(context), + .getPrimaryDisabledButtonStyle(context), child: Text( "Next", style: STextStyles.button(context), diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/sub_widgets/restore_options_platform_layout.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/sub_widgets/restore_options_platform_layout.dart index 12121cd7d..b83b2eb8e 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/sub_widgets/restore_options_platform_layout.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_options_view/sub_widgets/restore_options_platform_layout.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; class RestoreOptionsPlatformLayout extends StatelessWidget { const RestoreOptionsPlatformLayout({ diff --git a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart index 7a21a6f72..7df799a24 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart @@ -12,10 +12,13 @@ import 'package:flutter_libmonero/wownero/wownero.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart'; import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/confirm_recovery_dialog.dart'; import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/sub_widgets/restore_failed_dialog.dart'; import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/sub_widgets/restore_succeeded_dialog.dart'; import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/sub_widgets/restoring_dialog.dart'; +import 'package:stackwallet/pages/add_wallet_views/select_wallet_for_token_view.dart'; +import 'package:stackwallet/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart'; import 'package:stackwallet/pages/home_view/home_view.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart'; @@ -24,6 +27,7 @@ import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/coins/coin_service.dart'; import 'package:stackwallet/services/coins/manager.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/address_utils.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/barcode_scanner_interface.dart'; @@ -35,7 +39,6 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/form_input_status_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; @@ -54,6 +57,7 @@ class RestoreWalletView extends ConsumerStatefulWidget { required this.walletName, required this.coin, required this.seedWordsLength, + required this.mnemonicPassphrase, required this.restoreFromDate, this.barcodeScanner = const BarcodeScannerWrapper(), this.clipboard = const ClipboardWrapper(), @@ -63,6 +67,7 @@ class RestoreWalletView extends ConsumerStatefulWidget { final String walletName; final Coin coin; + final String mnemonicPassphrase; final int seedWordsLength; final DateTime restoreFromDate; @@ -290,6 +295,7 @@ class _RestoreWalletViewState extends ConsumerState { // without using them await manager.recoverFromMnemonic( mnemonic: mnemonic, + mnemonicPassphrase: widget.mnemonicPassphrase, maxUnusedAddressGap: widget.coin == Coin.firo ? 50 : 20, maxNumberOfIndexesToCheck: 1000, height: height, @@ -307,24 +313,62 @@ class _RestoreWalletViewState extends ConsumerState { .read(walletsChangeNotifierProvider.notifier) .addWallet(walletId: manager.walletId, manager: manager); - if (mounted) { - if (isDesktop) { - Navigator.of(context) - .popUntil(ModalRoute.withName(DesktopHomeView.routeName)); - } else { - unawaited(Navigator.of(context).pushNamedAndRemoveUntil( - HomeView.routeName, (route) => false)); - } + final isCreateSpecialEthWallet = + ref.read(createSpecialEthWalletRoutingFlag); + if (isCreateSpecialEthWallet) { + ref.read(createSpecialEthWalletRoutingFlag.notifier).state = + false; + ref + .read(newEthWalletTriggerTempUntilHiveCompletelyDeleted.state) + .state = + !ref + .read(newEthWalletTriggerTempUntilHiveCompletelyDeleted + .state) + .state; + } + + if (mounted) { + if (isDesktop) { + Navigator.of(context).popUntil( + ModalRoute.withName( + DesktopHomeView.routeName, + ), + ); + } else { + if (isCreateSpecialEthWallet) { + Navigator.of(context).popUntil( + ModalRoute.withName( + SelectWalletForTokenView.routeName, + ), + ); + } else { + unawaited( + Navigator.of(context).pushNamedAndRemoveUntil( + HomeView.routeName, + (route) => false, + ), + ); + if (manager.coin == Coin.ethereum) { + unawaited( + Navigator.of(context).pushNamed( + EditWalletTokensView.routeName, + arguments: manager.walletId, + ), + ); + } + } + } + + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return const RestoreSucceededDialog(); + }, + ); } - await showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) { - return const RestoreSucceededDialog(); - }, - ); if (!Platform.isLinux && !isDesktop) { await Wakelock.disable(); } @@ -373,17 +417,22 @@ class _RestoreWalletViewState extends ConsumerState { FormInputStatus status, String prefix) { Color color; Color prefixColor; + Color borderColor; Widget? suffixIcon; switch (status) { case FormInputStatus.empty: color = Theme.of(context).extension()!.textFieldDefaultBG; prefixColor = Theme.of(context).extension()!.textSubtitle2; + borderColor = + Theme.of(context).extension()!.textFieldDefaultBG; break; case FormInputStatus.invalid: color = Theme.of(context).extension()!.textFieldErrorBG; prefixColor = Theme.of(context) .extension()! .textFieldErrorSearchIconLeft; + borderColor = + Theme.of(context).extension()!.textFieldErrorBorder; suffixIcon = SvgPicture.asset( Assets.svg.alertCircle, width: 16, @@ -398,6 +447,8 @@ class _RestoreWalletViewState extends ConsumerState { prefixColor = Theme.of(context) .extension()! .textFieldSuccessSearchIconLeft; + borderColor = + Theme.of(context).extension()!.textFieldSuccessBorder; suffixIcon = SvgPicture.asset( Assets.svg.checkCircle, width: 16, @@ -449,11 +500,11 @@ class _RestoreWalletViewState extends ConsumerState { child: suffixIcon, ), ), - enabledBorder: _buildOutlineInputBorder(color), - focusedBorder: _buildOutlineInputBorder(color), - errorBorder: _buildOutlineInputBorder(color), - disabledBorder: _buildOutlineInputBorder(color), - focusedErrorBorder: _buildOutlineInputBorder(color), + enabledBorder: _buildOutlineInputBorder(borderColor), + focusedBorder: _buildOutlineInputBorder(borderColor), + errorBorder: _buildOutlineInputBorder(borderColor), + disabledBorder: _buildOutlineInputBorder(borderColor), + focusedErrorBorder: _buildOutlineInputBorder(borderColor), ); } @@ -585,6 +636,8 @@ class _RestoreWalletViewState extends ConsumerState { child: AspectRatio( aspectRatio: 1, child: AppBarIconButton( + semanticsLabel: + "View QR Code Button. Opens Camera To Scan QR Code For Restoring Wallet.", key: const Key("restoreWalletViewQrCodeButton"), size: 36, shadows: const [], @@ -611,6 +664,8 @@ class _RestoreWalletViewState extends ConsumerState { child: AspectRatio( aspectRatio: 1, child: AppBarIconButton( + semanticsLabel: + "Paste Button. Pastes From Clipboard For Restoring Wallet.", key: const Key("restoreWalletPasteButton"), size: 36, shadows: const [], @@ -736,6 +791,8 @@ class _RestoreWalletViewState extends ConsumerState { child: Column( children: [ TextFormField( + autocorrect: !isDesktop, + enableSuggestions: !isDesktop, textCapitalization: TextCapitalization.none, key: Key( @@ -784,7 +841,7 @@ class _RestoreWalletViewState extends ConsumerState { .copyWith( color: Theme.of(context) .extension()! - .overlay, + .textRestore, fontSize: isDesktop ? 16 : 14, ), ), @@ -831,6 +888,8 @@ class _RestoreWalletViewState extends ConsumerState { child: Column( children: [ TextFormField( + autocorrect: !isDesktop, + enableSuggestions: !isDesktop, textCapitalization: TextCapitalization.none, key: Key( @@ -954,6 +1013,8 @@ class _RestoreWalletViewState extends ConsumerState { padding: const EdgeInsets.symmetric(vertical: 4), child: TextFormField( + autocorrect: !isDesktop, + enableSuggestions: !isDesktop, textCapitalization: TextCapitalization.none, key: Key("restoreMnemonicFormField_$i"), @@ -987,7 +1048,7 @@ class _RestoreWalletViewState extends ConsumerState { STextStyles.field(context).copyWith( color: Theme.of(context) .extension()! - .overlay, + .textRestore, fontSize: isDesktop ? 16 : 14, ), ), diff --git a/lib/pages/add_wallet_views/restore_wallet_view/sub_widgets/mnemonic_word_count_select_sheet.dart b/lib/pages/add_wallet_views/restore_wallet_view/sub_widgets/mnemonic_word_count_select_sheet.dart index 44bdf5f99..d0f4667d8 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/sub_widgets/mnemonic_word_count_select_sheet.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/sub_widgets/mnemonic_word_count_select_sheet.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/providers/ui/verify_recovery_phrase/mnemonic_word_count_state_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; class MnemonicWordCountSelectSheet extends ConsumerWidget { const MnemonicWordCountSelectSheet({ diff --git a/lib/pages/add_wallet_views/restore_wallet_view/sub_widgets/restore_failed_dialog.dart b/lib/pages/add_wallet_views/restore_wallet_view/sub_widgets/restore_failed_dialog.dart index daf5cf1fc..1f2435158 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/sub_widgets/restore_failed_dialog.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/sub_widgets/restore_failed_dialog.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; class RestoreFailedDialog extends ConsumerStatefulWidget { @@ -47,7 +47,7 @@ class _RestoreFailedDialogState extends ConsumerState { rightButton: TextButton( style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), child: Text( "Ok", style: STextStyles.itemSubtitle12(context), diff --git a/lib/pages/add_wallet_views/restore_wallet_view/sub_widgets/restore_succeeded_dialog.dart b/lib/pages/add_wallet_views/restore_wallet_view/sub_widgets/restore_succeeded_dialog.dart index 51bb8f2d7..5a4fb8282 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/sub_widgets/restore_succeeded_dialog.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/sub_widgets/restore_succeeded_dialog.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; @@ -80,7 +80,7 @@ class RestoreSucceededDialog extends StatelessWidget { rightButton: TextButton( style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), child: Text( "Ok", style: STextStyles.itemSubtitle12(context), diff --git a/lib/pages/add_wallet_views/restore_wallet_view/sub_widgets/restoring_dialog.dart b/lib/pages/add_wallet_views/restore_wallet_view/sub_widgets/restoring_dialog.dart index 001fff6c0..108f64c24 100644 --- a/lib/pages/add_wallet_views/restore_wallet_view/sub_widgets/restoring_dialog.dart +++ b/lib/pages/add_wallet_views/restore_wallet_view/sub_widgets/restoring_dialog.dart @@ -1,9 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/animated_widgets/rotating_arrows.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; @@ -21,37 +20,15 @@ class RestoringDialog extends StatefulWidget { State createState() => _RestoringDialogState(); } -class _RestoringDialogState extends State - with TickerProviderStateMixin { - late AnimationController? _spinController; - late Animation _spinAnimation; - +class _RestoringDialogState extends State { late final Future Function() onCancel; @override void initState() { onCancel = widget.onCancel; - _spinController = AnimationController( - duration: const Duration(seconds: 2), - vsync: this, - )..repeat(); - - _spinAnimation = CurvedAnimation( - parent: _spinController!, - curve: Curves.linear, - ); - super.initState(); } - @override - void dispose() { - _spinController?.dispose(); - _spinController = null; - - super.dispose(); - } - @override Widget build(BuildContext context) { if (Util.isDesktop) { @@ -69,14 +46,9 @@ class _RestoringDialogState extends State const Spacer( flex: 1, ), - RotationTransition( - turns: _spinAnimation, - child: SvgPicture.asset(Assets.svg.arrowRotate3, - width: 40, - height: 40, - color: Theme.of(context) - .extension()! - .accentColorDark), + const RotatingArrows( + width: 40, + height: 40, ), const Spacer( flex: 2, @@ -127,19 +99,14 @@ class _RestoringDialogState extends State child: StackDialog( title: "Restoring wallet", message: "This may take a while. Please do not exit this screen.", - icon: RotationTransition( - turns: _spinAnimation, - child: SvgPicture.asset(Assets.svg.arrowRotate3, - width: 24, - height: 24, - color: Theme.of(context) - .extension()! - .accentColorDark), + icon: const RotatingArrows( + width: 24, + height: 24, ), rightButton: TextButton( style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), child: Text( "Cancel", style: STextStyles.itemSubtitle12(context), diff --git a/lib/pages/add_wallet_views/select_wallet_for_token_view.dart b/lib/pages/add_wallet_views/select_wallet_for_token_view.dart new file mode 100644 index 000000000..3a22dcbfa --- /dev/null +++ b/lib/pages/add_wallet_views/select_wallet_for_token_view.dart @@ -0,0 +1,286 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/db/hive/db.dart'; +import 'package:stackwallet/models/add_wallet_list_entity/sub_classes/coin_entity.dart'; +import 'package:stackwallet/models/add_wallet_list_entity/sub_classes/eth_token_entity.dart'; +import 'package:stackwallet/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart'; +import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart'; +import 'package:stackwallet/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart'; +import 'package:stackwallet/providers/global/wallets_service_provider.dart'; +import 'package:stackwallet/services/wallets_service.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; +import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/eth_wallet_radio.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/wallet_info_row/wallet_info_row.dart'; +import 'package:tuple/tuple.dart'; + +final newEthWalletTriggerTempUntilHiveCompletelyDeleted = + StateProvider((ref) => false); + +class SelectWalletForTokenView extends ConsumerStatefulWidget { + const SelectWalletForTokenView({ + Key? key, + required this.entity, + }) : super(key: key); + + static const String routeName = "/selectWalletForTokenView"; + + final EthTokenEntity entity; + + @override + ConsumerState createState() => + _SelectWalletForTokenViewState(); +} + +class _SelectWalletForTokenViewState + extends ConsumerState { + final isDesktop = Util.isDesktop; + late final List ethWalletIds; + bool _hasEthWallets = false; + + String? _selectedWalletId; + + void _onContinue() { + Navigator.of(context).pushNamed( + EditWalletTokensView.routeName, + arguments: Tuple2( + _selectedWalletId!, + [widget.entity.token.address], + ), + ); + } + + void _onAddNewEthWallet() { + ref.read(createSpecialEthWalletRoutingFlag.notifier).state = true; + Navigator.of(context).pushNamed( + CreateOrRestoreWalletView.routeName, + arguments: CoinEntity(widget.entity.coin), + ); + } + + late int _cachedWalletCount; + + void _updateWalletsList(Map walletsData) { + _cachedWalletCount = walletsData.length; + + walletsData.removeWhere((key, value) => value.coin != widget.entity.coin); + ethWalletIds.clear(); + + _hasEthWallets = walletsData.isNotEmpty; + + // TODO: proper wallet data class instead of this Hive silliness + for (final walletId in walletsData.values.map((e) => e.walletId).toList()) { + final walletContracts = DB.instance.get( + boxName: walletId, + key: DBKeys.ethTokenContracts, + ) as List? ?? + []; + if (!walletContracts.contains(widget.entity.token.address)) { + ethWalletIds.add(walletId); + } + } + } + + @override + void initState() { + ethWalletIds = []; + + final walletsData = + ref.read(walletsServiceChangeNotifierProvider).fetchWalletsData(); + _updateWalletsList(walletsData); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + // dumb hack + ref.watch(newEthWalletTriggerTempUntilHiveCompletelyDeleted); + final walletsData = + ref.read(walletsServiceChangeNotifierProvider).fetchWalletsData(); + if (walletsData.length != _cachedWalletCount) { + _updateWalletsList(walletsData); + } + + return WillPopScope( + onWillPop: () async { + ref.read(createSpecialEthWalletRoutingFlag.notifier).state = false; + return true; + }, + child: ConditionalParent( + condition: !isDesktop, + builder: (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(16), + child: child, + ), + + // child: LayoutBuilder( + // builder: (ctx, constraints) { + // return SingleChildScrollView( + // child: ConstrainedBox( + // constraints: + // BoxConstraints(minHeight: constraints.maxHeight), + // child: IntrinsicHeight( + // child: child, + // ), + // ), + // ); + // }, + // ), + ), + ), + ), + child: ConditionalParent( + condition: isDesktop, + builder: (child) => DesktopScaffold( + appBar: const DesktopAppBar( + isCompactHeight: false, + leading: AppBarBackButton(), + ), + body: SizedBox( + width: 500, + child: child, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (isDesktop) + const SizedBox( + height: 24, + ), + Text( + "Select Ethereum wallet", + textAlign: TextAlign.center, + style: isDesktop + ? STextStyles.desktopH2(context) + : STextStyles.pageTitleH1(context), + ), + SizedBox( + height: isDesktop ? 16 : 8, + ), + Text( + "You are adding an ETH token.", + textAlign: TextAlign.center, + style: isDesktop + ? STextStyles.desktopSubtitleH2(context) + : STextStyles.subtitle(context), + ), + const SizedBox( + height: 8, + ), + Text( + "You must choose an Ethereum wallet in order to use ${widget.entity.name}", + textAlign: TextAlign.center, + style: isDesktop + ? STextStyles.desktopSubtitleH2(context) + : STextStyles.subtitle(context), + ), + SizedBox( + height: isDesktop ? 60 : 16, + ), + ethWalletIds.isEmpty + ? RoundedWhiteContainer( + padding: EdgeInsets.all(isDesktop ? 16 : 12), + child: Text( + _hasEthWallets + ? "All current Ethereum wallets already have ${widget.entity.name}" + : "You do not have any Ethereum wallets", + style: isDesktop + ? STextStyles.desktopSubtitleH2(context) + : STextStyles.label(context), + ), + ) + : ConditionalParent( + condition: !isDesktop, + builder: (child) => Expanded( + child: Column( + children: [ + RoundedWhiteContainer( + padding: const EdgeInsets.all(8), + child: child, + ), + ], + ), + ), + child: ListView.separated( + itemCount: ethWalletIds.length, + shrinkWrap: true, + separatorBuilder: (_, __) => SizedBox( + height: isDesktop ? 12 : 6, + ), + itemBuilder: (_, index) { + return RoundedContainer( + padding: EdgeInsets.all(isDesktop ? 16 : 8), + onPressed: () { + setState(() { + _selectedWalletId = ethWalletIds[index]; + }); + }, + color: isDesktop + ? Theme.of(context) + .extension()! + .popupBG + : _selectedWalletId == ethWalletIds[index] + ? Theme.of(context) + .extension()! + .highlight + : Colors.transparent, + child: isDesktop + ? EthWalletRadio( + walletId: ethWalletIds[index], + selectedWalletId: _selectedWalletId, + ) + : WalletInfoRow( + walletId: ethWalletIds[index], + ), + ); + }, + ), + ), + if (ethWalletIds.isEmpty || isDesktop) + const SizedBox( + height: 16, + ), + if (isDesktop) + const SizedBox( + height: 16, + ), + ethWalletIds.isEmpty + ? PrimaryButton( + label: "Add new Ethereum wallet", + onPressed: _onAddNewEthWallet, + ) + : PrimaryButton( + label: "Continue", + enabled: _selectedWalletId != null, + onPressed: _onContinue, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/add_wallet_views/verify_recovery_phrase_view/sub_widgets/word_table_item.dart b/lib/pages/add_wallet_views/verify_recovery_phrase_view/sub_widgets/word_table_item.dart index 9f8f43b59..81d4bb850 100644 --- a/lib/pages/add_wallet_views/verify_recovery_phrase_view/sub_widgets/word_table_item.dart +++ b/lib/pages/add_wallet_views/verify_recovery_phrase_view/sub_widgets/word_table_item.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; class WordTableItem extends ConsumerWidget { const WordTableItem({ diff --git a/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart b/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart index 0ce1fac26..79de8f879 100644 --- a/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart +++ b/lib/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart @@ -1,26 +1,32 @@ import 'dart:async'; import 'dart:math'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart'; import 'package:stackwallet/pages/add_wallet_views/new_wallet_recovery_phrase_view/new_wallet_recovery_phrase_view.dart'; +import 'package:stackwallet/pages/add_wallet_views/select_wallet_for_token_view.dart'; import 'package:stackwallet/pages/add_wallet_views/verify_recovery_phrase_view/sub_widgets/word_table.dart'; import 'package:stackwallet/pages/home_view/home_view.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/coins/manager.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; import 'package:tuple/tuple.dart'; +final createSpecialEthWalletRoutingFlag = StateProvider((ref) => false); + class VerifyRecoveryPhraseView extends ConsumerStatefulWidget { const VerifyRecoveryPhraseView({ Key? key, @@ -92,29 +98,75 @@ class _VerifyRecoveryPhraseViewState .read(walletsChangeNotifierProvider.notifier) .addWallet(walletId: _manager.walletId, manager: _manager); - if (mounted) { - if (isDesktop) { - Navigator.of(context).popUntil( - ModalRoute.withName( - DesktopHomeView.routeName, - ), - ); - } else { - unawaited( - Navigator.of(context).pushNamedAndRemoveUntil( - HomeView.routeName, - (route) => false, - ), - ); - } + final isCreateSpecialEthWallet = + ref.read(createSpecialEthWalletRoutingFlag); + if (isCreateSpecialEthWallet) { + ref.read(createSpecialEthWalletRoutingFlag.notifier).state = false; + ref + .read(newEthWalletTriggerTempUntilHiveCompletelyDeleted.state) + .state = + !ref + .read(newEthWalletTriggerTempUntilHiveCompletelyDeleted.state) + .state; } - unawaited(showFloatingFlushBar( - type: FlushBarType.success, - message: "Correct! Your wallet is set up.", - iconAsset: Assets.svg.check, - context: context, - )); + if (mounted) { + if (isDesktop) { + if (isCreateSpecialEthWallet) { + Navigator.of(context).popUntil( + ModalRoute.withName( + SelectWalletForTokenView.routeName, + ), + ); + } else { + Navigator.of(context).popUntil( + ModalRoute.withName( + DesktopHomeView.routeName, + ), + ); + if (widget.manager.coin == Coin.ethereum) { + unawaited( + Navigator.of(context).pushNamed( + EditWalletTokensView.routeName, + arguments: widget.manager.walletId, + ), + ); + } + } + } else { + if (isCreateSpecialEthWallet) { + Navigator.of(context).popUntil( + ModalRoute.withName( + SelectWalletForTokenView.routeName, + ), + ); + } else { + unawaited( + Navigator.of(context).pushNamedAndRemoveUntil( + HomeView.routeName, + (route) => false, + ), + ); + if (widget.manager.coin == Coin.ethereum) { + unawaited( + Navigator.of(context).pushNamed( + EditWalletTokensView.routeName, + arguments: widget.manager.walletId, + ), + ); + } + } + } + + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "Correct! Your wallet is set up.", + iconAsset: Assets.svg.check, + context: context, + ), + ); + } } else { unawaited(showFloatingFlushBar( type: FlushBarType.warning, @@ -160,8 +212,9 @@ class _VerifyRecoveryPhraseViewState result.insert(random.nextInt(wordsToShow), chosenWord); - //todo: this prints sensitive info - // debugPrint("Mnemonic game correct word: $chosenWord"); + if (kDebugMode) { + print("Mnemonic game correct word: $chosenWord"); + } return Tuple2(result, chosenWord); } @@ -329,10 +382,10 @@ class _VerifyRecoveryPhraseViewState style: selectedWord.isNotEmpty ? Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context) + .getPrimaryEnabledButtonStyle(context) : Theme.of(context) .extension()! - .getPrimaryDisabledButtonColor(context), + .getPrimaryDisabledButtonStyle(context), child: isDesktop ? Text( "Verify", diff --git a/lib/pages/address_book_views/address_book_view.dart b/lib/pages/address_book_views/address_book_view.dart index 0fbf334a7..3faaad48e 100644 --- a/lib/pages/address_book_views/address_book_view.dart +++ b/lib/pages/address_book_views/address_book_view.dart @@ -1,18 +1,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/models/contact.dart'; -import 'package:stackwallet/models/contact_address_entry.dart'; +import 'package:stackwallet/models/isar/models/contact_entry.dart'; import 'package:stackwallet/pages/address_book_views/subviews/add_address_book_entry_view.dart'; import 'package:stackwallet/pages/address_book_views/subviews/address_book_filter_view.dart'; import 'package:stackwallet/providers/global/address_book_service_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/ui/address_book_providers/address_book_filter_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/address_book_card.dart'; import 'package:stackwallet/widgets/background.dart'; @@ -73,19 +72,18 @@ class _AddressBookViewState extends ConsumerState { final managers = ref.read(walletsChangeNotifierProvider).managers; for (final manager in managers) { addresses.add( - ContactAddressEntry( - coin: manager.coin, - address: await manager.currentReceivingAddress, - label: "Current Receiving", - other: manager.walletName, - ), + ContactAddressEntry() + ..coinName = manager.coin.name + ..address = await manager.currentReceivingAddress + ..label = "Current Receiving" + ..other = manager.walletName, ); } - final self = Contact( + final self = ContactEntry( name: "My Stack", addresses: addresses, isFavorite: true, - id: "default", + customId: "default", ); await ref.read(addressBookServiceProvider).editContact(self); }); @@ -306,8 +304,8 @@ class _AddressBookViewState extends ConsumerState { .where((element) => element.isFavorite) .map( (e) => AddressBookCard( - key: Key("favContactCard_${e.id}_key"), - contactId: e.id, + key: Key("favContactCard_${e.customId}_key"), + contactId: e.customId, ), ), ], @@ -352,8 +350,9 @@ class _AddressBookViewState extends ConsumerState { .matches(widget.filterTerm ?? _searchTerm, e)) .map( (e) => AddressBookCard( - key: Key("desktopContactCard_${e.id}_key"), - contactId: e.id, + key: + Key("desktopContactCard_${e.customId}_key"), + contactId: e.customId, ), ), ], diff --git a/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart b/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart index 36191e0b7..74e312da4 100644 --- a/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart +++ b/lib/pages/address_book_views/subviews/add_address_book_entry_view.dart @@ -2,19 +2,18 @@ import 'package:emojis/emoji.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:stackwallet/models/contact.dart'; -import 'package:stackwallet/models/contact_address_entry.dart'; +import 'package:stackwallet/models/isar/models/contact_entry.dart'; import 'package:stackwallet/pages/address_book_views/subviews/new_contact_address_entry_form.dart'; import 'package:stackwallet/providers/global/address_book_service_provider.dart'; import 'package:stackwallet/providers/ui/address_book_providers/address_entry_data_provider.dart'; import 'package:stackwallet/providers/ui/address_book_providers/contact_name_is_not_empty_state_provider.dart'; import 'package:stackwallet/providers/ui/address_book_providers/valid_contact_state_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/barcode_scanner_interface.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; @@ -28,6 +27,7 @@ import 'package:stackwallet/widgets/emoji_select_sheet.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; +import 'package:uuid/uuid.dart'; class AddAddressBookEntryView extends ConsumerStatefulWidget { const AddAddressBookEntryView({ @@ -584,7 +584,7 @@ class _AddAddressBookEntryViewState "Address ${i + 1}", style: STextStyles.smallMed12(context), ), - BlueTextButton( + CustomTextButton( onTap: () { _removeForm(forms[i].id); }, @@ -601,7 +601,7 @@ class _AddAddressBookEntryViewState const SizedBox( height: 16, ), - BlueTextButton( + CustomTextButton( onTap: () { _addForm(); scrollController.animateTo( @@ -688,11 +688,12 @@ class _AddAddressBookEntryViewState forms[i].id)) .buildAddressEntry()); } - Contact contact = Contact( + ContactEntry contact = ContactEntry( emojiChar: _selectedEmoji?.char, name: nameController.text, addresses: entries, isFavorite: _isFavorite, + customId: const Uuid().v1(), ); if (await ref diff --git a/lib/pages/address_book_views/subviews/add_new_contact_address_view.dart b/lib/pages/address_book_views/subviews/add_new_contact_address_view.dart index de2dfe90c..f01829ab2 100644 --- a/lib/pages/address_book_views/subviews/add_new_contact_address_view.dart +++ b/lib/pages/address_book_views/subviews/add_new_contact_address_view.dart @@ -1,17 +1,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/models/contact.dart'; -import 'package:stackwallet/models/contact_address_entry.dart'; +import 'package:stackwallet/models/isar/models/contact_entry.dart'; import 'package:stackwallet/pages/address_book_views/subviews/new_contact_address_entry_form.dart'; import 'package:stackwallet/providers/global/address_book_service_provider.dart'; import 'package:stackwallet/providers/ui/address_book_providers/address_entry_data_provider.dart'; import 'package:stackwallet/providers/ui/address_book_providers/valid_contact_state_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/barcode_scanner_interface.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; @@ -208,7 +207,7 @@ class _AddNewContactAddressViewState .read(addressEntryDataProvider(0)) .buildAddressEntry()); - Contact editedContact = + ContactEntry editedContact = contact.copyWith(addresses: entries); if (await ref diff --git a/lib/pages/address_book_views/subviews/address_book_filter_view.dart b/lib/pages/address_book_views/subviews/address_book_filter_view.dart index 9f410aae7..b98dc5fc3 100644 --- a/lib/pages/address_book_views/subviews/address_book_filter_view.dart +++ b/lib/pages/address_book_views/subviews/address_book_filter_view.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/providers/ui/address_book_providers/address_book_filter_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; diff --git a/lib/pages/address_book_views/subviews/coin_select_sheet.dart b/lib/pages/address_book_views/subviews/coin_select_sheet.dart index 7be2f8739..d343dbdbb 100644 --- a/lib/pages/address_book_views/subviews/coin_select_sheet.dart +++ b/lib/pages/address_book_views/subviews/coin_select_sheet.dart @@ -1,12 +1,14 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; -import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/themes/coin_image_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; class CoinSelectSheet extends StatelessWidget { const CoinSelectSheet({Key? key}) : super(key: key); @@ -87,8 +89,10 @@ class CoinSelectSheet extends StatelessWidget { padding: const EdgeInsets.all(12), child: Row( children: [ - SvgPicture.asset( - Assets.svg.iconFor(coin: coin), + SvgPicture.file( + File( + ref.watch(coinImageProvider(coin)), + ), height: 20, width: 20, ), diff --git a/lib/pages/address_book_views/subviews/contact_details_view.dart b/lib/pages/address_book_views/subviews/contact_details_view.dart index 0ab6ebba9..b7d54ea71 100644 --- a/lib/pages/address_book_views/subviews/contact_details_view.dart +++ b/lib/pages/address_book_views/subviews/contact_details_view.dart @@ -1,9 +1,12 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/models/contact.dart'; -import 'package:stackwallet/models/paymint/transactions_model.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/address_book_views/subviews/add_new_contact_address_view.dart'; import 'package:stackwallet/pages/address_book_views/subviews/edit_contact_address_view.dart'; @@ -12,12 +15,12 @@ import 'package:stackwallet/providers/global/address_book_service_provider.dart' import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/ui/address_book_providers/address_entry_data_provider.dart'; import 'package:stackwallet/services/coins/manager.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; @@ -50,15 +53,6 @@ class _ContactDetailsViewState extends ConsumerState { List> _cachedTransactions = []; - bool _contactHasAddress(String address, Contact contact) { - for (final entry in contact.addresses) { - if (entry.address == address) { - return true; - } - } - return false; - } - Future>> _filteredTransactionsByContact( List managers, ) async { @@ -69,18 +63,18 @@ class _ContactDetailsViewState extends ConsumerState { List> result = []; for (final manager in managers) { - final transactions = (await manager.transactionData) - .getAllTransactions() - .values - .toList() - .where((e) => _contactHasAddress(e.address, contact)); + final transactions = await MainDB.instance + .getTransactions(manager.walletId) + .filter() + .anyOf(contact.addresses.map((e) => e.address), + (q, String e) => q.address((q) => q.valueEqualTo(e))) + .sortByTimestampDesc() + .findAll(); for (final tx in transactions) { result.add(Tuple2(manager.walletId, tx)); } } - // sort by date - result.sort((a, b) => b.item2.timestamp - a.item2.timestamp); return result; } @@ -185,7 +179,7 @@ class _ContactDetailsViewState extends ConsumerState { leftButton: TextButton( style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), child: Text( "Cancel", style: STextStyles.itemSubtitle12(context), @@ -197,7 +191,7 @@ class _ContactDetailsViewState extends ConsumerState { rightButton: TextButton( style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), child: Text( "Delete", style: STextStyles.button(context), @@ -205,7 +199,7 @@ class _ContactDetailsViewState extends ConsumerState { onPressed: () { ref .read(addressBookServiceProvider) - .removeContact(_contact.id); + .removeContact(_contact.customId); Navigator.of(context).pop(); Navigator.of(context).pop(); showFloatingFlushBar( @@ -276,12 +270,12 @@ class _ContactDetailsViewState extends ConsumerState { onPressed: () { Navigator.of(context).pushNamed( EditContactNameEmojiView.routeName, - arguments: _contact.id, + arguments: _contact.customId, ); }, style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context)! + .getSecondaryEnabledButtonStyle(context)! .copyWith( minimumSize: MaterialStateProperty.all( const Size(46, 32)), @@ -319,12 +313,12 @@ class _ContactDetailsViewState extends ConsumerState { "Addresses", style: STextStyles.itemSubtitle(context), ), - BlueTextButton( + CustomTextButton( text: "Add new", onTap: () { Navigator.of(context).pushNamed( AddNewContactAddressView.routeName, - arguments: _contact.id, + arguments: _contact.customId, ); }, ), @@ -342,8 +336,10 @@ class _ContactDetailsViewState extends ConsumerState { padding: const EdgeInsets.all(12), child: Row( children: [ - SvgPicture.asset( - Assets.svg.iconFor(coin: e.coin), + SvgPicture.file( + File( + ref.watch(coinIconProvider(e.coin)), + ), height: 24, ), const SizedBox( @@ -389,7 +385,7 @@ class _ContactDetailsViewState extends ConsumerState { Navigator.of(context).pushNamed( EditContactAddressView.routeName, - arguments: Tuple2(_contact.id, e), + arguments: Tuple2(_contact.customId, e), ); }, child: RoundedContainer( diff --git a/lib/pages/address_book_views/subviews/contact_popup.dart b/lib/pages/address_book_views/subviews/contact_popup.dart index 67ab32cda..fafebfcb1 100644 --- a/lib/pages/address_book_views/subviews/contact_popup.dart +++ b/lib/pages/address_book_views/subviews/contact_popup.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -7,15 +9,15 @@ import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/address_book_views/subviews/contact_details_view.dart'; import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_2_view.dart'; import 'package:stackwallet/pages/send_view/send_view.dart'; -import 'package:stackwallet/providers/exchange/exchange_flow_is_active_state_provider.dart'; import 'package:stackwallet/providers/global/address_book_service_provider.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:tuple/tuple.dart'; @@ -108,10 +110,17 @@ class ContactPopUp extends ConsumerWidget { .textFieldDefaultBG, borderRadius: BorderRadius.circular(32), ), - child: contact.id == "default" + child: contact.customId == "default" ? Center( - child: SvgPicture.asset( - Assets.svg.stackIcon(context), + child: SvgPicture.file( + File( + ref.watch( + themeProvider.select( + (value) => value + .assets.stackIcon, + ), + ), + ), width: 20, ), ) @@ -137,18 +146,18 @@ class ContactPopUp extends ConsumerWidget { STextStyles.itemSubtitle12(context), ), ), - if (contact.id != "default") + if (contact.customId != "default") TextButton( onPressed: () { Navigator.pop(context); Navigator.of(context).pushNamed( ContactDetailsView.routeName, - arguments: contact.id, + arguments: contact.customId, ); }, style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor( + .getSecondaryEnabledButtonStyle( context)! .copyWith( minimumSize: @@ -167,7 +176,7 @@ class ContactPopUp extends ConsumerWidget { ), ), SizedBox( - height: contact.id == "default" ? 16 : 8, + height: contact.customId == "default" ? 16 : 8, ), Container( height: 1, @@ -206,8 +215,12 @@ class ContactPopUp extends ConsumerWidget { const SizedBox( height: 2, ), - SvgPicture.asset( - Assets.svg.iconFor(coin: e.coin), + SvgPicture.file( + File( + ref.watch( + coinIconProvider(e.coin), + ), + ), height: 24, ), ], @@ -220,14 +233,14 @@ class ContactPopUp extends ConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (contact.id == "default") + if (contact.customId == "default") Text( e.other!, style: STextStyles.itemSubtitle12( context), ), - if (contact.id != "default") + if (contact.customId != "default") Text( "${e.label} (${e.coin.ticker})", style: @@ -323,13 +336,13 @@ class ContactPopUp extends ConsumerWidget { ), ], ), - if (contact.id != "default" && + if (contact.customId != "default" && hasActiveWallet && !isExchangeFlow) const SizedBox( width: 4, ), - if (contact.id != "default" && + if (contact.customId != "default" && hasActiveWallet && !isExchangeFlow) Column( diff --git a/lib/pages/address_book_views/subviews/edit_contact_address_view.dart b/lib/pages/address_book_views/subviews/edit_contact_address_view.dart index 0454903c3..4fd98ff88 100644 --- a/lib/pages/address_book_views/subviews/edit_contact_address_view.dart +++ b/lib/pages/address_book_views/subviews/edit_contact_address_view.dart @@ -1,17 +1,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/models/contact.dart'; -import 'package:stackwallet/models/contact_address_entry.dart'; +import 'package:stackwallet/models/isar/models/contact_entry.dart'; import 'package:stackwallet/pages/address_book_views/subviews/new_contact_address_entry_form.dart'; import 'package:stackwallet/providers/global/address_book_service_provider.dart'; import 'package:stackwallet/providers/ui/address_book_providers/address_entry_data_provider.dart'; import 'package:stackwallet/providers/ui/address_book_providers/valid_contact_state_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/barcode_scanner_interface.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; @@ -49,7 +48,7 @@ class _EditContactAddressViewState late final BarcodeScannerInterface barcodeScanner; late final ClipboardInterface clipboard; - Future save(Contact contact) async { + Future save(ContactEntry contact) async { if (FocusScope.of(context).hasFocus) { FocusScope.of(context).unfocus(); await Future.delayed( @@ -73,7 +72,7 @@ class _EditContactAddressViewState entries.insert(index, editedEntry); - Contact editedContact = contact.copyWith(addresses: entries); + ContactEntry editedContact = contact.copyWith(addresses: entries); if (await ref.read(addressBookServiceProvider).editContact(editedContact)) { if (mounted) { @@ -226,7 +225,8 @@ class _EditContactAddressViewState ); _addresses.remove(entry); - Contact editedContact = contact.copyWith(addresses: _addresses); + ContactEntry editedContact = + contact.copyWith(addresses: _addresses); if (await ref .read(addressBookServiceProvider) .editContact(editedContact)) { diff --git a/lib/pages/address_book_views/subviews/edit_contact_name_emoji_view.dart b/lib/pages/address_book_views/subviews/edit_contact_name_emoji_view.dart index 99638ad2c..12b4043f2 100644 --- a/lib/pages/address_book_views/subviews/edit_contact_name_emoji_view.dart +++ b/lib/pages/address_book_views/subviews/edit_contact_name_emoji_view.dart @@ -5,10 +5,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/providers/global/address_book_service_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; diff --git a/lib/pages/address_book_views/subviews/new_contact_address_entry_form.dart b/lib/pages/address_book_views/subviews/new_contact_address_entry_form.dart index 25cff073b..cf315130c 100644 --- a/lib/pages/address_book_views/subviews/new_contact_address_entry_form.dart +++ b/lib/pages/address_book_views/subviews/new_contact_address_entry_form.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -7,6 +9,8 @@ import 'package:stackwallet/pages/address_book_views/subviews/coin_select_sheet. import 'package:stackwallet/providers/providers.dart'; // import 'package:stackwallet/providers/global/should_show_lockscreen_on_resume_state_provider.dart'; import 'package:stackwallet/providers/ui/address_book_providers/address_entry_data_provider.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/address_utils.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/barcode_scanner_interface.dart'; @@ -15,7 +19,6 @@ import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart'; @@ -140,8 +143,10 @@ class _NewContactAddressEntryFormState padding: const EdgeInsets.symmetric(vertical: 4), child: Row( children: [ - SvgPicture.asset( - Assets.svg.iconFor(coin: coin), + SvgPicture.file( + File( + ref.watch(coinIconProvider(coin)), + ), height: 24, width: 24, ), @@ -210,12 +215,19 @@ class _NewContactAddressEntryFormState ) : Row( children: [ - SvgPicture.asset( - Assets.svg.iconFor( - coin: ref.watch( + SvgPicture.file( + File( + ref.watch( + coinIconProvider( + ref.watch( addressEntryDataProvider(widget.id) .select( - (value) => value.coin))!), + (value) => value.coin, + ), + )!, + ), + ), + ), height: 20, width: 20, ), diff --git a/lib/pages/buy_view/buy_form.dart b/lib/pages/buy_view/buy_form.dart new file mode 100644 index 000000000..1a31077e2 --- /dev/null +++ b/lib/pages/buy_view/buy_form.dart @@ -0,0 +1,1472 @@ +import 'dart:async'; + +import 'package:decimal/decimal.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:intl/intl.dart'; +import 'package:stackwallet/models/buy/response_objects/crypto.dart'; +import 'package:stackwallet/models/buy/response_objects/fiat.dart'; +import 'package:stackwallet/models/buy/response_objects/quote.dart'; +import 'package:stackwallet/models/contact_address_entry.dart'; +import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; +import 'package:stackwallet/pages/address_book_views/address_book_view.dart'; +import 'package:stackwallet/pages/buy_view/buy_quote_preview.dart'; +import 'package:stackwallet/pages/buy_view/sub_widgets/crypto_selection_view.dart'; +import 'package:stackwallet/pages/buy_view/sub_widgets/fiat_selection_view.dart'; +import 'package:stackwallet/pages/exchange_view/choose_from_stack_view.dart'; +import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/services/buy/buy_response.dart'; +import 'package:stackwallet/services/buy/simplex/simplex_api.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/address_utils.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/barcode_scanner_interface.dart'; +import 'package:stackwallet/utilities/clipboard_interface.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; +import 'package:stackwallet/widgets/custom_loading_overlay.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; + +class BuyForm extends ConsumerStatefulWidget { + const BuyForm({ + Key? key, + this.coin, + this.tokenContract, + this.clipboard = const ClipboardWrapper(), + this.scanner = const BarcodeScannerWrapper(), + }) : super(key: key); + + final Coin? coin; + + final ClipboardInterface clipboard; + final BarcodeScannerInterface scanner; + final EthContract? tokenContract; + + @override + ConsumerState createState() => _BuyFormState(); +} + +class _BuyFormState extends ConsumerState { + late final Coin? coin; + + late final ClipboardInterface clipboard; + late final BarcodeScannerInterface scanner; + + late final TextEditingController _receiveAddressController; + late final TextEditingController _buyAmountController; + final FocusNode _receiveAddressFocusNode = FocusNode(); + final FocusNode _fiatFocusNode = FocusNode(); + final FocusNode _cryptoFocusNode = FocusNode(); + final FocusNode _buyAmountFocusNode = FocusNode(); + + final isDesktop = Util.isDesktop; + + List? coins; + List? fiats; + String? _address; + + static Fiat? selectedFiat; + static Crypto? selectedCrypto; + SimplexQuote quote = SimplexQuote( + crypto: Crypto.fromJson({'ticker': 'BTC', 'name': 'Bitcoin'}), + fiat: Fiat.fromJson({'ticker': 'USD', 'name': 'United States Dollar'}), + youPayFiatPrice: Decimal.parse("100"), + youReceiveCryptoAmount: Decimal.parse("1.0238917"), + id: "someID", + receivingAddress: '', + buyWithFiat: true, + ); // TODO enum this or something + + static bool buyWithFiat = true; + bool _addressToggleFlag = false; + bool _hovering1 = false; + bool _hovering2 = false; + + // TODO actually check USD min and max, these could get updated by Simplex + static Decimal minFiat = Decimal.fromInt(50); + static Decimal maxFiat = Decimal.fromInt(20000); + + // // We can't get crypto min and max without asking for a quote + // static Decimal minCrypto = Decimal.parse((0.00000001) + // .toString()); // lol how to go from double->Decimal more easily? + // static Decimal maxCrypto = Decimal.parse((10000.00000000).toString()); + // static String boundedCryptoTicker = ''; + + String _amountOutOfRangeErrorString = ""; + void validateAmount() { + if (_buyAmountController.text.isEmpty) { + setState(() { + _amountOutOfRangeErrorString = ""; + }); + return; + } + + final value = Decimal.tryParse(_buyAmountController.text); + if (value == null) { + setState(() { + _amountOutOfRangeErrorString = "Invalid amount"; + }); + } else if (value > maxFiat && buyWithFiat) { + setState(() { + _amountOutOfRangeErrorString = + "Maximum amount: ${maxFiat.toStringAsFixed(2)}"; + }); + } else if (value < minFiat && buyWithFiat) { + setState(() { + _amountOutOfRangeErrorString = + "Minimum amount: ${minFiat.toStringAsFixed(2)}"; + }); + } else { + setState(() { + _amountOutOfRangeErrorString = ""; + }); + } + } + + void selectCrypto() async { + if (ref.read(simplexProvider).supportedCryptos.isEmpty) { + bool shouldPop = false; + unawaited( + showDialog( + context: context, + builder: (context) => WillPopScope( + child: const CustomLoadingOverlay( + message: "Loading currency data", + eventBus: null, + ), + onWillPop: () async => shouldPop, + ), + ), + ); + await _loadSimplexCryptos(); + shouldPop = true; + if (mounted) { + Navigator.of(context, rootNavigator: isDesktop).pop(); + } + } + + await _showFloatingCryptoSelectionSheet( + coins: ref.read(simplexProvider).supportedCryptos, + onSelected: (crypto) { + setState(() { + // if (selectedCrypto?.ticker != _BuyFormState.boundedCryptoTicker) { + // // Reset crypto mins and maxes ... we don't know these bounds until we request a quote + // _BuyFormState.minCrypto = Decimal.parse((0.00000001) + // .toString()); // lol how to go from double->Decimal more easily? + // _BuyFormState.maxCrypto = + // Decimal.parse((10000.00000000).toString()); + // } + selectedCrypto = crypto; + }); + }, + ); + } + + Future _showFloatingCryptoSelectionSheet({ + required List coins, + required void Function(Crypto) onSelected, + }) async { + _fiatFocusNode.unfocus(); + _cryptoFocusNode.unfocus(); + + final result = isDesktop + ? await showDialog( + context: context, + builder: (context) { + return DesktopDialog( + maxHeight: 700, + maxWidth: 580, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Choose a crypto to buy", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: Row( + children: [ + Expanded( + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(16), + borderColor: Theme.of(context) + .extension()! + .background, + child: CryptoSelectionView( + coins: coins, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ); + }) + : await Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => CryptoSelectionView( + coins: coins, + ), + ), + ); + + if (mounted && result is Crypto) { + onSelected(result); + } + } + + Future selectFiat() async { + if (ref.read(simplexProvider).supportedFiats.isEmpty) { + bool shouldPop = false; + unawaited( + showDialog( + context: context, + builder: (context) => WillPopScope( + child: const CustomLoadingOverlay( + message: "Loading currency data", + eventBus: null, + ), + onWillPop: () async => shouldPop, + ), + ), + ); + await _loadSimplexFiats(); + shouldPop = true; + if (mounted) { + Navigator.of(context, rootNavigator: isDesktop).pop(); + } + } + + await _showFloatingFiatSelectionSheet( + fiats: ref.read(simplexProvider).supportedFiats, + onSelected: (fiat) { + setState(() { + selectedFiat = fiat; + minFiat = fiat.minAmount != minFiat ? fiat.minAmount : minFiat; + maxFiat = fiat.maxAmount != maxFiat ? fiat.maxAmount : maxFiat; + }); + validateAmount(); + }, + ); + } + + Future _loadSimplexCryptos() async { + final response = await SimplexAPI.instance.getSupportedCryptos(); + + if (response.value != null) { + ref + .read(simplexProvider) + .updateSupportedCryptos(response.value!); // TODO validate + } else { + Logging.instance.log( + "_loadSimplexCurrencies: $response", + level: LogLevel.Warning, + ); + } + } + + Future _loadSimplexFiats() async { + final response = await SimplexAPI.instance.getSupportedFiats(); + + if (response.value != null) { + ref + .read(simplexProvider) + .updateSupportedFiats(response.value!); // TODO validate + } else { + Logging.instance.log( + "_loadSimplexCurrencies: $response", + level: LogLevel.Warning, + ); + } + } + + Future _showFloatingFiatSelectionSheet({ + required List fiats, + required void Function(Fiat) onSelected, + }) async { + _fiatFocusNode.unfocus(); + _cryptoFocusNode.unfocus(); + + final result = isDesktop + ? await showDialog( + context: context, + builder: (context) { + return DesktopDialog( + maxHeight: 700, + maxWidth: 580, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Choose a fiat with which to pay", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: Row( + children: [ + Expanded( + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(16), + borderColor: Theme.of(context) + .extension()! + .background, + child: FiatSelectionView( + fiats: fiats, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ); + }) + : await Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => FiatSelectionView( + fiats: fiats, + ), + ), + ); + + if (mounted && result is Fiat) { + onSelected(result); + } + } + + // String? _fetchIconUrlFromTicker(String? ticker) { + // if (ticker == null) return null; + // + // return null; + // } + + bool isStackCoin(String? ticker) { + if (ticker == null) return false; + + try { + coinFromTickerCaseInsensitive(ticker); + return true; + } on ArgumentError catch (_) { + return false; + } + } + + Future previewQuote(SimplexQuote quote) async { + bool shouldPop = false; + unawaited( + showDialog( + context: context, + builder: (context) => WillPopScope( + child: const CustomLoadingOverlay( + message: "Loading quote data", + eventBus: null, + ), + onWillPop: () async => shouldPop, + ), + ), + ); + + quote = SimplexQuote( + crypto: selectedCrypto!, + fiat: selectedFiat!, + youPayFiatPrice: buyWithFiat + ? Decimal.parse(_buyAmountController.text) + : Decimal.parse("100"), // dummy value + youReceiveCryptoAmount: buyWithFiat + ? Decimal.parse("0.000420282") // dummy value + : Decimal.parse(_buyAmountController.text), // Ternary for this + id: "id", // anything; we get an ID back + receivingAddress: _receiveAddressController.text, + buyWithFiat: buyWithFiat, + ); + + BuyResponse quoteResponse = await _loadQuote(quote); + shouldPop = true; + if (mounted) { + Navigator.of(context, rootNavigator: isDesktop).pop(); + } + if (quoteResponse.exception == null) { + quote = quoteResponse.value as SimplexQuote; + + if (quote.id != 'id' && quote.id != 'someID') { + // TODO detect default quote better + await _showFloatingBuyQuotePreviewSheet( + quote: ref.read(simplexProvider).quote, + onSelected: (quote) { + // TODO launch URL + }, + ); + } else if (mounted) { + await showDialog( + context: context, + barrierDismissible: true, + builder: (context) { + if (isDesktop) { + return DesktopDialog( + maxWidth: 450, + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Simplex API unresponsive", + style: STextStyles.desktopH3(context), + ), + const SizedBox( + height: 24, + ), + Text( + "Simplex API unresponsive, please try again later", + style: STextStyles.smallMed14(context), + ), + const SizedBox( + height: 56, + ), + Row( + children: [ + const Spacer(), + Expanded( + child: PrimaryButton( + buttonHeight: ButtonHeight.l, + label: "Ok", + onPressed: Navigator.of(context).pop, + ), + ), + ], + ) + ], + ), + ), + ); + } else { + return StackDialog( + title: "Simplex API error", + message: "${quoteResponse.exception?.errorMessage}", + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Ok", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ); + } + }, + ); + } + } else if (mounted) { + // Error; probably amount out of bounds + // String errorMessage = "${quoteResponse.exception?.errorMessage}"; + // if (errorMessage.contains('must be between')) { + // errorMessage = errorMessage.substring( + // errorMessage.indexOf('getQuote exception: ') + 20, + // errorMessage.indexOf(", value: null")); + // _BuyFormState.boundedCryptoTicker = errorMessage.substring( + // errorMessage.indexOf('The ') + 4, + // errorMessage.indexOf(' amount must be between')); + // _BuyFormState.minCrypto = Decimal.parse(errorMessage.substring( + // errorMessage.indexOf('must be between ') + 16, + // errorMessage.indexOf(' and '))); + // _BuyFormState.maxCrypto = Decimal.parse(errorMessage.substring( + // errorMessage.indexOf("$minCrypto and ") + "$minCrypto and ".length, + // errorMessage.length)); + // if (Decimal.parse(_buyAmountController.text) > + // _BuyFormState.maxCrypto) { + // _buyAmountController.text = _BuyFormState.maxCrypto.toString(); + // } + // } + await showDialog( + context: context, + barrierDismissible: true, + builder: (context) { + if (isDesktop) { + return DesktopDialog( + maxWidth: 450, + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Simplex API error", + style: STextStyles.desktopH3(context), + ), + const SizedBox( + height: 24, + ), + Text( + quoteResponse.exception!.errorMessage, + style: STextStyles.smallMed14(context), + ), + const SizedBox( + height: 56, + ), + Row( + children: [ + const Spacer(), + Expanded( + child: PrimaryButton( + buttonHeight: ButtonHeight.l, + label: "Ok", + onPressed: Navigator.of(context).pop, + ), + ), + ], + ) + ], + ), + ), + ); + } else { + return StackDialog( + title: "Simplex API error", + message: "${quoteResponse.exception?.errorMessage}", + // "${quoteResponse.exception?.errorMessage.substring(8, (quoteResponse.exception?.errorMessage?.length ?? 109) - (8 + 6))}", + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Ok", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ); + } + }, + ); + } + } + + Future> _loadQuote(SimplexQuote quote) async { + final response = await SimplexAPI.instance.getQuote(quote); + + if (response.value != null) { + // TODO check for error key + ref.read(simplexProvider).updateQuote(response.value!); + return BuyResponse(value: response.value!); + } else { + Logging.instance.log( + "_loadQuote: $response", + level: LogLevel.Warning, + ); + return BuyResponse( + exception: response.exception ?? + BuyException( + response.toString(), + BuyExceptionType.generic, + ), + ); + } + } + + Future _showFloatingBuyQuotePreviewSheet({ + required SimplexQuote quote, + required void Function(SimplexQuote) onSelected, + }) async { + _fiatFocusNode.unfocus(); + _cryptoFocusNode.unfocus(); + + final result = isDesktop + ? await showDialog( + context: context, + builder: (context) { + return DesktopDialog( + maxHeight: 700, + maxWidth: 580, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Preview quote", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: Row( + children: [ + Expanded( + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(16), + borderColor: Theme.of(context) + .extension()! + .background, + child: BuyQuotePreviewView( + quote: quote, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ); + }) + : await Navigator.of(context).push( + MaterialPageRoute( + builder: (_) => BuyQuotePreviewView( + quote: quote, + ), + ), + ); + + if (mounted && result is SimplexQuote) { + onSelected(result); + } + } + + @override + void initState() { + _receiveAddressController = TextEditingController(); + _buyAmountController = TextEditingController(); + + clipboard = widget.clipboard; + scanner = widget.scanner; + + coins = ref.read(simplexProvider).supportedCryptos; + fiats = ref.read(simplexProvider).supportedFiats; + // quote = ref.read(simplexProvider).quote; + + quote = SimplexQuote( + crypto: Crypto.fromJson({'ticker': 'BTC', 'name': 'Bitcoin'}), + fiat: Fiat.fromJson({'ticker': 'USD', 'name': 'United States Dollar'}), + youPayFiatPrice: Decimal.parse("100"), + youReceiveCryptoAmount: Decimal.parse("1.0238917"), + id: "someID", + receivingAddress: '', + buyWithFiat: true, + ); // TODO enum this or something + + // TODO set defaults better; should probably explicitly enumerate the coins & fiats used and pull the specific ones we need rather than generating them as defaults here + selectedFiat = + Fiat.fromJson({'ticker': 'USD', 'name': 'United States Dollar'}); + selectedCrypto = Crypto.fromJson({ + 'ticker': widget.coin?.ticker ?? 'BTC', + 'name': widget.coin?.prettyName ?? 'Bitcoin' + }); + + // THIS IS BAD. No way to be certain the simplex ticker points to the same + // contract as the ticker symbol of this contract + // if (widget.tokenContract != null && + // DefaultTokens.list + // .where((e) => e.address == widget.tokenContract!.address) + // .isNotEmpty) { + // selectedCrypto = Crypto.fromJson({ + // 'ticker': widget.tokenContract!.symbol, + // 'name': widget.tokenContract!.name, + // }); + // } + + // TODO set initial crypto to open wallet if a wallet is open + + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + + Locale locale = Localizations.localeOf(context); + var format = NumberFormat.simpleCurrency(locale: locale.toString()); + // See https://stackoverflow.com/a/67055685 + + return ConditionalParent( + condition: isDesktop, + builder: (child) => SizedBox( + width: 458, + child: child, + ), + child: ConditionalParent( + condition: !isDesktop, + builder: (child) => LayoutBuilder( + builder: (context, constraints) => SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: child, + ), + ), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "I want to buy", + style: STextStyles.itemSubtitle(context).copyWith( + color: Theme.of(context).extension()!.textDark3, + ), + ), + SizedBox( + height: isDesktop ? 10 : 4, + ), + MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (_) => setState(() => _hovering1 = true), + onExit: (_) => setState(() => _hovering1 = false), + child: GestureDetector( + onTap: () { + selectCrypto(); + }, + child: RoundedContainer( + padding: + const EdgeInsets.symmetric(vertical: 6, horizontal: 2), + color: _hovering1 + ? Theme.of(context) + .extension()! + .currencyListItemBG + .withOpacity(_hovering1 ? 0.3 : 0) + : Theme.of(context) + .extension()! + .textFieldDefaultBG, + child: Padding( + padding: const EdgeInsets.all(12), + child: Row( + children: [ + CoinIconForTicker( + ticker: selectedCrypto?.ticker ?? "BTC", + size: 20, + ), + const SizedBox( + width: 10, + ), + Expanded( + child: Text( + selectedCrypto?.ticker ?? "ERR", + style: STextStyles.largeMedium14(context), + ), + ), + SvgPicture.asset( + Assets.svg.chevronDown, + color: Theme.of(context) + .extension()! + .buttonTextSecondaryDisabled, + width: 10, + height: 5, + ), + ], + ), + ), + ), + ), + ), + SizedBox( + height: isDesktop ? 20 : 12, + ), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "I want to pay with", + style: STextStyles.itemSubtitle(context).copyWith( + color: + Theme.of(context).extension()!.textDark3, + ), + ), + ], + ), + SizedBox( + height: isDesktop ? 10 : 4, + ), + MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (_) => setState(() => _hovering2 = true), + onExit: (_) => setState(() => _hovering2 = false), + child: GestureDetector( + onTap: () { + selectFiat(); + }, + child: RoundedContainer( + padding: + const EdgeInsets.symmetric(vertical: 3, horizontal: 2), + color: _hovering2 + ? Theme.of(context) + .extension()! + .currencyListItemBG + .withOpacity(_hovering2 ? 0.3 : 0) + : Theme.of(context) + .extension()! + .textFieldDefaultBG, + child: Padding( + padding: const EdgeInsets.only( + left: 12.0, + top: 12.0, + right: 12.0, + bottom: 12.0, + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.symmetric( + vertical: 3, horizontal: 6), + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .currencyListItemBG, + borderRadius: BorderRadius.circular(4), + ), + child: Text( + format.simpleCurrencySymbol( + selectedFiat?.ticker ?? "ERR".toUpperCase()), + textAlign: TextAlign.center, + style: STextStyles.smallMed12(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + ), + const SizedBox( + width: 8, + ), + Text( + selectedFiat?.ticker ?? 'ERR', + style: STextStyles.largeMedium14(context), + ), + const SizedBox( + width: 12, + ), + Expanded( + child: Text( + selectedFiat?.name ?? 'Error', + style: STextStyles.largeMedium14(context), + ), + ), + SvgPicture.asset( + Assets.svg.chevronDown, + color: Theme.of(context) + .extension()! + .buttonTextSecondaryDisabled, + width: 10, + height: 5, + ), + ], + ), + ), + ), + ), + ), + SizedBox( + height: isDesktop ? 10 : 4, + ), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + buyWithFiat ? "Enter amount" : "Enter crypto amount", + style: STextStyles.itemSubtitle(context).copyWith( + color: + Theme.of(context).extension()!.textDark3, + ), + ), + CustomTextButton( + text: buyWithFiat ? "Use crypto amount" : "Use fiat amount", + onTap: () { + setState(() { + buyWithFiat = !buyWithFiat; + }); + validateAmount(); + }, + ) + ], + ), + SizedBox( + height: isDesktop ? 10 : 4, + ), + TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + style: STextStyles.smallMed14(context).copyWith( + color: Theme.of(context).extension()!.textDark, + ), + key: const Key("buyAmountInputFieldTextFieldKey"), + controller: _buyAmountController, + // note: setting the text value here will set it every time this widget rebuilds + // ..text = _BuyFormState.buyWithFiat + // ? _BuyFormState.minFiat.toStringAsFixed(2) ?? '50.00' + // : _BuyFormState.minCrypto.toStringAsFixed(8), + focusNode: _buyAmountFocusNode, + keyboardType: Util.isDesktop + ? null + : const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), + textAlign: TextAlign.left, + // inputFormatters: [NumericalRangeFormatter()], + onChanged: (_) { + validateAmount(); + }, + decoration: InputDecoration( + contentPadding: const EdgeInsets.only( + // top: 22, + // right: 12, + // bottom: 22, + left: 0, + top: 8, + bottom: 10, + right: 5, + ), + hintText: "0", + hintStyle: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldDefaultText, + ), + prefixIcon: FittedBox( + fit: BoxFit.scaleDown, + child: Padding( + padding: const EdgeInsets.all(12), + child: Row(children: [ + const SizedBox(width: 2), + buyWithFiat + ? Container( + padding: const EdgeInsets.symmetric( + vertical: 3, horizontal: 6), + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .currencyListItemBG, + borderRadius: BorderRadius.circular(4), + ), + child: Text( + format.simpleCurrencySymbol( + selectedFiat?.ticker.toUpperCase() ?? + "ERR"), + textAlign: TextAlign.center, + style: STextStyles.smallMed12(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + ) + : CoinIconForTicker( + ticker: selectedCrypto?.ticker ?? "BTC", + size: 20, + ), + SizedBox( + width: buyWithFiat + ? 8 + : 10), // maybe make isDesktop-aware? + Text( + buyWithFiat + ? selectedFiat?.ticker ?? "ERR" + : selectedCrypto?.ticker ?? "ERR", + style: STextStyles.smallMed14(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + ]), + ), + ), + suffixIcon: Padding( + padding: const EdgeInsets.all(0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _buyAmountController.text.isNotEmpty + ? TextFieldIconButton( + key: const Key( + "buyViewClearAmountFieldButtonKey"), + onTap: () { + // if (_BuyFormState.buyWithFiat) { + // _buyAmountController.text = _BuyFormState + // .minFiat + // .toStringAsFixed(2); + // } else { + // if (selectedCrypto?.ticker == + // _BuyFormState.boundedCryptoTicker) { + // _buyAmountController.text = _BuyFormState + // .minCrypto + // .toStringAsFixed(8); + // } + // } + _buyAmountController.text = ""; + validateAmount(); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + key: const Key( + "buyViewPasteAddressFieldButtonKey"), + onTap: () async { + final ClipboardData? data = await clipboard + .getData(Clipboard.kTextPlain); + + final amountString = + Decimal.tryParse(data?.text ?? ""); + if (amountString != null) { + _buyAmountController.text = + amountString.toString(); + + validateAmount(); + } + }, + child: _buyAmountController.text.isEmpty + ? const ClipboardIcon() + : const XIcon(), + ), + ], + ), + ), + ), + ), + ), + SizedBox( + height: isDesktop ? 10 : 4, + ), + if (_amountOutOfRangeErrorString.isNotEmpty) + Text( + _amountOutOfRangeErrorString, + style: STextStyles.errorSmall(context), + ), + SizedBox( + height: isDesktop ? 20 : 12, + ), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Enter receiving address", + style: STextStyles.itemSubtitle(context).copyWith( + color: + Theme.of(context).extension()!.textDark3, + ), + ), + if (isStackCoin(selectedCrypto?.ticker)) + CustomTextButton( + text: "Choose from Stack", + onTap: () { + try { + final coin = coinFromTickerCaseInsensitive( + selectedCrypto!.ticker, + ); + Navigator.of(context) + .pushNamed( + ChooseFromStackView.routeName, + arguments: coin, + ) + .then((value) async { + if (value is String) { + final manager = ref + .read(walletsChangeNotifierProvider) + .getManager(value); + + // _toController.text = manager.walletName; + // model.recipientAddress = + // await manager.currentReceivingAddress; + _receiveAddressController.text = + await manager.currentReceivingAddress; + + setState(() { + _addressToggleFlag = + _receiveAddressController.text.isNotEmpty; + }); + validateAmount(); + } + }); + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Info); + } + }, + ), + ], + ), + SizedBox( + height: isDesktop ? 10 : 4, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("buyViewReceiveAddressFieldKey"), + controller: _receiveAddressController, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + // inputFormatters: [ + // FilteringTextInputFormatter.allow( + // RegExp("[a-zA-Z0-9]{34}")), + // ], + toolbarOptions: const ToolbarOptions( + copy: false, + cut: false, + paste: true, + selectAll: false, + ), + onChanged: (newValue) { + _address = newValue; + setState(() { + _addressToggleFlag = newValue.isNotEmpty; + }); + }, + focusNode: _receiveAddressFocusNode, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Enter ${selectedCrypto?.ticker} address", + _receiveAddressFocusNode, + context, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 13, + top: 6, + bottom: 8, + right: 5, + ), + suffixIcon: Padding( + padding: _receiveAddressController.text.isEmpty + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _addressToggleFlag + ? TextFieldIconButton( + key: const Key( + "buyViewClearAddressFieldButtonKey"), + onTap: () { + _receiveAddressController.text = ""; + _address = ""; + setState(() { + _addressToggleFlag = false; + }); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + key: const Key( + "buyViewPasteAddressFieldButtonKey"), + onTap: () async { + final ClipboardData? data = await clipboard + .getData(Clipboard.kTextPlain); + if (data?.text != null && + data!.text!.isNotEmpty) { + String content = data.text!.trim(); + if (content.contains("\n")) { + content = content.substring( + 0, content.indexOf("\n")); + } + + _receiveAddressController.text = content; + _address = content; + + setState(() { + _addressToggleFlag = + _receiveAddressController + .text.isNotEmpty; + }); + } + }, + child: _receiveAddressController.text.isEmpty + ? const ClipboardIcon() + : const XIcon(), + ), + if (_receiveAddressController.text.isEmpty && + isStackCoin(selectedCrypto?.ticker) && + isDesktop) + TextFieldIconButton( + key: const Key("buyViewAddressBookButtonKey"), + onTap: () async { + final entry = + await showDialog( + context: context, + builder: (context) => DesktopDialog( + maxWidth: 696, + maxHeight: 600, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Address book", + style: STextStyles.desktopH3( + context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: AddressBookAddressChooser( + coin: coinFromTickerCaseInsensitive( + selectedCrypto!.ticker + .toString()), + ), + ), + ], + ), + ), + ); + + if (entry != null) { + _receiveAddressController.text = + entry.address; + _address = entry.address; + + setState(() { + _addressToggleFlag = true; + }); + } + }, + child: const AddressBookIcon(), + ), + if (_receiveAddressController.text.isEmpty && + isStackCoin(selectedCrypto?.ticker) && + !isDesktop) + TextFieldIconButton( + key: const Key("buyViewAddressBookButtonKey"), + onTap: () { + Navigator.of(context, rootNavigator: isDesktop) + .pushNamed( + AddressBookView.routeName, + ); + }, + child: const AddressBookIcon(), + ), + if (_receiveAddressController.text.isEmpty && + !isDesktop) + TextFieldIconButton( + key: const Key("buyViewScanQrButtonKey"), + onTap: () async { + try { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75)); + } + + final qrResult = await scanner.scan(); + + Logging.instance.log( + "qrResult content: ${qrResult.rawContent}", + level: LogLevel.Info); + + final results = AddressUtils.parseUri( + qrResult.rawContent); + + Logging.instance.log( + "qrResult parsed: $results", + level: LogLevel.Info); + + if (results.isNotEmpty) { + // auto fill address + _address = results["address"] ?? ""; + _receiveAddressController.text = _address!; + + setState(() { + _addressToggleFlag = + _receiveAddressController + .text.isNotEmpty; + }); + + // now check for non standard encoded basic address + } else { + _address = qrResult.rawContent; + _receiveAddressController.text = + _address ?? ""; + + setState(() { + _addressToggleFlag = + _receiveAddressController + .text.isNotEmpty; + }); + } + } on PlatformException catch (e, s) { + // here we ignore the exception caused by not giving permission + // to use the camera to scan a qr code + Logging.instance.log( + "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s", + level: LogLevel.Warning, + ); + } + }, + child: const QrCodeIcon(), + ), + ], + ), + ), + ), + ), + ), + ), + SizedBox( + height: isDesktop ? 20 : 12, + ), + PrimaryButton( + buttonHeight: isDesktop ? ButtonHeight.l : null, + enabled: _addressToggleFlag && + _amountOutOfRangeErrorString.isEmpty && + _buyAmountController.text.isNotEmpty, + onPressed: () { + previewQuote(quote); + }, + label: "Preview quote", + ), + ], + ), + ), + ); + } +} + +// might need this again in the future + +// // See https://stackoverflow.com/a/68072967 +// class NumericalRangeFormatter extends TextInputFormatter { +// NumericalRangeFormatter(); +// +// @override +// TextEditingValue formatEditUpdate( +// TextEditingValue oldValue, +// TextEditingValue newValue, +// ) { +// TextSelection newSelection = newValue.selection; +// String newVal = _BuyFormState.buyWithFiat +// ? Decimal.parse(newValue.text).toStringAsFixed(2) +// : Decimal.parse(newValue.text).toStringAsFixed(8); +// if (newValue.text == '') { +// return newValue; +// } else { +// if (_BuyFormState.buyWithFiat) { +// if (Decimal.parse(newValue.text) < _BuyFormState.minFiat) { +// newVal = _BuyFormState.minFiat.toStringAsFixed(2); +// // _BuyFormState._buyAmountController.selection = +// // TextSelection.collapsed( +// // offset: _BuyFormState.buyWithFiat +// // ? _BuyFormState._buyAmountController.text.length - 2 +// // : _BuyFormState._buyAmountController.text.length - 8); +// } else if (Decimal.parse(newValue.text) > _BuyFormState.maxFiat) { +// newVal = _BuyFormState.maxFiat.toStringAsFixed(2); +// } +// } else if (!_BuyFormState.buyWithFiat && +// _BuyFormState.selectedCrypto?.ticker == +// _BuyFormState.boundedCryptoTicker) { +// if (Decimal.parse(newValue.text) < _BuyFormState.minCrypto) { +// newVal = _BuyFormState.minCrypto.toStringAsFixed(8); +// } else if (Decimal.parse(newValue.text) > _BuyFormState.maxCrypto) { +// newVal = _BuyFormState.maxCrypto.toStringAsFixed(8); +// } +// } +// } +// +// final regexString = _BuyFormState.buyWithFiat +// ? r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$' +// : r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$'; +// +// // return RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$') +// return RegExp(regexString).hasMatch(newVal) +// ? TextEditingValue(text: newVal, selection: newSelection) +// : oldValue; +// } +// } diff --git a/lib/pages/buy_view/buy_in_wallet_view.dart b/lib/pages/buy_view/buy_in_wallet_view.dart new file mode 100644 index 000000000..8e74aee7d --- /dev/null +++ b/lib/pages/buy_view/buy_in_wallet_view.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; +import 'package:stackwallet/pages/buy_view/buy_view.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; + +class BuyInWalletView extends StatefulWidget { + const BuyInWalletView({ + Key? key, + required this.coin, + this.contract, + }) : super(key: key); + + static const String routeName = "/stackBuyInWalletView"; + + final Coin? coin; + final EthContract? contract; + + @override + State createState() => _BuyInWalletViewState(); +} + +class _BuyInWalletViewState extends State { + late final Coin? coin; + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Buy ${widget.coin?.ticker}", + style: STextStyles.navBarTitle(context), + ), + ), + body: BuyView( + coin: widget.coin, + tokenContract: widget.contract, + ), + ), + ); + } +} diff --git a/lib/pages/buy_view/buy_order_details.dart b/lib/pages/buy_view/buy_order_details.dart new file mode 100644 index 000000000..b21167201 --- /dev/null +++ b/lib/pages/buy_view/buy_order_details.dart @@ -0,0 +1,267 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/buy/response_objects/order.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class BuyOrderDetailsView extends StatefulWidget { + const BuyOrderDetailsView({ + Key? key, + required this.order, + }) : super(key: key); + + final SimplexOrder order; + + static const String routeName = "/buyOrderDetails"; + + @override + State createState() => _BuyOrderDetailsViewState(); +} + +class _BuyOrderDetailsViewState extends State { + final isDesktop = Util.isDesktop; + + @override + Widget build(BuildContext context) { + return ConditionalParent( + condition: !isDesktop, + builder: (child) { + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension()!.backgroundAppBar, + leading: const AppBarBackButton(), + title: Text( + "Order details", + style: STextStyles.navBarTitle(context), + ), + ), + body: LayoutBuilder( + builder: (builderContext, constraints) { + return Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, + ), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: child, + ), + ), + ), + ), + ); + }, + ), + ), + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Simplex order", + style: STextStyles.pageTitleH1(context), + ), + const SizedBox( + height: 16, + ), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Purchase ID", + style: STextStyles.label(context), + ), + Text( + widget.order.paymentId, + style: STextStyles.label(context).copyWith( + color: Theme.of(context).extension()!.textDark, + ), + ), + ], + ), + ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "User ID", + style: STextStyles.label(context), + ), + Text( + widget.order.userId, + style: STextStyles.label(context).copyWith( + color: Theme.of(context).extension()!.textDark, + ), + ), + ], + ), + ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Quote ID", + style: STextStyles.label(context), + ), + Text( + widget.order.quote.id, + style: STextStyles.label(context).copyWith( + color: Theme.of(context).extension()!.textDark, + ), + ), + ], + ), + ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Quoted cost", + style: STextStyles.label(context), + ), + Text( + "${widget.order.quote.youPayFiatPrice.toStringAsFixed(2)} ${widget.order.quote.fiat.ticker.toUpperCase()}", + style: STextStyles.label(context).copyWith( + color: Theme.of(context).extension()!.textDark, + ), + ), + ], + ), + ), + const SizedBox( + height: 8, + ), + // RoundedWhiteContainer( + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // Text( + // "You pay with", + // style: STextStyles.label(context), + // ), + // Text( + // widget.quote.fiat.name, + // style: STextStyles.label(context).copyWith( + // color: Theme.of(context).extension()!.textDark, + // ), + // ), + // ], + // ), + // ), + // const SizedBox( + // height: 8, + // ), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Quoted amount", + style: STextStyles.label(context), + ), + Text( + "${widget.order.quote.youReceiveCryptoAmount} ${widget.order.quote.crypto.ticker.toUpperCase()}", + style: STextStyles.label(context).copyWith( + color: Theme.of(context).extension()!.textDark, + ), + ), + ], + ), + ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Receiving ${widget.order.quote.crypto.ticker.toUpperCase()} address", + style: STextStyles.label(context), + ), + Text( + "${widget.order.quote.receivingAddress} ", + style: STextStyles.label(context).copyWith( + color: Theme.of(context).extension()!.textDark, + ), + ), + ], + ), + ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Provider", + style: STextStyles.label(context), + ), + SizedBox( + width: 64, + height: 32, + child: SvgPicture.asset( + Assets.buy.simplexLogo(context), + ), + ), + ], + ), + ), + const SizedBox( + height: 24, + ), + Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + Text( + "This information is not saved,\nscreenshot it now for your records", + style: STextStyles.label(context).copyWith( + color: Theme.of(context).extension()!.textDark, + ), + textAlign: TextAlign.center, + ), + ]), + const Spacer(), + PrimaryButton( + label: "Dismiss", + onPressed: () { + Navigator.of(context, rootNavigator: isDesktop).pop(); + }, + ) + ], + ), + ); + } +} diff --git a/lib/pages/buy_view/buy_quote_preview.dart b/lib/pages/buy_view/buy_quote_preview.dart new file mode 100644 index 000000000..a3f872407 --- /dev/null +++ b/lib/pages/buy_view/buy_quote_preview.dart @@ -0,0 +1,233 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:intl/intl.dart'; +import 'package:stackwallet/models/buy/response_objects/quote.dart'; +import 'package:stackwallet/pages/buy_view/sub_widgets/buy_warning_popup.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class BuyQuotePreviewView extends StatefulWidget { + const BuyQuotePreviewView({ + Key? key, + required this.quote, + }) : super(key: key); + + final SimplexQuote quote; + + static const String routeName = "/buyQuotePreview"; + + @override + State createState() => _BuyQuotePreviewViewState(); +} + +class _BuyQuotePreviewViewState extends State { + final isDesktop = Util.isDesktop; + + Future _buyWarning() async { + await showDialog( + context: context, + builder: (context) => BuyWarningPopup( + quote: widget.quote, + ), + ); + } + + @override + Widget build(BuildContext context) { + Locale locale = Localizations.localeOf(context); + var format = NumberFormat.simpleCurrency(locale: locale.toString()); + // See https://stackoverflow.com/a/67055685 + + return ConditionalParent( + condition: !isDesktop, + builder: (child) { + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension()!.backgroundAppBar, + leading: const AppBarBackButton(), + title: Text( + "Preview quote", + style: STextStyles.navBarTitle(context), + ), + ), + body: LayoutBuilder( + builder: (builderContext, constraints) { + return Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, + ), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: child, + ), + ), + ), + ), + ); + }, + ), + ), + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + "Buy ${widget.quote.crypto.ticker.toUpperCase()}", + style: STextStyles.pageTitleH1(context), + ), + const SizedBox( + height: 16, + ), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "You pay", + style: STextStyles.label(context), + ), + Text( + "${format.simpleCurrencySymbol(widget.quote.fiat.ticker.toUpperCase())}${widget.quote.youPayFiatPrice.toStringAsFixed(2)} ${widget.quote.fiat.ticker.toUpperCase()}", + style: STextStyles.label(context).copyWith( + color: Theme.of(context).extension()!.textDark, + ), + ), + ], + ), + ), + const SizedBox( + height: 8, + ), + // RoundedWhiteContainer( + // child: Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // Text( + // "You pay with", + // style: STextStyles.label(context), + // ), + // Text( + // widget.quote.fiat.name, + // style: STextStyles.label(context).copyWith( + // color: Theme.of(context).extension()!.textDark, + // ), + // ), + // ], + // ), + // ), + // const SizedBox( + // height: 8, + // ), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "You receive", + style: STextStyles.label(context), + ), + Text( + "${widget.quote.youReceiveCryptoAmount} ${widget.quote.crypto.ticker.toUpperCase()}", + style: STextStyles.label(context).copyWith( + color: Theme.of(context).extension()!.textDark, + ), + ), + ], + ), + ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Receiving ${widget.quote.crypto.ticker.toUpperCase()} address", + style: STextStyles.label(context), + ), + Text( + "${widget.quote.receivingAddress} ", + style: STextStyles.label(context).copyWith( + color: Theme.of(context).extension()!.textDark, + ), + ), + ], + ), + ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Quote ID", + style: STextStyles.label(context), + ), + Text( + widget.quote.id, + style: STextStyles.label(context).copyWith( + color: Theme.of(context).extension()!.textDark, + ), + ), + ], + ), + ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Provider", + style: STextStyles.label(context), + ), + SizedBox( + width: 64, + height: 32, + child: SvgPicture.asset( + Assets.buy.simplexLogo(context), + ), + ), + ], + ), + ), + const SizedBox( + height: 8, + ), + const Spacer(), + PrimaryButton( + label: "Buy", + onPressed: _buyWarning, + ) + ], + ), + ); + } +} diff --git a/lib/pages/buy_view/buy_view.dart b/lib/pages/buy_view/buy_view.dart index cc536dd45..4136bc5e1 100644 --- a/lib/pages/buy_view/buy_view.dart +++ b/lib/pages/buy_view/buy_view.dart @@ -1,49 +1,35 @@ import 'package:flutter/material.dart'; -import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; +import 'package:stackwallet/pages/buy_view/buy_form.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; -class BuyView extends StatefulWidget { - const BuyView({Key? key}) : super(key: key); +class BuyView extends StatelessWidget { + const BuyView({ + Key? key, + this.coin, + this.tokenContract, + }) : super(key: key); - @override - State createState() => _BuyViewState(); -} + static const String routeName = "/stackBuyView"; + + final Coin? coin; + final EthContract? tokenContract; -class _BuyViewState extends State { @override Widget build(BuildContext context) { - //todo: check if print needed - // debugPrint("BUILD: BuyView"); + debugPrint("BUILD: $runtimeType"); + return SafeArea( - child: Center( - child: SingleChildScrollView( - child: Column( - children: [ - Center( - child: Text( - "Coming soon", - style: STextStyles.pageTitleH1(context), - ), - ), - ], - ), + child: Padding( + padding: const EdgeInsets.only( + left: 16, + right: 16, + top: 16, + ), + child: BuyForm( + coin: coin, + tokenContract: tokenContract, ), - // child: Column( - // children: [ - // Container( - // color: Colors.green, - // child: Text("BuyView"), - // ), - // Container( - // color: Colors.green, - // child: Text("BuyView"), - // ), - // Spacer(), - // Container( - // color: Colors.green, - // child: Text("BuyView"), - // ), - // ], - // ), ), ); } diff --git a/lib/pages/buy_view/sub_widgets/buy_warning_popup.dart b/lib/pages/buy_view/sub_widgets/buy_warning_popup.dart new file mode 100644 index 000000000..aec4ce45c --- /dev/null +++ b/lib/pages/buy_view/sub_widgets/buy_warning_popup.dart @@ -0,0 +1,290 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/buy/response_objects/order.dart'; +import 'package:stackwallet/models/buy/response_objects/quote.dart'; +import 'package:stackwallet/pages/buy_view/buy_order_details.dart'; +import 'package:stackwallet/services/buy/buy_response.dart'; +import 'package:stackwallet/services/buy/simplex/simplex_api.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; + +class BuyWarningPopup extends StatefulWidget { + const BuyWarningPopup({ + Key? key, + required this.quote, + this.order, + }) : super(key: key); + final SimplexQuote quote; + final SimplexOrder? order; + @override + State createState() => _BuyWarningPopupState(); +} + +class _BuyWarningPopupState extends State { + late final bool isDesktop; + SimplexOrder? order; + + String get title => "Buy ${widget.quote.crypto.ticker}"; + String get message => + "This purchase is provided and fulfilled by Simplex by nuvei " + "(a third party). You will be taken to their website. Please follow " + "their instructions."; + + Future> newOrder(SimplexQuote quote) async { + final orderResponse = await SimplexAPI.instance.newOrder(quote); + + return orderResponse; + } + + Future> redirect(SimplexOrder order) async { + return SimplexAPI.instance.redirect(order); + } + + Future _buyInvoice() async { + await showDialog( + context: context, + // useRootNavigator: isDesktop, + builder: (context) { + return isDesktop + ? DesktopDialog( + maxHeight: 700, + maxWidth: 580, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Order details", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: Row( + children: [ + Expanded( + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(16), + borderColor: Theme.of(context) + .extension()! + .background, + child: BuyOrderDetailsView( + order: order as SimplexOrder, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ) + : BuyOrderDetailsView( + order: order as SimplexOrder, + ); + }, + ); + } + + Future onContinue() async { + BuyResponse orderResponse = await newOrder(widget.quote); + if (orderResponse.exception == null) { + await redirect(orderResponse.value as SimplexOrder) + .then((_response) async { + order = orderResponse.value as SimplexOrder; + Navigator.of(context, rootNavigator: isDesktop).pop(); + Navigator.of(context, rootNavigator: isDesktop).pop(); + await _buyInvoice(); + }); + } else { + await showDialog( + context: context, + barrierDismissible: true, + builder: (context) { + if (isDesktop) { + return DesktopDialog( + maxWidth: 450, + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Simplex API error", + style: STextStyles.desktopH3(context), + ), + const SizedBox( + height: 24, + ), + Text( + "${orderResponse.exception?.errorMessage}", + style: STextStyles.smallMed14(context), + ), + const SizedBox( + height: 56, + ), + Row( + children: [ + const Spacer(), + Expanded( + child: PrimaryButton( + buttonHeight: ButtonHeight.l, + label: "Ok", + onPressed: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + Navigator.of(context).pop(); // weee + }, + ), + ), + ], + ) + ], + ), + ), + ); + } else { + return StackDialog( + title: "Simplex API error", + message: "${orderResponse.exception?.errorMessage}", + // "${quoteResponse.exception?.errorMessage.substring(8, (quoteResponse.exception?.errorMessage?.length ?? 109) - (8 + 6))}", + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Ok", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + onPressed: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + Navigator.of(context).pop(); // weee + }, + ), + ); + } + }, + ); + } + } + + @override + void initState() { + order = widget.order; + isDesktop = Util.isDesktop; + super.initState(); + } + + @override + Widget build(BuildContext context) { + if (isDesktop) { + return DesktopDialog( + maxWidth: 580, + maxHeight: 350, + child: Padding( + padding: const EdgeInsets.all(32), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: STextStyles.desktopH3(context), + ), + SizedBox( + width: 64, + height: 32, + child: SvgPicture.asset( + Assets.buy.simplexLogo(context), + ), + ), + ], + ), + const Spacer(), + Text( + message, + style: STextStyles.desktopTextSmall(context), + ), + const Spacer( + flex: 2, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + buttonHeight: ButtonHeight.l, + onPressed: + Navigator.of(context, rootNavigator: isDesktop).pop, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + buttonHeight: ButtonHeight.l, + label: "Continue", + onPressed: onContinue, + ), + ), + ], + ) + ], + ), + ), + ); + } else { + return StackDialog( + title: title, + message: message, + leftButton: SecondaryButton( + label: "Cancel", + onPressed: Navigator.of(context, rootNavigator: isDesktop).pop, + ), + rightButton: PrimaryButton( + label: "Continue", + onPressed: onContinue, + ), + icon: SizedBox( + width: 64, + height: 32, + child: SvgPicture.asset( + Assets.buy.simplexLogo(context), + ), + ), + ); + } + } +} diff --git a/lib/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart b/lib/pages/buy_view/sub_widgets/crypto_selection_view.dart similarity index 59% rename from lib/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart rename to lib/pages/buy_view/sub_widgets/crypto_selection_view.dart index 7c8ff34bd..7ee9b296b 100644 --- a/lib/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart +++ b/lib/pages/buy_view/sub_widgets/crypto_selection_view.dart @@ -1,46 +1,49 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/models/exchange/response_objects/currency.dart'; +import 'package:stackwallet/models/buy/response_objects/crypto.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; -import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; -class FloatingRateCurrencySelectionView extends StatefulWidget { - const FloatingRateCurrencySelectionView({ +class CryptoSelectionView extends ConsumerStatefulWidget { + const CryptoSelectionView({ Key? key, - required this.currencies, + required this.coins, }) : super(key: key); - final List currencies; + final List coins; @override - State createState() => - _FloatingRateCurrencySelectionViewState(); + ConsumerState createState() => + _CryptoSelectionViewState(); } -class _FloatingRateCurrencySelectionViewState - extends State { +class _CryptoSelectionViewState extends ConsumerState { late TextEditingController _searchController; final _searchFocusNode = FocusNode(); - late final List currencies; - late List _currencies; + late final List coins; + late List _coins; void filter(String text) { setState(() { - _currencies = [ - ...currencies.where((e) => + _coins = [ + ...coins.where((e) => e.name.toLowerCase().contains(text.toLowerCase()) || e.ticker.toLowerCase().contains(text.toLowerCase())) ]; @@ -51,19 +54,19 @@ class _FloatingRateCurrencySelectionViewState void initState() { _searchController = TextEditingController(); - currencies = [...widget.currencies]; - currencies.sort( + coins = [...widget.coins]; + coins.sort( (a, b) => a.ticker.toLowerCase().compareTo(b.ticker.toLowerCase())); for (Coin coin in Coin.values.reversed) { - int index = currencies.indexWhere((element) => + int index = coins.indexWhere((element) => element.ticker.toLowerCase() == coin.ticker.toLowerCase()); if (index > 0) { - final currency = currencies.removeAt(index); - currencies.insert(0, currency); + final currency = coins.removeAt(index); + coins.insert(0, currency); } } - _currencies = [...currencies]; + _coins = [...coins]; super.initState(); } @@ -99,7 +102,7 @@ class _FloatingRateCurrencySelectionViewState }, ), title: Text( - "Choose a coin to exchange", + "Choose a coin to buy", style: STextStyles.pageTitleH2(context), ), ), @@ -175,88 +178,6 @@ class _FloatingRateCurrencySelectionViewState const SizedBox( height: 10, ), - Text( - "Popular coins", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 12, - ), - Flexible( - child: Builder(builder: (context) { - final items = _currencies - .where((e) => Coin.values - .where((coin) => - coin.ticker.toLowerCase() == e.ticker.toLowerCase()) - .isNotEmpty) - .toList(growable: false); - - return RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: ListView.builder( - shrinkWrap: true, - primary: isDesktop ? false : null, - itemCount: items.length, - itemBuilder: (builderContext, index) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: GestureDetector( - onTap: () { - Navigator.of(context).pop(items[index]); - }, - child: RoundedWhiteContainer( - child: Row( - children: [ - SizedBox( - width: 24, - height: 24, - child: SvgPicture.network( - items[index].image, - width: 24, - height: 24, - placeholderBuilder: (_) => - const LoadingIndicator(), - ), - ), - const SizedBox( - width: 10, - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - items[index].name, - style: STextStyles.largeMedium14(context), - ), - const SizedBox( - height: 2, - ), - Text( - items[index].ticker.toUpperCase(), - style: STextStyles.smallMed12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), - ], - ), - ), - ], - ), - ), - ), - ); - }, - ), - ); - }), - ), - const SizedBox( - height: 20, - ), Text( "All coins", style: STextStyles.smallMed12(context), @@ -270,13 +191,13 @@ class _FloatingRateCurrencySelectionViewState child: ListView.builder( shrinkWrap: true, primary: isDesktop ? false : null, - itemCount: _currencies.length, + itemCount: _coins.length, itemBuilder: (builderContext, index) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: GestureDetector( onTap: () { - Navigator.of(context).pop(_currencies[index]); + Navigator.of(context).pop(_coins[index]); }, child: RoundedWhiteContainer( child: Row( @@ -284,12 +205,9 @@ class _FloatingRateCurrencySelectionViewState SizedBox( width: 24, height: 24, - child: SvgPicture.network( - _currencies[index].image, - width: 24, - height: 24, - placeholderBuilder: (_) => - const LoadingIndicator(), + child: CoinIconForTicker( + size: 20, + ticker: _coins[index].ticker, ), ), const SizedBox( @@ -300,14 +218,14 @@ class _FloatingRateCurrencySelectionViewState crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - _currencies[index].name, + _coins[index].name, style: STextStyles.largeMedium14(context), ), const SizedBox( height: 2, ), Text( - _currencies[index].ticker.toUpperCase(), + _coins[index].ticker.toUpperCase(), style: STextStyles.smallMed12(context) .copyWith( color: Theme.of(context) @@ -332,3 +250,70 @@ class _FloatingRateCurrencySelectionViewState ); } } + +bool isStackCoin(String? ticker) { + if (ticker == null) return false; + + try { + coinFromTickerCaseInsensitive(ticker); + return true; + } on ArgumentError catch (_) { + return false; + } +} + +// make a stateless widget that takes in string and double (won't ever be null) +// class getIconForTicker extends ConsumerWidget{ +// const getIconForTicker({ +// Key? key, +// this.ticker, +// +// }) : super(key: key); +// @override +// Widget build(BuildContext context, WidgetRef ref) { +// // TODO: implement build +// throw UnimplementedError(); +// } +// +// } +/// caller must ensure [Coin] for ticker exists +class CoinIconForTicker extends ConsumerWidget { + const CoinIconForTicker({ + Key? key, + required this.ticker, + required this.size, + }) : super(key: key); + + final String ticker; + final double size; + + @override + Widget build(BuildContext context, WidgetRef ref) { + try { + final coin = coinFromTickerCaseInsensitive(ticker); + return SvgPicture.file( + File( + ref.watch(coinIconProvider(coin)), + ), + width: size, + height: size, + ); + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Fatal); + rethrow; + } + } +} + +// Widget? getIconForTicker( +// String ticker, { +// double size = 20, +// }) { +// String? iconAsset = /*isStackCoin(ticker) +// ?*/ +// Assets.svg.iconFor(coin: coinFromTickerCaseInsensitive(ticker)); +// // : Assets.svg.buyIconFor(ticker); +// return (iconAsset != null) +// ? SvgPicture.asset(iconAsset, height: size, width: size) +// : null; +// } diff --git a/lib/pages/buy_view/sub_widgets/fiat_selection_view.dart b/lib/pages/buy_view/sub_widgets/fiat_selection_view.dart new file mode 100644 index 000000000..07bd504d5 --- /dev/null +++ b/lib/pages/buy_view/sub_widgets/fiat_selection_view.dart @@ -0,0 +1,368 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:intl/intl.dart'; +import 'package:stackwallet/models/buy/response_objects/fiat.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/fiat_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; + +class FiatSelectionView extends StatefulWidget { + const FiatSelectionView({ + Key? key, + required this.fiats, + }) : super(key: key); + + final List fiats; + + @override + State createState() => _FiatSelectionViewState(); +} + +class _FiatSelectionViewState extends State { + late TextEditingController _searchController; + final _searchFocusNode = FocusNode(); + + late final List fiats; + late List _fiats; + + void filter(String text) { + setState(() { + _fiats = [ + ...fiats.where((e) => + e.name.toLowerCase().contains(text.toLowerCase()) || + e.ticker.toLowerCase().contains(text.toLowerCase())) + ]; + }); + } + + @override + void initState() { + _searchController = TextEditingController(); + + fiats = [...widget.fiats]; + fiats.sort( + (a, b) => a.ticker.toLowerCase().compareTo(b.ticker.toLowerCase())); + for (Fiats fiat in Fiats.values.reversed) { + int index = fiats.indexWhere((element) => + element.ticker.toLowerCase() == fiat.ticker.toLowerCase()); + if (index > 0) { + final currency = fiats.removeAt(index); + fiats.insert(0, currency); + } + } + + _fiats = [...fiats]; + + super.initState(); + } + + @override + void dispose() { + _searchController.dispose(); + _searchFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + Locale locale = Localizations.localeOf(context); + final format = NumberFormat.simpleCurrency(locale: locale.toString()); + // See https://stackoverflow.com/a/67055685 + + final isDesktop = Util.isDesktop; + + return ConditionalParent( + condition: !isDesktop, + builder: (child) { + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 50)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Choose a currency with which to pay", + style: STextStyles.pageTitleH2(context), + ), + ), + body: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + child: child, + ), + ), + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: isDesktop ? MainAxisSize.min : MainAxisSize.max, + children: [ + if (!isDesktop) + const SizedBox( + height: 16, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autofocus: isDesktop, + autocorrect: !isDesktop, + enableSuggestions: !isDesktop, + controller: _searchController, + focusNode: _searchFocusNode, + onChanged: filter, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Search", + _searchFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 16, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: 16, + height: 16, + ), + ), + suffixIcon: _searchController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchController.text = ""; + }); + filter(""); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), + const SizedBox( + height: 10, + ), + Text( + "All currencies", + style: STextStyles.smallMed12(context), + ), + const SizedBox( + height: 12, + ), + Flexible( + child: SingleChildScrollView( + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: Table( + columnWidths: const { + 0: IntrinsicColumnWidth(), + 1: FlexColumnWidth(), + }, + defaultVerticalAlignment: TableCellVerticalAlignment.middle, + children: [ + ..._fiats.map( + (e) { + return TableRow( + children: [ + TableCell( + verticalAlignment: + TableCellVerticalAlignment.fill, + child: GestureDetector( + onTap: () => Navigator.of(context).pop(e), + child: Container( + color: Colors.transparent, + padding: const EdgeInsets.only(left: 12), + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: + CrossAxisAlignment.stretch, + children: [ + Container( + padding: const EdgeInsets.all(7.5), + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .currencyListItemBG, + borderRadius: + BorderRadius.circular(4), + ), + child: Text( + format.simpleCurrencySymbol( + e.ticker.toUpperCase()), + style: STextStyles.subtitle(context) + .apply( + fontSizeFactor: (1 / + format + .simpleCurrencySymbol( + e.ticker.toUpperCase()) + .length * // Couldn't get pow() working here + format + .simpleCurrencySymbol( + e.ticker.toUpperCase()) + .length), + ), + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + ), + ), + GestureDetector( + onTap: () => Navigator.of(context).pop(e), + child: Container( + color: Colors.transparent, + child: Padding( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + e.name, + style: + STextStyles.largeMedium14(context), + ), + const SizedBox( + height: 2, + ), + Text( + e.ticker.toUpperCase(), + style: STextStyles.smallMed12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ], + ), + ), + ), + ), + ], + ); + }, + ), + ], + ), + + // child: ListView.builder( + // shrinkWrap: true, + // primary: isDesktop ? false : null, + // itemCount: _fiats.length, + // itemBuilder: (builderContext, index) { + // return Padding( + // padding: const EdgeInsets.symmetric(vertical: 4), + // child: GestureDetector( + // onTap: () { + // Navigator.of(context).pop(_fiats[index]); + // }, + // child: RoundedWhiteContainer( + // child: Row( + // children: [ + // Container( + // padding: const EdgeInsets.all(7.5), + // decoration: BoxDecoration( + // color: Theme.of(context) + // .extension()! + // .currencyListItemBG, + // borderRadius: BorderRadius.circular(4), + // ), + // child: Text( + // format.simpleCurrencySymbol( + // _fiats[index].ticker.toUpperCase()), + // style: STextStyles.subtitle(context).apply( + // fontSizeFactor: (1 / + // format + // .simpleCurrencySymbol(_fiats[index] + // .ticker + // .toUpperCase()) + // .length * // Couldn't get pow() working here + // format + // .simpleCurrencySymbol(_fiats[index] + // .ticker + // .toUpperCase()) + // .length)), + // textAlign: TextAlign.center, + // ), + // ), + // const SizedBox( + // width: 10, + // ), + // Expanded( + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // Text( + // _fiats[index].name, + // style: STextStyles.largeMedium14(context), + // ), + // const SizedBox( + // height: 2, + // ), + // Text( + // _fiats[index].ticker.toUpperCase(), + // style: STextStyles.smallMed12(context) + // .copyWith( + // color: Theme.of(context) + // .extension()! + // .textSubtitle1, + // ), + // ), + // ], + // ), + // ), + // ], + // ), + // ), + // ), + // ); + // }, + // ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/coin_control/coin_control_view.dart b/lib/pages/coin_control/coin_control_view.dart new file mode 100644 index 000000000..5892aa2c9 --- /dev/null +++ b/lib/pages/coin_control/coin_control_view.dart @@ -0,0 +1,780 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/pages/coin_control/utxo_card.dart'; +import 'package:stackwallet/pages/coin_control/utxo_details_view.dart'; +import 'package:stackwallet/providers/global/locale_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/services/mixins/coin_control_interface.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/animated_widgets/rotate_icon.dart'; +import 'package:stackwallet/widgets/app_bar_field.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/custom_buttons/dropdown_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/expandable2.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/toggle.dart'; +import 'package:tuple/tuple.dart'; + +enum CoinControlViewType { + manage, + use; +} + +class CoinControlView extends ConsumerStatefulWidget { + const CoinControlView({ + Key? key, + required this.walletId, + required this.type, + this.requestedTotal, + this.selectedUTXOs, + }) : super(key: key); + + static const routeName = "/coinControl"; + + final String walletId; + final CoinControlViewType type; + final Amount? requestedTotal; + final Set? selectedUTXOs; + + @override + ConsumerState createState() => _CoinControlViewState(); +} + +class _CoinControlViewState extends ConsumerState { + final searchController = TextEditingController(); + final searchFocus = FocusNode(); + + bool _isSearching = false; + bool _showBlocked = false; + + CCSortDescriptor _sort = CCSortDescriptor.age; + + Map>? _map; + List? _list; + + final Set _selectedAvailable = {}; + final Set _selectedBlocked = {}; + + Future _refreshBalance() async { + final coinControlInterface = ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId) + .wallet as CoinControlInterface; + await coinControlInterface.refreshBalance(notify: true); + } + + @override + void initState() { + if (widget.selectedUTXOs != null) { + _selectedAvailable.addAll(widget.selectedUTXOs!); + } + searchController.addListener(() { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + setState(() {}); + }); + }); + super.initState(); + } + + @override + void dispose() { + searchController.dispose(); + searchFocus.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + + final coin = ref.watch( + walletsChangeNotifierProvider.select( + (value) => value + .getManager( + widget.walletId, + ) + .coin, + ), + ); + + final currentChainHeight = ref.watch( + walletsChangeNotifierProvider.select( + (value) => value + .getManager( + widget.walletId, + ) + .currentHeight, + ), + ); + + if (_sort == CCSortDescriptor.address && !_isSearching) { + _list = null; + _map = MainDB.instance.queryUTXOsGroupedByAddressSync( + walletId: widget.walletId, + filter: CCFilter.all, + sort: _sort, + searchTerm: "", + coin: coin, + ); + } else { + _map = null; + _list = MainDB.instance.queryUTXOsSync( + walletId: widget.walletId, + filter: _isSearching + ? CCFilter.all + : _showBlocked + ? CCFilter.frozen + : CCFilter.available, + sort: _sort, + searchTerm: _isSearching ? searchController.text : "", + coin: coin, + ); + } + + return WillPopScope( + onWillPop: () async { + unawaited(_refreshBalance()); + Navigator.of(context).pop( + widget.type == CoinControlViewType.use ? _selectedAvailable : null); + return false; + }, + child: Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + automaticallyImplyLeading: false, + leading: _isSearching + ? null + : widget.type == CoinControlViewType.use && + _selectedAvailable.isNotEmpty + ? AppBarIconButton( + icon: XIcon( + width: 24, + height: 24, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, + ), + onPressed: () { + setState(() { + _selectedAvailable.clear(); + }); + }, + ) + : AppBarBackButton( + onPressed: () { + unawaited(_refreshBalance()); + Navigator.of(context).pop( + widget.type == CoinControlViewType.use + ? _selectedAvailable + : null); + }, + ), + title: _isSearching + ? AppBarSearchField( + controller: searchController, + focusNode: searchFocus, + ) + : Text( + "Coin control", + style: STextStyles.navBarTitle(context), + ), + titleSpacing: 0, + actions: _isSearching + ? [ + AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + size: 36, + icon: SvgPicture.asset( + Assets.svg.x, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, + ), + onPressed: () { + // show search + setState(() { + _isSearching = false; + }); + }, + ), + ), + ] + : [ + AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + size: 36, + icon: SvgPicture.asset( + Assets.svg.search, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, + ), + onPressed: () { + // show search + setState(() { + _isSearching = true; + }); + }, + ), + ), + AspectRatio( + aspectRatio: 1, + child: JDropdownIconButton( + mobileAppBar: true, + groupValue: _sort, + items: CCSortDescriptor.values.toSet(), + onSelectionChanged: (CCSortDescriptor? newValue) { + if (newValue != null && newValue != _sort) { + setState(() { + _sort = newValue; + }); + } + }, + displayPrefix: "Sort by", + ), + ), + ], + ), + body: SafeArea( + child: Column( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + child: Column( + children: [ + const SizedBox( + height: 10, + ), + if (!_isSearching) + RoundedWhiteContainer( + child: Text( + "This option allows you to control, freeze, and utilize " + "outputs at your discretion. Tap the output circle to " + "select.", + style: STextStyles.w500_14(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ), + if (!_isSearching) + const SizedBox( + height: 10, + ), + if (!(_isSearching || _map != null)) + SizedBox( + height: 48, + child: Toggle( + key: UniqueKey(), + onColor: Theme.of(context) + .extension()! + .popupBG, + onText: "Available outputs", + offColor: Theme.of(context) + .extension()! + .textFieldDefaultBG, + offText: "Frozen outputs", + isOn: _showBlocked, + onValueChanged: (value) { + setState(() { + _showBlocked = value; + }); + }, + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + ), + if (!_isSearching) + const SizedBox( + height: 10, + ), + if (_isSearching) + Expanded( + child: ListView.separated( + itemCount: _list!.length, + separatorBuilder: (context, _) => const SizedBox( + height: 10, + ), + itemBuilder: (context, index) { + final utxo = MainDB.instance.isar.utxos + .where() + .idEqualTo(_list![index]) + .findFirstSync()!; + + final isSelected = + _selectedBlocked.contains(utxo) || + _selectedAvailable.contains(utxo); + + return UtxoCard( + key: Key( + "${utxo.walletId}_${utxo.id}_$isSelected"), + walletId: widget.walletId, + utxo: utxo, + canSelect: widget.type == + CoinControlViewType.manage || + (widget.type == CoinControlViewType.use && + !utxo.isBlocked && + utxo.isConfirmed( + currentChainHeight, + coin.requiredConfirmations, + )), + initialSelectedState: isSelected, + onSelectedChanged: (value) { + if (value) { + utxo.isBlocked + ? _selectedBlocked.add(utxo) + : _selectedAvailable.add(utxo); + } else { + utxo.isBlocked + ? _selectedBlocked.remove(utxo) + : _selectedAvailable.remove(utxo); + } + setState(() {}); + }, + onPressed: () async { + final result = + await Navigator.of(context).pushNamed( + UtxoDetailsView.routeName, + arguments: Tuple2( + utxo.id, + widget.walletId, + ), + ); + if (mounted && result == "refresh") { + setState(() {}); + } + }, + ); + }, + ), + ), + if (!_isSearching) + _list != null + ? Expanded( + child: ListView.separated( + itemCount: _list!.length, + separatorBuilder: (context, _) => + const SizedBox( + height: 10, + ), + itemBuilder: (context, index) { + final utxo = MainDB.instance.isar.utxos + .where() + .idEqualTo(_list![index]) + .findFirstSync()!; + + final isSelected = _showBlocked + ? _selectedBlocked.contains(utxo) + : _selectedAvailable.contains(utxo); + + return UtxoCard( + key: Key( + "${utxo.walletId}_${utxo.id}_$isSelected"), + walletId: widget.walletId, + utxo: utxo, + canSelect: widget.type == + CoinControlViewType.manage || + (widget.type == + CoinControlViewType.use && + !_showBlocked && + utxo.isConfirmed( + currentChainHeight, + coin.requiredConfirmations, + )), + initialSelectedState: isSelected, + onSelectedChanged: (value) { + if (value) { + _showBlocked + ? _selectedBlocked.add(utxo) + : _selectedAvailable.add(utxo); + } else { + _showBlocked + ? _selectedBlocked.remove(utxo) + : _selectedAvailable + .remove(utxo); + } + setState(() {}); + }, + onPressed: () async { + final result = + await Navigator.of(context) + .pushNamed( + UtxoDetailsView.routeName, + arguments: Tuple2( + utxo.id, + widget.walletId, + ), + ); + if (mounted && result == "refresh") { + setState(() {}); + } + }, + ); + }, + ), + ) + : Expanded( + child: ListView.separated( + itemCount: _map!.entries.length, + separatorBuilder: (context, _) => + const SizedBox( + height: 10, + ), + itemBuilder: (context, index) { + final entry = + _map!.entries.elementAt(index); + final _controller = + RotateIconController(); + + return Expandable2( + border: Theme.of(context) + .extension()! + .backgroundAppBar, + background: Theme.of(context) + .extension()! + .popupBG, + animationDurationMultiplier: + 0.2 * entry.value.length, + onExpandWillChange: (state) { + if (state == + Expandable2State.expanded) { + _controller.forward?.call(); + } else { + _controller.reverse?.call(); + } + }, + header: RoundedContainer( + padding: const EdgeInsets.all(14), + color: Colors.transparent, + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + entry.key, + style: + STextStyles.w600_14( + context), + ), + const SizedBox( + height: 2, + ), + Text( + "${entry.value.length} " + "output${entry.value.length > 1 ? "s" : ""}", + style: + STextStyles.w500_12( + context) + .copyWith( + color: Theme.of(context) + .extension< + StackColors>()! + .textSubtitle1, + ), + ), + ], + ), + ), + RotateIcon( + animationDurationMultiplier: + 0.2 * entry.value.length, + icon: SvgPicture.asset( + Assets.svg.chevronDown, + width: 14, + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + curve: Curves.easeInOut, + controller: _controller, + ), + ], + ), + ), + children: entry.value.map( + (id) { + final utxo = MainDB + .instance.isar.utxos + .where() + .idEqualTo(id) + .findFirstSync()!; + + final isSelected = _selectedBlocked + .contains(utxo) || + _selectedAvailable + .contains(utxo); + + return UtxoCard( + key: Key( + "${utxo.walletId}_${utxo.id}_$isSelected"), + walletId: widget.walletId, + utxo: utxo, + canSelect: widget.type == + CoinControlViewType + .manage || + (widget.type == + CoinControlViewType + .use && + !utxo.isBlocked && + utxo.isConfirmed( + currentChainHeight, + coin.requiredConfirmations, + )), + initialSelectedState: isSelected, + onSelectedChanged: (value) { + if (value) { + utxo.isBlocked + ? _selectedBlocked + .add(utxo) + : _selectedAvailable + .add(utxo); + } else { + utxo.isBlocked + ? _selectedBlocked + .remove(utxo) + : _selectedAvailable + .remove(utxo); + } + setState(() {}); + }, + onPressed: () async { + final result = + await Navigator.of(context) + .pushNamed( + UtxoDetailsView.routeName, + arguments: Tuple2( + utxo.id, + widget.walletId, + ), + ); + if (mounted && + result == "refresh") { + setState(() {}); + } + }, + ); + }, + ).toList(), + ); + }, + ), + ), + ], + ), + ), + ), + if (((_showBlocked && _selectedBlocked.isNotEmpty) || + (!_showBlocked && _selectedAvailable.isNotEmpty)) && + widget.type == CoinControlViewType.manage) + Container( + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .backgroundAppBar, + boxShadow: [ + Theme.of(context) + .extension()! + .standardBoxShadow, + ], + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: SecondaryButton( + label: _showBlocked ? "Unfreeze" : "Freeze", + onPressed: () async { + if (_showBlocked) { + await MainDB.instance.putUTXOs(_selectedBlocked + .map( + (e) => e.copyWith( + isBlocked: false, + ), + ) + .toList()); + _selectedBlocked.clear(); + } else { + await MainDB.instance.putUTXOs(_selectedAvailable + .map( + (e) => e.copyWith( + isBlocked: true, + ), + ) + .toList()); + _selectedAvailable.clear(); + } + setState(() {}); + }, + ), + ), + ), + if (!_showBlocked && widget.type == CoinControlViewType.use) + Container( + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .backgroundAppBar, + boxShadow: [ + Theme.of(context) + .extension()! + .standardBoxShadow, + ], + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(12), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Selected amount", + style: STextStyles.w600_14(context), + ), + Builder( + builder: (context) { + final int selectedSumInt = + _selectedAvailable.isEmpty + ? 0 + : _selectedAvailable + .map((e) => e.value) + .reduce( + (value, element) => + value += element, + ); + final selectedSum = + selectedSumInt.toAmountAsRaw( + fractionDigits: coin.decimals, + ); + return Text( + "${selectedSum.localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider + .select( + (value) => value.locale, + ), + ), + )} ${coin.ticker}", + style: widget.requestedTotal == null + ? STextStyles.w600_14(context) + : STextStyles.w600_14(context).copyWith( + color: selectedSum >= + widget + .requestedTotal! + ? Theme.of(context) + .extension< + StackColors>()! + .accentColorGreen + : Theme.of(context) + .extension< + StackColors>()! + .accentColorRed), + ); + }, + ), + ], + ), + ), + if (widget.requestedTotal != null) + Container( + width: double.infinity, + height: 1.5, + color: Theme.of(context) + .extension()! + .backgroundAppBar, + ), + if (widget.requestedTotal != null) + Padding( + padding: const EdgeInsets.all(12), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Amount to send", + style: STextStyles.w600_14(context), + ), + Text( + "${widget.requestedTotal!.localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider + .select( + (value) => value.locale, + ), + ), + )} ${coin.ticker}", + style: STextStyles.w600_14(context), + ), + ], + ), + ), + ], + ), + ), + const SizedBox( + height: 12, + ), + PrimaryButton( + label: "Use coins", + enabled: _selectedAvailable.isNotEmpty, + onPressed: () async { + if (searchFocus.hasFocus) { + searchFocus.unfocus(); + } + Navigator.of(context).pop( + _selectedAvailable, + ); + }, + ), + ], + ), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/coin_control/utxo_card.dart b/lib/pages/coin_control/utxo_card.dart new file mode 100644 index 000000000..265a6de79 --- /dev/null +++ b/lib/pages/coin_control/utxo_card.dart @@ -0,0 +1,166 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/providers/global/locale_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/icon_widgets/utxo_status_icon.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; + +class UtxoCard extends ConsumerStatefulWidget { + const UtxoCard({ + Key? key, + required this.utxo, + required this.walletId, + required this.onSelectedChanged, + required this.initialSelectedState, + required this.canSelect, + this.onPressed, + }) : super(key: key); + + final String walletId; + final UTXO utxo; + final void Function(bool) onSelectedChanged; + final bool initialSelectedState; + final VoidCallback? onPressed; + final bool canSelect; + + @override + ConsumerState createState() => _UtxoCardState(); +} + +class _UtxoCardState extends ConsumerState { + late Stream stream; + late UTXO utxo; + + late bool _selected; + + @override + void initState() { + _selected = widget.initialSelectedState; + utxo = widget.utxo; + + stream = MainDB.instance.watchUTXO(id: utxo.id); + super.initState(); + } + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + + final coin = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(widget.walletId).coin)); + + final currentChainHeight = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(widget.walletId).currentHeight)); + + return ConditionalParent( + condition: widget.onPressed != null, + builder: (child) => MaterialButton( + padding: const EdgeInsets.all(0), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + color: Theme.of(context).extension()!.popupBG, + elevation: 0, + disabledElevation: 0, + hoverElevation: 0, + focusElevation: 0, + highlightElevation: 0, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(Constants.size.circularBorderRadius), + ), + onPressed: widget.onPressed, + child: child, + ), + child: RoundedContainer( + color: widget.onPressed == null + ? Theme.of(context).extension()!.popupBG + : Colors.transparent, + child: StreamBuilder( + stream: stream, + builder: (context, snapshot) { + if (snapshot.hasData) { + utxo = snapshot.data!; + } + return Row( + children: [ + ConditionalParent( + condition: widget.canSelect, + builder: (child) => GestureDetector( + onTap: () { + _selected = !_selected; + widget.onSelectedChanged(_selected); + setState(() {}); + }, + child: child, + ), + child: UTXOStatusIcon( + blocked: utxo.isBlocked, + status: utxo.isConfirmed( + currentChainHeight, + coin.requiredConfirmations, + ) + ? UTXOStatusIconStatus.confirmed + : UTXOStatusIconStatus.unconfirmed, + background: + Theme.of(context).extension()!.popupBG, + selected: _selected, + width: 32, + height: 32, + ), + ), + const SizedBox( + width: 10, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "${utxo.value.toAmountAsRaw( + fractionDigits: coin.decimals, + ).localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), + ), + )} ${coin.ticker}", + style: STextStyles.w600_14(context), + ), + const SizedBox( + height: 2, + ), + Row( + children: [ + Flexible( + child: Text( + utxo.name.isNotEmpty + ? utxo.name + : utxo.address ?? utxo.txid, + style: STextStyles.w500_12(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ), + ], + ), + ], + ), + ), + ], + ); + }), + ), + ); + } +} diff --git a/lib/pages/coin_control/utxo_details_view.dart b/lib/pages/coin_control/utxo_details_view.dart new file mode 100644 index 000000000..53ba74b38 --- /dev/null +++ b/lib/pages/coin_control/utxo_details_view.dart @@ -0,0 +1,570 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart'; +import 'package:stackwallet/providers/global/locale_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart'; +import 'package:stackwallet/widgets/custom_buttons/simple_edit_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/icon_widgets/utxo_status_icon.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; + +class UtxoDetailsView extends ConsumerStatefulWidget { + const UtxoDetailsView({ + Key? key, + required this.utxoId, + required this.walletId, + }) : super(key: key); + + static const routeName = "/utxoDetails"; + + final Id utxoId; + final String walletId; + + @override + ConsumerState createState() => _UtxoDetailsViewState(); +} + +class _UtxoDetailsViewState extends ConsumerState { + final isDesktop = Util.isDesktop; + + late Stream streamUTXO; + UTXO? utxo; + + Stream? streamLabel; + AddressLabel? label; + + bool _popWithRefresh = false; + + Future _toggleFreeze() async { + _popWithRefresh = true; + await MainDB.instance.putUTXO(utxo!.copyWith(isBlocked: !utxo!.isBlocked)); + } + + @override + void initState() { + utxo = MainDB.instance.isar.utxos + .where() + .idEqualTo(widget.utxoId) + .findFirstSync()!; + + streamUTXO = MainDB.instance.watchUTXO(id: widget.utxoId); + + if (utxo?.address != null) { + label = MainDB.instance.getAddressLabelSync( + widget.walletId, + utxo!.address!, + ); + + if (label != null) { + streamLabel = MainDB.instance.watchAddressLabel(id: label!.id); + } + } + + super.initState(); + } + + @override + Widget build(BuildContext context) { + final coin = ref.watch( + walletsChangeNotifierProvider.select( + (value) => value.getManager(widget.walletId).coin, + ), + ); + + final currentHeight = ref.watch( + walletsChangeNotifierProvider.select( + (value) => value.getManager(widget.walletId).currentHeight, + ), + ); + + final confirmed = utxo!.isConfirmed( + currentHeight, + coin.requiredConfirmations, + ); + + return ConditionalParent( + condition: !isDesktop, + builder: (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(_popWithRefresh ? "refresh" : null); + }, + ), + title: Text( + "Output details", + style: STextStyles.navBarTitle(context), + ), + ), + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: child, + ), + ), + ), + ); + }, + ), + ), + ), + ), + child: StreamBuilder( + stream: streamUTXO, + builder: (context, snapshot) { + if (snapshot.hasData) { + utxo = snapshot.data!; + } + return ConditionalParent( + condition: isDesktop, + builder: (child) { + return DesktopDialog( + maxHeight: double.infinity, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Output details", + style: STextStyles.desktopH3(context), + ), + ), + DesktopDialogCloseButton( + onPressedOverride: () { + Navigator.of(context) + .pop(_popWithRefresh ? "refresh" : null); + }, + ), + ], + ), + IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + top: 10, + ), + child: Column( + children: [ + IntrinsicHeight( + child: RoundedContainer( + padding: EdgeInsets.zero, + color: Colors.transparent, + borderColor: Theme.of(context) + .extension()! + .textFieldDefaultBG, + child: child, + ), + ), + const SizedBox( + height: 20, + ), + SecondaryButton( + buttonHeight: ButtonHeight.l, + label: utxo!.isBlocked ? "Unfreeze" : "Freeze", + onPressed: _toggleFreeze, + ), + ], + ), + ), + ), + ], + ), + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (!isDesktop) + const SizedBox( + height: 10, + ), + RoundedContainer( + padding: const EdgeInsets.all(12), + color: isDesktop + ? Colors.transparent + : Theme.of(context).extension()!.popupBG, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + if (isDesktop) + UTXOStatusIcon( + blocked: utxo!.isBlocked, + status: confirmed + ? UTXOStatusIconStatus.confirmed + : UTXOStatusIconStatus.unconfirmed, + background: Theme.of(context) + .extension()! + .popupBG, + selected: false, + width: 32, + height: 32, + ), + if (isDesktop) + const SizedBox( + width: 16, + ), + Text( + "${utxo!.value.toAmountAsRaw(fractionDigits: coin.decimals).localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), + ), + )} ${coin.ticker}", + style: STextStyles.pageTitleH2(context), + ), + ], + ), + Text( + utxo!.isBlocked + ? "Frozen" + : confirmed + ? "Available" + : "Unconfirmed", + style: STextStyles.w500_14(context).copyWith( + color: utxo!.isBlocked + ? const Color(0xFF7FA2D4) // todo theme + : confirmed + ? Theme.of(context) + .extension()! + .accentColorGreen + : Theme.of(context) + .extension()! + .accentColorYellow, + ), + ), + ], + ), + ), + const _Div(), + RoundedContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: isDesktop + ? Colors.transparent + : Theme.of(context).extension()!.popupBG, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Label", + style: STextStyles.w500_14(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + SimpleEditButton( + editValue: utxo!.name, + editLabel: "label", + onValueChanged: (newName) { + MainDB.instance.putUTXO( + utxo!.copyWith( + name: newName, + ), + ); + }, + ), + ], + ), + const SizedBox( + height: 4, + ), + Text( + utxo!.name, + style: STextStyles.w500_14(context), + ), + ], + ), + ), + const _Div(), + RoundedContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: isDesktop + ? Colors.transparent + : Theme.of(context).extension()!.popupBG, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Address", + style: STextStyles.w500_14(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + isDesktop + ? IconCopyButton( + data: utxo!.address!, + ) + : SimpleCopyButton( + data: utxo!.address!, + ), + ], + ), + const SizedBox( + height: 4, + ), + Text( + utxo!.address!, + style: STextStyles.w500_14(context), + ), + ], + ), + ), + if (label != null && label!.value.isNotEmpty) const _Div(), + if (label != null && label!.value.isNotEmpty) + RoundedContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: isDesktop + ? Colors.transparent + : Theme.of(context).extension()!.popupBG, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Address label", + style: STextStyles.w500_14(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + isDesktop + ? IconCopyButton( + data: label!.value, + ) + : SimpleCopyButton( + data: label!.value, + ), + ], + ), + const SizedBox( + height: 4, + ), + Text( + label!.value, + style: STextStyles.w500_14(context), + ), + ], + ), + ), + const _Div(), + RoundedContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: isDesktop + ? Colors.transparent + : Theme.of(context).extension()!.popupBG, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Transaction ID", + style: STextStyles.w500_14(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + isDesktop + ? IconCopyButton( + data: utxo!.txid, + ) + : SimpleCopyButton( + data: utxo!.txid, + ), + ], + ), + const SizedBox( + height: 4, + ), + Text( + utxo!.txid, + style: STextStyles.w500_14(context), + ), + ], + ), + ), + const _Div(), + RoundedContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: isDesktop + ? Colors.transparent + : Theme.of(context).extension()!.popupBG, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Confirmations", + style: STextStyles.w500_14(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + const SizedBox( + height: 4, + ), + Text( + "${utxo!.getConfirmations(currentHeight)}", + style: STextStyles.w500_14(context), + ), + ], + ), + ), + if (utxo!.isBlocked) const _Div(), + if (utxo!.isBlocked) + Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + RoundedContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + color: isDesktop + ? Colors.transparent + : Theme.of(context) + .extension()! + .popupBG, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Freeze reason", + style: STextStyles.w500_14(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + SimpleEditButton( + editValue: utxo!.blockedReason ?? "", + editLabel: "freeze reason", + onValueChanged: (newReason) { + MainDB.instance.putUTXO( + utxo!.copyWith( + blockedReason: newReason, + ), + ); + }, + ), + ], + ), + const SizedBox( + height: 4, + ), + Text( + utxo!.blockedReason ?? "", + style: STextStyles.w500_14(context), + ), + ], + ), + ), + if (!isDesktop) const _Div(), + ], + ), + if (!isDesktop) const Spacer(), + if (!isDesktop) + SecondaryButton( + label: utxo!.isBlocked ? "Unfreeze" : "Freeze", + onPressed: _toggleFreeze, + ), + if (!isDesktop) + const SizedBox( + height: 16, + ), + ], + ), + ); + }, + ), + ); + } +} + +class _Div extends StatelessWidget { + const _Div({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + if (Util.isDesktop) { + return Container( + width: double.infinity, + height: 1.0, + color: Theme.of(context).extension()!.textFieldDefaultBG, + ); + } else { + return const SizedBox( + height: 12, + ); + } + } +} diff --git a/lib/pages/exchange_view/choose_from_stack_view.dart b/lib/pages/exchange_view/choose_from_stack_view.dart index 7c7669430..5e32a8c27 100644 --- a/lib/pages/exchange_view/choose_from_stack_view.dart +++ b/lib/pages/exchange_view/choose_from_stack_view.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; -import 'package:stackwallet/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance_future.dart'; +import 'package:stackwallet/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance.dart'; import 'package:stackwallet/widgets/wallet_info_row/sub_widgets/wallet_info_row_coin_icon.dart'; class ChooseFromStackView extends ConsumerStatefulWidget { @@ -112,7 +112,7 @@ class _ChooseFromStackViewState extends ConsumerState { const SizedBox( height: 2, ), - WalletInfoRowBalanceFuture( + WalletInfoRowBalance( walletId: walletIds[index], ), ], diff --git a/lib/pages/exchange_view/confirm_change_now_send.dart b/lib/pages/exchange_view/confirm_change_now_send.dart index 86599f2e5..ce24fd0da 100644 --- a/lib/pages/exchange_view/confirm_change_now_send.dart +++ b/lib/pages/exchange_view/confirm_change_now_send.dart @@ -11,11 +11,11 @@ import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; @@ -61,34 +61,59 @@ class _ConfirmChangeNowSendViewState late final String routeOnSuccessName; late final Trade trade; + final isDesktop = Util.isDesktop; + Future _attemptSend(BuildContext context) async { + final manager = + ref.read(walletsChangeNotifierProvider).getManager(walletId); + + final sendProgressController = ProgressAndSuccessController(); + unawaited( showDialog( context: context, useSafeArea: false, barrierDismissible: false, builder: (context) { - return const SendingTransactionDialog(); + return SendingTransactionDialog( + coin: manager.coin, + controller: sendProgressController, + ); }, ), ); + final time = Future.delayed( + const Duration( + milliseconds: 2500, + ), + ); + + late String txid; + Future txidFuture; + final String note = transactionInfo["note"] as String? ?? ""; - final manager = - ref.read(walletsChangeNotifierProvider).getManager(walletId); try { - late final String txid; - if (widget.shouldSendPublicFiroFunds == true) { - txid = await (manager.wallet as FiroWallet) + txidFuture = (manager.wallet as FiroWallet) .confirmSendPublic(txData: transactionInfo); } else { - txid = await manager.confirmSend(txData: transactionInfo); + txidFuture = manager.confirmSend(txData: transactionInfo); } unawaited(manager.refresh()); + final results = await Future.wait([ + txidFuture, + time, + ]); + + sendProgressController.triggerSuccess?.call(); + await Future.delayed(const Duration(seconds: 5)); + + txid = results.first as String; + // save note await ref .read(notesServiceChangeNotifierProvider(walletId)) @@ -135,7 +160,7 @@ class _ConfirmChangeNowSendViewState rightButton: TextButton( style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), child: Text( "Ok", style: STextStyles.button(context).copyWith( @@ -227,8 +252,6 @@ class _ConfirmChangeNowSendViewState final managerProvider = ref.watch(walletsChangeNotifierProvider .select((value) => value.getManagerProvider(walletId))); - final isDesktop = Util.isDesktop; - return ConditionalParent( condition: !isDesktop, builder: (child) { @@ -238,7 +261,7 @@ class _ConfirmChangeNowSendViewState Theme.of(context).extension()!.background, appBar: AppBar( backgroundColor: - Theme.of(context).extension()!.background, + Theme.of(context).extension()!.backgroundAppBar, leading: AppBarBackButton( onPressed: () async { // if (FocusScope.of(context).hasFocus) { @@ -343,14 +366,16 @@ class _ConfirmChangeNowSendViewState mainAxisAlignment: MainAxisAlignment.end, children: [ Text( - "${Format.satoshiAmountToPrettyString( - (transactionInfo["fee"] as int), - ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), - ), - ref.watch( - managerProvider.select((value) => value.coin), + "${(transactionInfo["fee"] is Amount ? transactionInfo["fee"] as Amount : (transactionInfo["fee"] as int).toAmountAsRaw( + fractionDigits: ref.watch( + managerProvider + .select((value) => value.coin.decimals), + ), + )).localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), ), )} ${ref.watch( managerProvider.select((value) => value.coin), @@ -384,26 +409,39 @@ class _ConfirmChangeNowSendViewState .textConfirmTotalAmount, ), ), - Text( - "${Format.satoshiAmountToPrettyString( - (transactionInfo["fee"] as int) + - (transactionInfo["recipientAmt"] as int), - ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), - ), - ref.watch( + Builder( + builder: (context) { + final coin = ref.watch( managerProvider.select((value) => value.coin), - ), - )} ${ref.watch( - managerProvider.select((value) => value.coin), - ).ticker}", - style: STextStyles.itemSubtitle12(context).copyWith( - color: Theme.of(context) - .extension()! - .textConfirmTotalAmount, - ), - textAlign: TextAlign.right, + ); + final fee = transactionInfo["fee"] is Amount + ? transactionInfo["fee"] as Amount + : (transactionInfo["fee"] as int) + .toAmountAsRaw( + fractionDigits: coin.decimals, + ); + final amount = + transactionInfo["recipientAmt"] as Amount; + final total = amount + fee; + final locale = ref.watch( + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), + ); + return Text( + "${total.localizedStringAsFixed( + locale: locale, + )}" + " ${coin.ticker}", + style: STextStyles.itemSubtitle12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textConfirmTotalAmount, + ), + textAlign: TextAlign.right, + ); + }, ), ], ), @@ -554,16 +592,20 @@ class _ConfirmChangeNowSendViewState final price = ref.watch( priceAnd24hChangeNotifierProvider .select((value) => value.getPrice(coin))); - final amount = Format.satoshisToAmount( - transactionInfo["recipientAmt"] as int, - coin: coin, - ); - final value = price.item1 * amount; + final amount = + transactionInfo["recipientAmt"] as Amount; + final value = (price.item1 * amount.decimal) + .toAmount(fractionDigits: 2); final currency = ref.watch(prefsChangeNotifierProvider .select((value) => value.currency)); + final locale = ref.watch( + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), + ); return Text( - " | ${value.toStringAsFixed(Constants.decimalPlacesForCoin(coin))} $currency", + " | ${value.localizedStringAsFixed(locale: locale)} $currency", style: STextStyles.desktopTextExtraExtraSmall(context) .copyWith( @@ -576,12 +618,13 @@ class _ConfirmChangeNowSendViewState ], ), child: Text( - "${Format.satoshiAmountToPrettyString(transactionInfo["recipientAmt"] as int, ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), - ), ref.watch( - managerProvider.select((value) => value.coin), - ))} ${ref.watch( + "${(transactionInfo["recipientAmt"] as Amount).localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), + ), + )} ${ref.watch( managerProvider.select((value) => value.coin), ).ticker}", style: STextStyles.itemSubtitle12(context), @@ -609,12 +652,17 @@ class _ConfirmChangeNowSendViewState style: STextStyles.smallMed12(context), ), Text( - "${Format.satoshiAmountToPrettyString(transactionInfo["fee"] as int, ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), - ), ref.watch( - managerProvider.select((value) => value.coin), - ))} ${ref.watch( + "${(transactionInfo["fee"] is Amount ? transactionInfo["fee"] as Amount : (transactionInfo["fee"] as int).toAmountAsRaw(fractionDigits: ref.watch( + managerProvider.select( + (value) => value.coin.decimals, + ), + ))).localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), + ), + )} ${ref.watch( managerProvider.select((value) => value.coin), ).ticker}", style: STextStyles.itemSubtitle12(context), @@ -695,21 +743,37 @@ class _ConfirmChangeNowSendViewState .textConfirmTotalAmount, ), ), - Text( - "${Format.satoshiAmountToPrettyString((transactionInfo["fee"] as int) + (transactionInfo["recipientAmt"] as int), ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), - ), ref.watch( - managerProvider.select((value) => value.coin), - ))} ${ref.watch( - managerProvider.select((value) => value.coin), - ).ticker}", - style: STextStyles.itemSubtitle12(context).copyWith( - color: Theme.of(context) - .extension()! - .textConfirmTotalAmount, - ), - textAlign: TextAlign.right, + Builder( + builder: (context) { + final coin = ref.watch( + managerProvider.select((value) => value.coin), + ); + final fee = transactionInfo["fee"] is Amount + ? transactionInfo["fee"] as Amount + : (transactionInfo["fee"] as int).toAmountAsRaw( + fractionDigits: coin.decimals, + ); + final amount = + transactionInfo["recipientAmt"] as Amount; + final total = amount + fee; + final locale = ref.watch( + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), + ); + return Text( + "${total.localizedStringAsFixed( + locale: locale, + )}" + " ${coin.ticker}", + style: STextStyles.itemSubtitle12(context).copyWith( + color: Theme.of(context) + .extension()! + .textConfirmTotalAmount, + ), + textAlign: TextAlign.right, + ); + }, ), ], ), diff --git a/lib/pages/exchange_view/edit_trade_note_view.dart b/lib/pages/exchange_view/edit_trade_note_view.dart index 19804b7c5..1878e1385 100644 --- a/lib/pages/exchange_view/edit_trade_note_view.dart +++ b/lib/pages/exchange_view/edit_trade_note_view.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/providers/exchange/trade_note_service_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; @@ -136,7 +136,7 @@ class _EditNoteViewState extends ConsumerState { }, style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), child: Text( "Save", style: STextStyles.button(context), diff --git a/lib/pages/exchange_view/exchange_coin_selection/exchange_currency_selection_view.dart b/lib/pages/exchange_view/exchange_coin_selection/exchange_currency_selection_view.dart new file mode 100644 index 000000000..964ed6068 --- /dev/null +++ b/lib/pages/exchange_view/exchange_coin_selection/exchange_currency_selection_view.dart @@ -0,0 +1,438 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/exceptions/exchange/unsupported_currency_exception.dart'; +import 'package:stackwallet/models/isar/exchange_cache/currency.dart'; +import 'package:stackwallet/models/isar/exchange_cache/pair.dart'; +import 'package:stackwallet/pages/buy_view/sub_widgets/crypto_selection_view.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; +import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart'; +import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/custom_loading_overlay.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/loading_indicator.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; + +class ExchangeCurrencySelectionView extends StatefulWidget { + const ExchangeCurrencySelectionView({ + Key? key, + required this.willChangeTicker, + required this.pairedTicker, + required this.isFixedRate, + required this.willChangeIsSend, + }) : super(key: key); + + final String? willChangeTicker; + final String? pairedTicker; + final bool isFixedRate; + final bool willChangeIsSend; + + @override + State createState() => + _ExchangeCurrencySelectionViewState(); +} + +class _ExchangeCurrencySelectionViewState + extends State { + late TextEditingController _searchController; + final _searchFocusNode = FocusNode(); + final isDesktop = Util.isDesktop; + + List _currencies = []; + + bool _loaded = false; + String _searchString = ""; + + Future _showUpdatingCurrencies({ + required Future whileFuture, + }) async { + unawaited( + showDialog( + context: context, + barrierDismissible: false, + builder: (_) => WillPopScope( + onWillPop: () async => false, + child: Container( + color: Theme.of(context) + .extension()! + .overlay + .withOpacity(0.6), + child: const CustomLoadingOverlay( + message: "Loading currencies", + eventBus: null, + ), + ), + ), + ), + ); + + final result = await whileFuture; + + if (mounted) { + Navigator.of(context, rootNavigator: isDesktop).pop(); + } + + return result; + } + + Future> _loadCurrencies() async { + if (widget.pairedTicker == null) { + return await _getCurrencies(); + } + List currencies = await ExchangeDataLoadingService + .instance.isar.currencies + .where() + .exchangeNameEqualTo(MajesticBankExchange.exchangeName) + .findAll(); + + final cn = await ChangeNowExchange.instance.getPairedCurrencies( + widget.pairedTicker!, + widget.isFixedRate, + ); + + if (cn.value == null) { + if (cn.exception is UnsupportedCurrencyException) { + return currencies; + } + + if (mounted) { + await showDialog( + context: context, + builder: (context) => StackDialog( + title: "ChangeNOW Error", + message: "Failed to load currency data: ${cn.exception}", + leftButton: SecondaryButton( + label: "Ok", + onPressed: Navigator.of(context, rootNavigator: isDesktop).pop, + ), + rightButton: PrimaryButton( + label: "Retry", + onPressed: () async { + Navigator.of(context, rootNavigator: isDesktop).pop(); + _currencies = await _showUpdatingCurrencies( + whileFuture: _loadCurrencies()); + setState(() {}); + }, + ), + ), + ); + } + } else { + currencies.addAll(cn.value!); + } + + return _getDistinctCurrenciesFrom(currencies); + } + + Future> _getCurrencies() async { + final currencies = await ExchangeDataLoadingService.instance.isar.currencies + .where() + .filter() + .isFiatEqualTo(false) + .and() + .group((q) => widget.isFixedRate + ? q + .rateTypeEqualTo(SupportedRateType.both) + .or() + .rateTypeEqualTo(SupportedRateType.fixed) + : q + .rateTypeEqualTo(SupportedRateType.both) + .or() + .rateTypeEqualTo(SupportedRateType.estimated)) + .sortByIsStackCoin() + .thenByName() + .findAll(); + + return _getDistinctCurrenciesFrom(currencies); + } + + List _getDistinctCurrenciesFrom(List currencies) { + final List distinctCurrencies = []; + for (final currency in currencies) { + if (!distinctCurrencies.any((e) => e.ticker == currency.ticker)) { + distinctCurrencies.add(currency); + } + } + return distinctCurrencies; + } + + List filter(String text) { + if (widget.pairedTicker == null) { + if (text.isEmpty) { + return _currencies; + } + + return _currencies + .where((e) => + e.name.toLowerCase().contains(text.toLowerCase()) || + e.ticker.toLowerCase().contains(text.toLowerCase())) + .toList(); + } else { + if (text.isEmpty) { + return _currencies + .where((e) => + e.ticker.toLowerCase() != widget.pairedTicker!.toLowerCase()) + .toList(); + } + + return _currencies + .where((e) => + e.ticker.toLowerCase() != widget.pairedTicker!.toLowerCase() && + (e.name.toLowerCase().contains(text.toLowerCase()) || + e.ticker.toLowerCase().contains(text.toLowerCase()))) + .toList(); + } + } + + @override + void initState() { + _searchController = TextEditingController(); + + super.initState(); + } + + @override + void dispose() { + _searchController.dispose(); + _searchFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (!_loaded) { + _loaded = true; + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + _currencies = + await _showUpdatingCurrencies(whileFuture: _loadCurrencies()); + setState(() {}); + }); + } + + return ConditionalParent( + condition: !isDesktop, + builder: (child) { + return Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 50)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Choose a coin to exchange", + style: STextStyles.pageTitleH2(context), + ), + ), + body: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + child: child, + ), + ), + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: isDesktop ? MainAxisSize.min : MainAxisSize.max, + children: [ + if (!isDesktop) + const SizedBox( + height: 16, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autofocus: isDesktop, + autocorrect: !isDesktop, + enableSuggestions: !isDesktop, + controller: _searchController, + focusNode: _searchFocusNode, + onChanged: (value) => setState(() => _searchString = value), + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Search", + _searchFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 16, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: 16, + height: 16, + ), + ), + suffixIcon: _searchController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchController.text = ""; + _searchString = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), + const SizedBox( + height: 20, + ), + Flexible( + child: Builder( + builder: (context) { + final coins = Coin.values.where((e) => + e.ticker.toLowerCase() != + widget.pairedTicker?.toLowerCase()); + + final items = filter(_searchString); + + final walletCoins = items + .where((currency) => coins + .where((coin) => + coin.ticker.toLowerCase() == + currency.ticker.toLowerCase()) + .isNotEmpty) + .toList(); + + // sort alphabetically by name + items.sort((a, b) => a.name.compareTo(b.name)); + + // reverse sort walletCoins to prepare for next step + walletCoins.sort((a, b) => b.name.compareTo(a.name)); + + // insert wallet coins at beginning + for (final c in walletCoins) { + items.remove(c); + items.insert(0, c); + } + + return RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: ListView.builder( + shrinkWrap: true, + primary: isDesktop ? false : null, + itemCount: items.length, + itemBuilder: (builderContext, index) { + final bool hasImageUrl = + items[index].image.startsWith("http"); + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: GestureDetector( + onTap: () { + Navigator.of(context).pop(items[index]); + }, + child: RoundedWhiteContainer( + child: Row( + children: [ + SizedBox( + width: 24, + height: 24, + child: isStackCoin(items[index].ticker) + ? CoinIconForTicker( + ticker: items[index].ticker, size: 24) + // ? getIconForTicker( + // items[index].ticker, + // size: 24, + // ) + : hasImageUrl + ? SvgPicture.network( + items[index].image, + width: 24, + height: 24, + placeholderBuilder: (_) => + const LoadingIndicator(), + ) + : const SizedBox( + width: 24, + height: 24, + ), + ), + const SizedBox( + width: 10, + ), + Expanded( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + items[index].name, + style: + STextStyles.largeMedium14(context), + ), + const SizedBox( + height: 2, + ), + Text( + items[index].ticker.toUpperCase(), + style: STextStyles.smallMed12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ); + }, + ), + ); + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart b/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart deleted file mode 100644 index 57219dfd0..000000000 --- a/lib/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart +++ /dev/null @@ -1,384 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/models/exchange/response_objects/currency.dart'; -import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; -import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; -import 'package:stackwallet/utilities/util.dart'; -import 'package:stackwallet/widgets/background.dart'; -import 'package:stackwallet/widgets/conditional_parent.dart'; -import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; -import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; -import 'package:stackwallet/widgets/loading_indicator.dart'; -import 'package:stackwallet/widgets/rounded_white_container.dart'; -import 'package:stackwallet/widgets/stack_text_field.dart'; -import 'package:stackwallet/widgets/textfield_icon_button.dart'; -import 'package:tuple/tuple.dart'; - -class FixedRateMarketPairCoinSelectionView extends ConsumerStatefulWidget { - const FixedRateMarketPairCoinSelectionView({ - Key? key, - required this.markets, - required this.currencies, - required this.isFrom, - }) : super(key: key); - - final List markets; - final List currencies; - final bool isFrom; - - @override - ConsumerState createState() => - _FixedRateMarketPairCoinSelectionViewState(); -} - -class _FixedRateMarketPairCoinSelectionViewState - extends ConsumerState { - late TextEditingController _searchController; - final _searchFocusNode = FocusNode(); - - late final List markets; - late List _markets; - - late final bool isFrom; - - Tuple2 _imageUrlAndNameFor(String ticker) { - final matches = widget.currencies.where( - (element) => element.ticker.toLowerCase() == ticker.toLowerCase()); - - if (matches.isNotEmpty) { - return Tuple2(matches.first.image, matches.first.name); - } - return Tuple2("", ticker); - } - - void filter(String text) { - setState(() { - _markets = [ - ...markets.where((e) { - final String ticker = isFrom ? e.from : e.to; - final __currencies = widget.currencies - .where((e) => e.ticker.toLowerCase() == ticker.toLowerCase()); - if (__currencies.isNotEmpty) { - return __currencies.first.name - .toLowerCase() - .contains(text.toLowerCase()) || - ticker.toLowerCase().contains(text.toLowerCase()); - } - return ticker.toLowerCase().contains(text.toLowerCase()); - }) - ]; - }); - } - - @override - void initState() { - _searchController = TextEditingController(); - isFrom = widget.isFrom; - - markets = [...widget.markets]; - if (isFrom) { - markets.sort( - (a, b) => a.from.toLowerCase().compareTo(b.from.toLowerCase()), - ); - for (Coin coin in Coin.values.reversed) { - int index = markets.indexWhere((element) => - element.from.toLowerCase() == coin.ticker.toLowerCase()); - if (index > 0) { - final market = markets.removeAt(index); - markets.insert(0, market); - } - } - } else { - markets.sort( - (a, b) => a.to.toLowerCase().compareTo(b.to.toLowerCase()), - ); - for (Coin coin in Coin.values.reversed) { - int index = markets.indexWhere( - (element) => element.to.toLowerCase() == coin.ticker.toLowerCase()); - if (index > 0) { - final market = markets.removeAt(index); - markets.insert(0, market); - } - } - } - - _markets = [...markets]; - - super.initState(); - } - - @override - void dispose() { - _searchController.dispose(); - _searchFocusNode.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - final isDesktop = Util.isDesktop; - return ConditionalParent( - condition: !isDesktop, - builder: (child) { - return Background( - child: Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 50)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, - ), - title: Text( - "Choose a coin to exchange", - style: STextStyles.pageTitleH2(context), - ), - ), - body: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16, - ), - child: child, - ), - ), - ); - }, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (!isDesktop) - const SizedBox( - height: 16, - ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - autofocus: isDesktop, - autocorrect: !isDesktop, - enableSuggestions: !isDesktop, - controller: _searchController, - focusNode: _searchFocusNode, - onChanged: filter, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Search", - _searchFocusNode, - context, - ).copyWith( - prefixIcon: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 10, - vertical: 16, - ), - child: SvgPicture.asset( - Assets.svg.search, - width: 16, - height: 16, - ), - ), - suffixIcon: _searchController.text.isNotEmpty - ? Padding( - padding: const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - children: [ - TextFieldIconButton( - child: const XIcon(), - onTap: () async { - setState(() { - _searchController.text = ""; - }); - }, - ), - ], - ), - ), - ) - : null, - ), - ), - ), - const SizedBox( - height: 10, - ), - Text( - "Popular coins", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 12, - ), - Flexible( - child: Builder(builder: (context) { - final items = _markets - .where((e) => Coin.values - .where((coin) => - coin.ticker.toLowerCase() == - (isFrom ? e.from.toLowerCase() : e.to.toLowerCase())) - .isNotEmpty) - .toList(growable: false); - - return RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: ListView.builder( - shrinkWrap: true, - primary: isDesktop ? false : null, - itemCount: items.length, - itemBuilder: (builderContext, index) { - final String ticker = - isFrom ? items[index].from : items[index].to; - - final tuple = _imageUrlAndNameFor(ticker); - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: GestureDetector( - onTap: () { - Navigator.of(context).pop(ticker); - }, - child: RoundedWhiteContainer( - child: Row( - children: [ - SizedBox( - width: 24, - height: 24, - child: SvgPicture.network( - tuple.item1, - width: 24, - height: 24, - placeholderBuilder: (_) => - const LoadingIndicator(), - ), - ), - const SizedBox( - width: 10, - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - tuple.item2, - style: STextStyles.largeMedium14(context), - ), - const SizedBox( - height: 2, - ), - Text( - ticker.toUpperCase(), - style: STextStyles.smallMed12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), - ], - ), - ), - ], - ), - ), - ), - ); - }, - ), - ); - }), - ), - const SizedBox( - height: 20, - ), - Text( - "All coins", - style: STextStyles.smallMed12(context), - ), - const SizedBox( - height: 12, - ), - Flexible( - child: RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: ListView.builder( - shrinkWrap: true, - primary: isDesktop ? false : null, - itemCount: _markets.length, - itemBuilder: (builderContext, index) { - final String ticker = - isFrom ? _markets[index].from : _markets[index].to; - - final tuple = _imageUrlAndNameFor(ticker); - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: GestureDetector( - onTap: () { - Navigator.of(context).pop(ticker); - }, - child: RoundedWhiteContainer( - child: Row( - children: [ - SizedBox( - width: 24, - height: 24, - child: SvgPicture.network( - tuple.item1, - width: 24, - height: 24, - placeholderBuilder: (_) => - const LoadingIndicator(), - ), - ), - const SizedBox( - width: 10, - ), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - tuple.item2, - style: STextStyles.largeMedium14(context), - ), - const SizedBox( - height: 2, - ), - Text( - ticker.toUpperCase(), - style: STextStyles.smallMed12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), - ], - ), - ), - ], - ), - ), - ), - ); - }, - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/pages/exchange_view/exchange_form.dart b/lib/pages/exchange_view/exchange_form.dart index f584e16e5..2604d89af 100644 --- a/lib/pages/exchange_view/exchange_form.dart +++ b/lib/pages/exchange_view/exchange_form.dart @@ -5,28 +5,31 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/models/exchange/aggregate_currency.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; -import 'package:stackwallet/models/exchange/response_objects/currency.dart'; -import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; -import 'package:stackwallet/models/exchange/response_objects/pair.dart'; -import 'package:stackwallet/notifications/show_flush_bar.dart'; -import 'package:stackwallet/pages/exchange_view/exchange_coin_selection/fixed_rate_pair_coin_selection_view.dart'; -import 'package:stackwallet/pages/exchange_view/exchange_coin_selection/floating_rate_currency_selection_view.dart'; +import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; +import 'package:stackwallet/models/exchange/response_objects/range.dart'; +import 'package:stackwallet/models/isar/exchange_cache/currency.dart'; +import 'package:stackwallet/models/isar/exchange_cache/pair.dart'; +import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; +import 'package:stackwallet/pages/exchange_view/exchange_coin_selection/exchange_currency_selection_view.dart'; import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_1_view.dart'; import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_2_view.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_provider_options.dart'; -import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/rate_type_toggle.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; -import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; +import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart'; +import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart'; +import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_loading_overlay.dart'; @@ -34,22 +37,26 @@ import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; -import 'package:stackwallet/widgets/desktop/simple_desktop_dialog.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:stackwallet/widgets/textfields/exchange_textfield.dart'; import 'package:tuple/tuple.dart'; +import 'package:uuid/uuid.dart'; + +import '../../services/exchange/exchange_response.dart'; class ExchangeForm extends ConsumerStatefulWidget { const ExchangeForm({ Key? key, this.walletId, this.coin, + this.contract, }) : super(key: key); final String? walletId; final Coin? coin; + final EthContract? contract; @override ConsumerState createState() => _ExchangeFormState(); @@ -60,6 +67,12 @@ class _ExchangeFormState extends ConsumerState { late final Coin? coin; late final bool walletInitiated; + final exchanges = [ + MajesticBankExchange.instance, + ChangeNowExchange.instance, + TrocadorExchange.instance, + ]; + late final TextEditingController _sendController; late final TextEditingController _receiveController; final isDesktop = Util.isDesktop; @@ -68,246 +81,12 @@ class _ExchangeFormState extends ConsumerState { bool _swapLock = false; - void sendFieldOnChanged(String value) async { - final newFromAmount = Decimal.tryParse(value); - - ref.read(exchangeFormStateProvider).fromAmount = - newFromAmount ?? Decimal.zero; - - if (newFromAmount == null) { - _receiveController.text = - ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated - ? "-" - : ""; - } - } - - void selectSendCurrency() async { - if (ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated) { - final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? "-"; - // ref.read(estimatedRateExchangeFormProvider).from?.ticker ?? "-"; - - if (walletInitiated && - fromTicker.toLowerCase() == coin!.ticker.toLowerCase()) { - // do not allow changing away from wallet coin - return; - } - - List currencies; - switch (ref.read(currentExchangeNameStateProvider.state).state) { - case ChangeNowExchange.exchangeName: - currencies = - ref.read(availableChangeNowCurrenciesProvider).currencies; - break; - case SimpleSwapExchange.exchangeName: - currencies = ref - .read(availableSimpleswapCurrenciesProvider) - .floatingRateCurrencies; - break; - default: - currencies = []; - } - - await _showFloatingRateSelectionSheet( - currencies: currencies, - excludedTicker: ref.read(exchangeFormStateProvider).toTicker ?? "-", - fromTicker: fromTicker, - onSelected: (from) => - ref.read(exchangeFormStateProvider).updateFrom(from, true)); - } else { - final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? ""; - final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? ""; - - if (walletInitiated && - fromTicker.toLowerCase() == coin!.ticker.toLowerCase()) { - // do not allow changing away from wallet coin - return; - } - - switch (ref.read(currentExchangeNameStateProvider.state).state) { - case ChangeNowExchange.exchangeName: - await _showFixedRateSelectionSheet( - excludedTicker: toTicker, - fromTicker: fromTicker, - onSelected: (selectedFromTicker) async { - try { - final market = ref - .read(availableChangeNowCurrenciesProvider) - .markets - .firstWhere( - (e) => e.to == toTicker && e.from == selectedFromTicker, - ); - - await ref - .read(exchangeFormStateProvider) - .updateMarket(market, true); - } catch (e) { - unawaited( - showDialog( - context: context, - builder: (_) { - if (isDesktop) { - return const SimpleDesktopDialog( - title: "Fixed rate market error", - message: - "Could not find the specified fixed rate trade pair", - ); - } else { - return const StackDialog( - title: "Fixed rate market error", - message: - "Could not find the specified fixed rate trade pair", - ); - } - }, - ), - ); - - return; - } - }, - ); - break; - case SimpleSwapExchange.exchangeName: - await _showFloatingRateSelectionSheet( - currencies: ref - .read(availableSimpleswapCurrenciesProvider) - .fixedRateCurrencies, - excludedTicker: - ref.read(exchangeFormStateProvider).toTicker ?? "-", - fromTicker: fromTicker, - onSelected: (from) => - ref.read(exchangeFormStateProvider).updateFrom(from, true)); - break; - default: - // TODO show error? - } - } - } - - void selectReceiveCurrency() async { - if (ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated) { - final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? ""; - - if (walletInitiated && - toTicker.toLowerCase() == coin!.ticker.toLowerCase()) { - // do not allow changing away from wallet coin - return; - } - - List currencies; - switch (ref.read(currentExchangeNameStateProvider.state).state) { - case ChangeNowExchange.exchangeName: - currencies = - ref.read(availableChangeNowCurrenciesProvider).currencies; - break; - case SimpleSwapExchange.exchangeName: - currencies = ref - .read(availableSimpleswapCurrenciesProvider) - .floatingRateCurrencies; - break; - default: - currencies = []; - } - - await _showFloatingRateSelectionSheet( - currencies: currencies, - excludedTicker: ref.read(exchangeFormStateProvider).fromTicker ?? "", - fromTicker: ref.read(exchangeFormStateProvider).fromTicker ?? "", - onSelected: (to) => - ref.read(exchangeFormStateProvider).updateTo(to, true)); - } else { - final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? ""; - final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? ""; - - if (walletInitiated && - toTicker.toLowerCase() == coin!.ticker.toLowerCase()) { - // do not allow changing away from wallet coin - return; - } - - switch (ref.read(currentExchangeNameStateProvider.state).state) { - case ChangeNowExchange.exchangeName: - await _showFixedRateSelectionSheet( - excludedTicker: fromTicker, - fromTicker: fromTicker, - onSelected: (selectedToTicker) async { - try { - final market = ref - .read(availableChangeNowCurrenciesProvider) - .markets - .firstWhere( - (e) => e.to == selectedToTicker && e.from == fromTicker, - ); - - await ref - .read(exchangeFormStateProvider) - .updateMarket(market, true); - } catch (e) { - unawaited( - showDialog( - context: context, - builder: (_) { - if (isDesktop) { - return const SimpleDesktopDialog( - title: "Fixed rate market error", - message: - "Could not find the specified fixed rate trade pair", - ); - } else { - return const StackDialog( - title: "Fixed rate market error", - message: - "Could not find the specified fixed rate trade pair", - ); - } - }, - ), - ); - return; - } - }, - ); - break; - case SimpleSwapExchange.exchangeName: - await _showFloatingRateSelectionSheet( - currencies: ref - .read(availableSimpleswapCurrenciesProvider) - .fixedRateCurrencies, - excludedTicker: - ref.read(exchangeFormStateProvider).fromTicker ?? "", - fromTicker: ref.read(exchangeFormStateProvider).fromTicker ?? "", - onSelected: (to) => - ref.read(exchangeFormStateProvider).updateTo(to, true)); - break; - default: - // TODO show error? - } - } - } - - void receiveFieldOnChanged(String value) async { - final newToAmount = Decimal.tryParse(value); - final isEstimated = - ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated; - if (!isEstimated) { - ref.read(exchangeFormStateProvider).toAmount = - newToAmount ?? Decimal.zero; - } - if (newToAmount == null) { - _sendController.text = ""; - } - } - - Future _swap() async { - _swapLock = true; - _sendFocusNode.unfocus(); - _receiveFocusNode.unfocus(); + // todo: check and adjust this value? + static const _valueCheckInterval = Duration(milliseconds: 1500); + Future showUpdatingExchangeRate({ + required Future whileFuture, + }) async { unawaited( showDialog( context: context, @@ -328,86 +107,200 @@ class _ExchangeFormState extends ConsumerState { ), ); - if (ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.fixed && - ref.read(exchangeFormStateProvider).exchange?.name == - ChangeNowExchange.exchangeName) { - final from = ref.read(exchangeFormStateProvider).fromTicker; - final to = ref.read(exchangeFormStateProvider).toTicker; + final result = await whileFuture; - if (to != null && from != null) { - final markets = ref - .read(availableChangeNowCurrenciesProvider) - .markets - .where((e) => e.from == to && e.to == from); - - if (markets.isNotEmpty) { - await ref.read(exchangeFormStateProvider).swap(market: markets.first); - } else { - Logging.instance.log( - "swap to fixed rate market failed", - level: LogLevel.Warning, - ); - } - } - } else { - await ref.read(exchangeFormStateProvider).swap(); - } if (mounted) { Navigator.of(context, rootNavigator: isDesktop).pop(); } - _swapLock = false; + + return result; } - Future _showFloatingRateSelectionSheet({ - required List currencies, - required String excludedTicker, - required String fromTicker, - required void Function(Currency) onSelected, - }) async { + Timer? _sendFieldOnChangedTimer; + void sendFieldOnChanged(String value) { + if (_sendFocusNode.hasFocus) { + _sendFieldOnChangedTimer?.cancel(); + + _sendFieldOnChangedTimer = Timer(_valueCheckInterval, () async { + final newFromAmount = _localizedStringToNum(value); + + ref.read(efSendAmountProvider.notifier).state = newFromAmount; + if (!_swapLock && !ref.read(efReversedProvider)) { + unawaited(update()); + } + }); + } + } + + Timer? _receiveFieldOnChangedTimer; + void receiveFieldOnChanged(String value) async { + _receiveFieldOnChangedTimer?.cancel(); + + _receiveFieldOnChangedTimer = Timer(_valueCheckInterval, () async { + final newToAmount = _localizedStringToNum(value); + + ref.read(efReceiveAmountProvider.notifier).state = newToAmount; + if (!_swapLock && ref.read(efReversedProvider)) { + unawaited(update()); + } + }); + } + + Decimal? _localizedStringToNum(String? value) { + if (value == null) { + return null; + } + try { + // wtf Dart????? + // This turns "99999999999999999999" into 100000000000000000000.0 + // final numFromLocalised = NumberFormat.decimalPattern( + // ref.read(localeServiceChangeNotifierProvider).locale) + // .parse(value); + // return Decimal.tryParse(numFromLocalised.toString()); + + try { + return Decimal.parse(value); + } catch (_) { + try { + return Decimal.parse(value.replaceAll(",", ".")); + } catch (_) { + rethrow; + } + } + } catch (_) { + return null; + } + } + + Future _getAggregateCurrency(Currency currency) async { + final rateType = ref.read(efRateTypeProvider); + final currencies = await ExchangeDataLoadingService.instance.isar.currencies + .filter() + .group((q) => rateType == ExchangeRateType.fixed + ? q + .rateTypeEqualTo(SupportedRateType.both) + .or() + .rateTypeEqualTo(SupportedRateType.fixed) + : q + .rateTypeEqualTo(SupportedRateType.both) + .or() + .rateTypeEqualTo(SupportedRateType.estimated)) + .and() + .tickerEqualTo( + currency.ticker, + caseSensitive: false, + ) + .and() + .tokenContractEqualTo(currency.tokenContract) + .findAll(); + + final items = [Tuple2(currency.exchangeName, currency)]; + + for (final currency in currencies) { + items.add(Tuple2(currency.exchangeName, currency)); + } + + return AggregateCurrency(exchangeCurrencyPairs: items); + } + + void selectSendCurrency() async { + final type = ref.read(efRateTypeProvider); + final fromTicker = ref.read(efCurrencyPairProvider).send?.ticker ?? ""; + + if (walletInitiated) { + if (widget.contract != null && + fromTicker.toLowerCase() == widget.contract!.symbol.toLowerCase()) { + return; + } + + if (fromTicker.toLowerCase() == coin!.ticker.toLowerCase()) { + // do not allow changing away from wallet coin + return; + } + } + + final selectedCurrency = await _showCurrencySelectionSheet( + willChange: ref.read(efCurrencyPairProvider).send?.ticker, + willChangeIsSend: true, + paired: ref.read(efCurrencyPairProvider).receive?.ticker, + isFixedRate: type == ExchangeRateType.fixed, + ); + + if (selectedCurrency != null) { + await showUpdatingExchangeRate( + whileFuture: _getAggregateCurrency(selectedCurrency).then( + (aggregateSelected) => ref.read(efCurrencyPairProvider).setSend( + aggregateSelected, + notifyListeners: true, + ), + ), + ); + } + } + + void selectReceiveCurrency() async { + final toTicker = ref.read(efCurrencyPairProvider).receive?.ticker ?? ""; + if (walletInitiated && + toTicker.toLowerCase() == coin!.ticker.toLowerCase()) { + // do not allow changing away from wallet coin + return; + } + + final selectedCurrency = await _showCurrencySelectionSheet( + willChange: ref.read(efCurrencyPairProvider).receive?.ticker, + willChangeIsSend: false, + paired: ref.read(efCurrencyPairProvider).send?.ticker, + isFixedRate: ref.read(efRateTypeProvider) == ExchangeRateType.fixed, + ); + + if (selectedCurrency != null) { + await showUpdatingExchangeRate( + whileFuture: _getAggregateCurrency(selectedCurrency).then( + (aggregateSelected) => ref.read(efCurrencyPairProvider).setReceive( + aggregateSelected, + notifyListeners: true, + ), + ), + ); + } + } + + Future _swap() async { + _swapLock = true; _sendFocusNode.unfocus(); _receiveFocusNode.unfocus(); - List allPairs; + final temp = ref.read(efCurrencyPairProvider).send; + ref.read(efCurrencyPairProvider).setSend( + ref.read(efCurrencyPairProvider).receive, + notifyListeners: true, + ); + ref.read(efCurrencyPairProvider).setReceive( + temp, + notifyListeners: true, + ); - switch (ref.read(currentExchangeNameStateProvider.state).state) { - case ChangeNowExchange.exchangeName: - allPairs = ref.read(availableChangeNowCurrenciesProvider).pairs; - break; - case SimpleSwapExchange.exchangeName: - allPairs = ref.read(exchangeFormStateProvider).exchangeType == - ExchangeRateType.fixed - ? ref.read(availableSimpleswapCurrenciesProvider).fixedRatePairs - : ref.read(availableSimpleswapCurrenciesProvider).floatingRatePairs; - break; - default: - allPairs = []; - } + // final reversed = ref.read(efReversedProvider); - List availablePairs; - if (fromTicker.isEmpty || - fromTicker == "-" || - excludedTicker.isEmpty || - excludedTicker == "-") { - availablePairs = allPairs; - } else if (excludedTicker == fromTicker) { - availablePairs = allPairs - .where((e) => e.from == excludedTicker) - .toList(growable: false); - } else { - availablePairs = - allPairs.where((e) => e.to == excludedTicker).toList(growable: false); - } + final amount = ref.read(efSendAmountProvider); + ref.read(efSendAmountProvider.notifier).state = + ref.read(efReceiveAmountProvider); - final List tickers = currencies.where((e) { - if (excludedTicker == fromTicker) { - return e.ticker != excludedTicker && - availablePairs.where((e2) => e2.to == e.ticker).isNotEmpty; - } else { - return e.ticker != excludedTicker && - availablePairs.where((e2) => e2.from == e.ticker).isNotEmpty; - } - }).toList(growable: false); + ref.read(efReceiveAmountProvider.notifier).state = amount; + + unawaited(update()); + + _swapLock = false; + } + + Future _showCurrencySelectionSheet({ + required String? willChange, + required String? paired, + required bool isFixedRate, + required bool willChangeIsSend, + }) async { + _sendFocusNode.unfocus(); + _receiveFocusNode.unfocus(); final result = isDesktop ? await showDialog( @@ -448,8 +341,11 @@ class _ExchangeFormState extends ConsumerState { borderColor: Theme.of(context) .extension()! .background, - child: FloatingRateCurrencySelectionView( - currencies: tickers, + child: ExchangeCurrencySelectionView( + willChangeTicker: willChange, + pairedTicker: paired, + isFixedRate: isFixedRate, + willChangeIsSend: willChangeIsSend, ), ), ), @@ -463,430 +359,62 @@ class _ExchangeFormState extends ConsumerState { }) : await Navigator.of(context).push( MaterialPageRoute( - builder: (_) => FloatingRateCurrencySelectionView( - currencies: tickers, + builder: (_) => ExchangeCurrencySelectionView( + willChangeTicker: willChange, + pairedTicker: paired, + isFixedRate: isFixedRate, + willChangeIsSend: willChangeIsSend, ), ), ); if (mounted && result is Currency) { - onSelected(result); - } - } - - String? _fetchIconUrlFromTicker(String? ticker) { - if (ticker == null) return null; - - Iterable possibleCurrencies; - - switch (ref.read(currentExchangeNameStateProvider.state).state) { - case ChangeNowExchange.exchangeName: - possibleCurrencies = ref - .read(availableChangeNowCurrenciesProvider) - .currencies - .where((e) => e.ticker.toUpperCase() == ticker.toUpperCase()); - break; - case SimpleSwapExchange.exchangeName: - possibleCurrencies = [ - ...ref - .read(availableSimpleswapCurrenciesProvider) - .fixedRateCurrencies - .where((e) => e.ticker.toUpperCase() == ticker.toUpperCase()), - ...ref - .read(availableSimpleswapCurrenciesProvider) - .floatingRateCurrencies - .where((e) => e.ticker.toUpperCase() == ticker.toUpperCase()), - ]; - break; - default: - possibleCurrencies = []; - } - - for (final currency in possibleCurrencies) { - if (currency.image.isNotEmpty) { - return currency.image; - } - } - - return null; - } - - Future _showFixedRateSelectionSheet({ - required String excludedTicker, - required String fromTicker, - required void Function(String) onSelected, - }) async { - _sendFocusNode.unfocus(); - _receiveFocusNode.unfocus(); - - List marketsThatPairWithExcludedTicker = []; - - if (excludedTicker == "" || - excludedTicker == "-" || - fromTicker == "" || - fromTicker == "-") { - marketsThatPairWithExcludedTicker = - ref.read(availableChangeNowCurrenciesProvider).markets; - } else if (excludedTicker == fromTicker) { - marketsThatPairWithExcludedTicker = ref - .read(availableChangeNowCurrenciesProvider) - .markets - .where((e) => e.from == excludedTicker && e.to != excludedTicker) - .toList(growable: false); + return result; } else { - marketsThatPairWithExcludedTicker = ref - .read(availableChangeNowCurrenciesProvider) - .markets - .where((e) => e.to == excludedTicker && e.from != excludedTicker) - .toList(growable: false); - } - - final result = isDesktop - ? await showDialog( - context: context, - builder: (context) { - return DesktopDialog( - maxHeight: 700, - maxWidth: 580, - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.only( - left: 32, - ), - child: Text( - "Choose a coin to exchange", - style: STextStyles.desktopH3(context), - ), - ), - const DesktopDialogCloseButton(), - ], - ), - Expanded( - child: Padding( - padding: const EdgeInsets.only( - left: 32, - right: 32, - bottom: 32, - ), - child: Row( - children: [ - Expanded( - child: RoundedWhiteContainer( - padding: const EdgeInsets.all(16), - borderColor: Theme.of(context) - .extension()! - .background, - child: FixedRateMarketPairCoinSelectionView( - markets: marketsThatPairWithExcludedTicker, - currencies: ref - .read( - availableChangeNowCurrenciesProvider) - .currencies, - isFrom: excludedTicker != fromTicker, - ), - ), - ), - ], - ), - ), - ), - ], - ), - ); - }) - : await Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => FixedRateMarketPairCoinSelectionView( - markets: marketsThatPairWithExcludedTicker, - currencies: - ref.read(availableChangeNowCurrenciesProvider).currencies, - isFrom: excludedTicker != fromTicker, - ), - ), - ); - - if (mounted && result is String) { - onSelected(result); + return null; } } - void onRateTypeChanged(ExchangeRateType rateType) async { + void onRateTypeChanged(ExchangeRateType newType) { _receiveFocusNode.unfocus(); _sendFocusNode.unfocus(); - unawaited( - showDialog( - context: context, - barrierDismissible: false, - builder: (_) => WillPopScope( - onWillPop: () async => false, - child: Container( - color: Theme.of(context) - .extension()! - .overlay - .withOpacity(0.6), - child: const CustomLoadingOverlay( - message: "Updating exchange rate", - eventBus: null, - ), - ), - ), - ), - ); - - final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? "-"; - final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? "-"; - - ref.read(exchangeFormStateProvider).exchangeType = rateType; - ref.read(exchangeFormStateProvider).reversed = false; - switch (rateType) { - case ExchangeRateType.estimated: - if (!(toTicker == "-" || fromTicker == "-")) { - late final Iterable available; - - switch (ref.read(currentExchangeNameStateProvider.state).state) { - case ChangeNowExchange.exchangeName: - available = ref - .read(availableChangeNowCurrenciesProvider) - .pairs - .where((e) => e.to == toTicker && e.from == fromTicker); - break; - case SimpleSwapExchange.exchangeName: - available = ref - .read(availableSimpleswapCurrenciesProvider) - .floatingRatePairs - .where((e) => e.to == toTicker && e.from == fromTicker); - break; - default: - available = []; - } - - if (available.isNotEmpty) { - late final Iterable availableCurrencies; - switch (ref.read(currentExchangeNameStateProvider.state).state) { - case ChangeNowExchange.exchangeName: - availableCurrencies = ref - .read(availableChangeNowCurrenciesProvider) - .currencies - .where( - (e) => e.ticker == fromTicker || e.ticker == toTicker); - break; - case SimpleSwapExchange.exchangeName: - availableCurrencies = ref - .read(availableSimpleswapCurrenciesProvider) - .floatingRateCurrencies - .where( - (e) => e.ticker == fromTicker || e.ticker == toTicker); - break; - default: - availableCurrencies = []; - } - - if (availableCurrencies.length > 1) { - final from = - availableCurrencies.firstWhere((e) => e.ticker == fromTicker); - final to = - availableCurrencies.firstWhere((e) => e.ticker == toTicker); - - final newFromAmount = Decimal.tryParse(_sendController.text); - ref.read(exchangeFormStateProvider).fromAmount = - newFromAmount ?? Decimal.zero; - if (newFromAmount == null) { - _receiveController.text = ""; - } - - await ref.read(exchangeFormStateProvider).updateTo(to, false); - await ref.read(exchangeFormStateProvider).updateFrom(from, true); - - _receiveController.text = - ref.read(exchangeFormStateProvider).toAmountString.isEmpty - ? "-" - : ref.read(exchangeFormStateProvider).toAmountString; - if (mounted) { - Navigator.of(context, rootNavigator: isDesktop).pop(); - } - return; - } - } - } - if (mounted) { - Navigator.of(context, rootNavigator: isDesktop).pop(); - } - if (!(fromTicker == "-" || toTicker == "-")) { - unawaited( - showFloatingFlushBar( - type: FlushBarType.warning, - message: - "Estimated rate trade pair \"$fromTicker-$toTicker\" unavailable. Reverting to last estimated rate pair.", - context: context, - ), - ); - } - break; - case ExchangeRateType.fixed: - if (!(toTicker == "-" || fromTicker == "-")) { - switch (ref.read(currentExchangeNameStateProvider.state).state) { - case ChangeNowExchange.exchangeName: - FixedRateMarket? market; - try { - market = ref - .read(availableChangeNowCurrenciesProvider) - .markets - .firstWhere( - (e) => e.from == fromTicker && e.to == toTicker); - } catch (_) { - market = null; - } - - final newFromAmount = Decimal.tryParse(_sendController.text); - ref.read(exchangeFormStateProvider).fromAmount = - newFromAmount ?? Decimal.zero; - - if (newFromAmount == null) { - _receiveController.text = ""; - } - - await ref - .read(exchangeFormStateProvider) - .updateMarket(market, false); - await ref - .read(exchangeFormStateProvider) - .setFromAmountAndCalculateToAmount( - Decimal.tryParse(_sendController.text) ?? Decimal.zero, - true, - ); - if (mounted) { - Navigator.of(context, rootNavigator: isDesktop).pop(); - } - return; - case SimpleSwapExchange.exchangeName: - final available = ref - .read(availableSimpleswapCurrenciesProvider) - .floatingRatePairs - .where((e) => e.to == toTicker && e.from == fromTicker); - if (available.isNotEmpty) { - final availableCurrencies = ref - .read(availableSimpleswapCurrenciesProvider) - .fixedRateCurrencies - .where( - (e) => e.ticker == fromTicker || e.ticker == toTicker); - if (availableCurrencies.length > 1) { - final from = availableCurrencies - .firstWhere((e) => e.ticker == fromTicker); - final to = availableCurrencies - .firstWhere((e) => e.ticker == toTicker); - - final newFromAmount = Decimal.tryParse(_sendController.text); - ref.read(exchangeFormStateProvider).fromAmount = - newFromAmount ?? Decimal.zero; - if (newFromAmount == null) { - _receiveController.text = ""; - } - - await ref.read(exchangeFormStateProvider).updateTo(to, false); - await ref - .read(exchangeFormStateProvider) - .updateFrom(from, true); - - _receiveController.text = - ref.read(exchangeFormStateProvider).toAmountString.isEmpty - ? "-" - : ref.read(exchangeFormStateProvider).toAmountString; - if (mounted) { - Navigator.of(context, rootNavigator: isDesktop).pop(); - } - return; - } - } - - break; - default: - // - } - } - if (mounted) { - Navigator.of(context, rootNavigator: isDesktop).pop(); - } - unawaited( - showFloatingFlushBar( - type: FlushBarType.warning, - message: - "Fixed rate trade pair \"$fromTicker-$toTicker\" unavailable. Reverting to last fixed rate pair.", - context: context, - ), - ); - break; - } + ref.read(efRateTypeProvider.notifier).state = newType; + update(); } void onExchangePressed() async { - final rateType = ref.read(prefsChangeNotifierProvider).exchangeRateType; - final fromTicker = ref.read(exchangeFormStateProvider).fromTicker ?? ""; - final toTicker = ref.read(exchangeFormStateProvider).toTicker ?? ""; - final sendAmount = ref.read(exchangeFormStateProvider).fromAmount!; - final estimate = ref.read(exchangeFormStateProvider).estimate!; + final rateType = ref.read(efRateTypeProvider); + final fromTicker = ref.read(efCurrencyPairProvider).send?.ticker ?? ""; + final toTicker = ref.read(efCurrencyPairProvider).receive?.ticker ?? ""; + final estimate = ref.read(efEstimateProvider)!; + final sendAmount = ref.read(efSendAmountProvider)!; + + if (rateType == ExchangeRateType.fixed && toTicker.toUpperCase() == "WOW") { + await showDialog( + context: context, + builder: (context) => const StackOkDialog( + title: "WOW error", + message: + "Wownero is temporarily disabled as a receiving currency for fixed rate trades due to network issues", + ), + ); + + return; + } String rate; + final amountToSend = + estimate.reversed ? estimate.estimatedAmount : sendAmount; + final amountToReceive = estimate.reversed + ? ref.read(efReceiveAmountProvider)! + : estimate.estimatedAmount; + switch (rateType) { case ExchangeRateType.estimated: - bool isAvailable = false; - late final Iterable availableFloatingPairs; - - switch (ref.read(currentExchangeNameStateProvider.state).state) { - case ChangeNowExchange.exchangeName: - availableFloatingPairs = ref - .read(availableChangeNowCurrenciesProvider) - .pairs - .where((e) => e.to == toTicker && e.from == fromTicker); - break; - case SimpleSwapExchange.exchangeName: - availableFloatingPairs = ref - .read(availableSimpleswapCurrenciesProvider) - .floatingRatePairs - .where((e) => e.to == toTicker && e.from == fromTicker); - break; - default: - availableFloatingPairs = []; - } - - for (final pair in availableFloatingPairs) { - if (pair.from == fromTicker && pair.to == toTicker) { - isAvailable = true; - break; - } - } - - if (!isAvailable) { - unawaited( - showDialog( - context: context, - barrierDismissible: true, - builder: (_) { - if (isDesktop) { - return SimpleDesktopDialog( - title: "Selected trade pair unavailable", - message: - "The $fromTicker - $toTicker market is currently disabled for estimated/floating rate trades", - ); - } else { - return StackDialog( - title: "Selected trade pair unavailable", - message: - "The $fromTicker - $toTicker market is currently disabled for estimated/floating rate trades", - ); - } - }, - ), - ); - return; - } rate = - "1 ${fromTicker.toUpperCase()} ~${(estimate.estimatedAmount / sendAmount).toDecimal(scaleOnInfinitePrecision: 8).toStringAsFixed(8)} ${toTicker.toUpperCase()}"; + "1 ${fromTicker.toUpperCase()} ~${(amountToReceive / sendAmount).toDecimal(scaleOnInfinitePrecision: 8).toStringAsFixed(8)} ${toTicker.toUpperCase()}"; break; case ExchangeRateType.fixed: bool? shouldCancel; @@ -965,7 +493,7 @@ class _ExchangeFormState extends ConsumerState { leftButton: TextButton( style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), child: Text( "Cancel", style: STextStyles.itemSubtitle12(context), @@ -978,7 +506,7 @@ class _ExchangeFormState extends ConsumerState { rightButton: TextButton( style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), child: Text( "Attempt", style: STextStyles.button(context), @@ -998,7 +526,9 @@ class _ExchangeFormState extends ConsumerState { return; } rate = - "1 ${fromTicker.toUpperCase()} ~${ref.read(exchangeFormStateProvider).rate!.toStringAsFixed(8)} ${toTicker.toUpperCase()}"; + "1 ${fromTicker.toUpperCase()} ~${(amountToReceive / amountToSend).toDecimal( + scaleOnInfinitePrecision: 12, + ).toStringAsFixed(8)} ${toTicker.toUpperCase()}"; break; } @@ -1006,13 +536,12 @@ class _ExchangeFormState extends ConsumerState { sendTicker: fromTicker.toUpperCase(), receiveTicker: toTicker.toUpperCase(), rateInfo: rate, - sendAmount: estimate.reversed ? estimate.estimatedAmount : sendAmount, - receiveAmount: estimate.reversed - ? ref.read(exchangeFormStateProvider).toAmount! - : estimate.estimatedAmount, + sendAmount: amountToSend, + receiveAmount: amountToReceive, rateType: rateType, - rateId: estimate.rateId, + estimate: estimate, reversed: estimate.reversed, + walletInitiated: walletInitiated, ); if (mounted) { @@ -1077,13 +606,9 @@ class _ExchangeFormState extends ConsumerState { return false; } - String? ticker; - - if (isSend) { - ticker = ref.read(exchangeFormStateProvider).fromTicker; - } else { - ticker = ref.read(exchangeFormStateProvider).toTicker; - } + String? ticker = isSend + ? ref.read(efCurrencyPairProvider).send?.ticker + : ref.read(efCurrencyPairProvider).receive?.ticker; if (ticker == null) { return false; @@ -1092,6 +617,97 @@ class _ExchangeFormState extends ConsumerState { return coin.ticker.toUpperCase() == ticker.toUpperCase(); } + Future update() async { + final uuid = const Uuid().v1(); + _latestUuid = uuid; + _addUpdate(uuid); + for (final exchange in exchanges) { + ref.read(efEstimatesListProvider(exchange.name).notifier).state = null; + } + + final reversed = ref.read(efReversedProvider); + final amount = reversed + ? ref.read(efReceiveAmountProvider) + : ref.read(efSendAmountProvider); + + final pair = ref.read(efCurrencyPairProvider); + if (amount == null || + amount <= Decimal.zero || + pair.send == null || + pair.receive == null) { + _removeUpdate(uuid); + return; + } + final rateType = ref.read(efRateTypeProvider); + final Map>, Range?>> + results = {}; + + for (final exchange in exchanges) { + final sendCurrency = pair.send?.forExchange(exchange.name); + final receiveCurrency = pair.receive?.forExchange(exchange.name); + + if (sendCurrency != null && receiveCurrency != null) { + final rangeResponse = await exchange.getRange( + reversed ? receiveCurrency.ticker : sendCurrency.ticker, + reversed ? sendCurrency.ticker : receiveCurrency.ticker, + rateType == ExchangeRateType.fixed, + ); + + final estimateResponse = await exchange.getEstimates( + sendCurrency.ticker, + receiveCurrency.ticker, + amount, + rateType == ExchangeRateType.fixed, + reversed, + ); + + results.addAll( + { + exchange.name: Tuple2( + estimateResponse, + rangeResponse.value, + ), + }, + ); + } + } + + for (final exchange in exchanges) { + if (uuid == _latestUuid) { + ref.read(efEstimatesListProvider(exchange.name).notifier).state = + results[exchange.name]; + } + } + + _removeUpdate(uuid); + } + + String? _latestUuid; + final Set _uuids = {}; + + void _addUpdate(String uuid) { + _uuids.add(uuid); + ref.read(efRefreshingProvider.notifier).state = true; + } + + void _removeUpdate(String uuid) { + _uuids.remove(uuid); + if (_uuids.isEmpty) { + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.read(efRefreshingProvider.notifier).state = false; + }); + } + } + + void updateSend(Estimate? estimate) { + ref.read(efSendAmountProvider.notifier).state = estimate?.estimatedAmount; + } + + void updateReceive(Estimate? estimate) { + ref.read(efReceiveAmountProvider.notifier).state = + estimate?.estimatedAmount; + } + @override void initState() { _sendController = TextEditingController(); @@ -1101,55 +717,62 @@ class _ExchangeFormState extends ConsumerState { coin = widget.coin; walletInitiated = walletId != null && coin != null; + _sendFocusNode.addListener(() { + if (_sendFocusNode.hasFocus) { + final reversed = ref.read(efReversedProvider); + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.read(efReversedProvider.notifier).state = false; + if (reversed == true) { + update(); + } + }); + } + }); + _receiveFocusNode.addListener(() { + if (_receiveFocusNode.hasFocus && + ref.read(efExchangeProvider).name != ChangeNowExchange.exchangeName) { + final reversed = ref.read(efReversedProvider); + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.read(efReversedProvider.notifier).state = true; + if (reversed != true) { + update(); + } + }); + } + }); + if (walletInitiated) { WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - ref.read(exchangeFormStateProvider).clearAmounts(true); - // ref.read(fixedRateExchangeFormProvider); + ref.read(efSendAmountProvider.notifier).state = null; + ref.read(efReceiveAmountProvider.notifier).state = null; + ref.read(efReversedProvider.notifier).state = false; + ref.read(efRefreshingProvider.notifier).state = false; + ref.read(efCurrencyPairProvider).setSend(null, notifyListeners: true); + ref + .read(efCurrencyPairProvider) + .setReceive(null, notifyListeners: true); + ExchangeDataLoadingService.instance + .getAggregateCurrency( + widget.contract == null ? coin!.ticker : widget.contract!.symbol, + ExchangeRateType.estimated, + widget.contract == null ? null : widget.contract!.address, + ) + .then((value) { + if (value != null) { + ref.read(efCurrencyPairProvider).setSend( + value, + notifyListeners: true, + ); + } + }); }); } else { - final isEstimated = - ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated; - _sendController.text = - ref.read(exchangeFormStateProvider).fromAmountString; - _receiveController.text = isEstimated - ? "-" //ref.read(estimatedRateExchangeFormProvider).toAmountString - : ref.read(exchangeFormStateProvider).toAmountString; + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + _sendController.text = ref.read(efSendAmountStringProvider); + _receiveController.text = ref.read(efReceiveAmountStringProvider); + }); } - _sendFocusNode.addListener(() async { - if (!_sendFocusNode.hasFocus) { - final newFromAmount = Decimal.tryParse(_sendController.text); - await ref - .read(exchangeFormStateProvider) - .setFromAmountAndCalculateToAmount( - newFromAmount ?? Decimal.zero, true); - - if (newFromAmount == null) { - _receiveController.text = - ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated - ? "-" - : ""; - } - } - }); - _receiveFocusNode.addListener(() async { - if (!_receiveFocusNode.hasFocus) { - final newToAmount = Decimal.tryParse(_receiveController.text); - if (ref.read(prefsChangeNotifierProvider).exchangeRateType != - ExchangeRateType.estimated) { - await ref - .read(exchangeFormStateProvider) - .setToAmountAndCalculateFromAmount( - newToAmount ?? Decimal.zero, true); - } - if (newToAmount == null) { - _sendController.text = ""; - } - } - }); - super.initState(); } @@ -1157,6 +780,8 @@ class _ExchangeFormState extends ConsumerState { void dispose() { _receiveController.dispose(); _sendController.dispose(); + _receiveFocusNode.dispose(); + _sendFocusNode.dispose(); super.dispose(); } @@ -1164,46 +789,42 @@ class _ExchangeFormState extends ConsumerState { Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - ref.listen(currentExchangeNameStateProvider, (previous, next) { - ref.read(exchangeFormStateProvider).exchange = ref.read(exchangeProvider); - }); + final rateType = ref.watch(efRateTypeProvider); - final isEstimated = ref.watch(prefsChangeNotifierProvider - .select((pref) => pref.exchangeRateType)) == - ExchangeRateType.estimated; + final isEstimated = rateType == ExchangeRateType.estimated; - ref.listen( - exchangeFormStateProvider.select((value) => value.toAmountString), - (previous, String next) { + ref.listen(efReceiveAmountStringProvider, (previous, String next) { if (!_receiveFocusNode.hasFocus) { - _receiveController.text = isEstimated && - ref.watch(exchangeProvider).name == - SimpleSwapExchange.exchangeName && - next.isEmpty - ? "-" - : next; - //todo: check if print needed - // debugPrint("RECEIVE AMOUNT LISTENER ACTIVATED"); - if (_swapLock) { - _sendController.text = - ref.read(exchangeFormStateProvider).fromAmountString; - } + _receiveController.text = isEstimated && next.isEmpty ? "-" : next; + // if (_swapLock) { + _sendController.text = ref.read(efSendAmountStringProvider); + // } } }); - ref.listen( - exchangeFormStateProvider.select((value) => value.fromAmountString), - (previous, String next) { + ref.listen(efSendAmountStringProvider, (previous, String next) { if (!_sendFocusNode.hasFocus) { _sendController.text = next; - //todo: check if print needed - // debugPrint("SEND AMOUNT LISTENER ACTIVATED"); - if (_swapLock) { - _receiveController.text = isEstimated - ? ref.read(exchangeFormStateProvider).toAmountString.isEmpty - ? "-" - : ref.read(exchangeFormStateProvider).toAmountString - : ref.read(exchangeFormStateProvider).toAmountString; - } + // if (_swapLock) { + _receiveController.text = + isEstimated && ref.read(efReceiveAmountStringProvider).isEmpty + ? "-" + : ref.read(efReceiveAmountStringProvider); + // } + } + }); + + ref.listen(efEstimateProvider.notifier, (previous, next) { + final estimate = (next as StateController).state; + if (ref.read(efReversedProvider)) { + updateSend(estimate); + } else { + updateReceive(estimate); + } + }); + + ref.listen(efCurrencyPairProvider, (previous, next) { + if (!_swapLock) { + update(); } }); @@ -1221,6 +842,9 @@ class _ExchangeFormState extends ConsumerState { height: isDesktop ? 10 : 4, ), ExchangeTextField( + key: Key("exchangeTextFieldKeyFor_" + "${Theme.of(context).extension()!.themeId}" + "${ref.watch(efCurrencyPairProvider.select((value) => value.send?.ticker))}"), controller: _sendController, focusNode: _sendFocusNode, textStyle: STextStyles.smallMed14(context).copyWith( @@ -1239,10 +863,8 @@ class _ExchangeFormState extends ConsumerState { onChanged: sendFieldOnChanged, onButtonTap: selectSendCurrency, isWalletCoin: isWalletCoin(coin, true), - image: _fetchIconUrlFromTicker(ref.watch( - exchangeFormStateProvider.select((value) => value.fromTicker))), - ticker: ref.watch( - exchangeFormStateProvider.select((value) => value.fromTicker)), + currency: + ref.watch(efCurrencyPairProvider.select((value) => value.send)), ), SizedBox( height: isDesktop ? 10 : 4, @@ -1250,17 +872,6 @@ class _ExchangeFormState extends ConsumerState { SizedBox( height: isDesktop ? 10 : 4, ), - if (ref - .watch( - exchangeFormStateProvider.select((value) => value.warning)) - .isNotEmpty && - !ref.watch( - exchangeFormStateProvider.select((value) => value.reversed))) - Text( - ref.watch( - exchangeFormStateProvider.select((value) => value.warning)), - style: STextStyles.errorSmall(context), - ), Row( crossAxisAlignment: CrossAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -1277,27 +888,31 @@ class _ExchangeFormState extends ConsumerState { cursor: SystemMouseCursors.click, child: child, ), - child: RoundedContainer( - padding: isDesktop - ? const EdgeInsets.all(6) - : const EdgeInsets.all(2), - color: Theme.of(context) - .extension()! - .buttonBackSecondary, - radiusMultiplier: 0.75, - child: GestureDetector( - onTap: () async { - await _swap(); - }, - child: Padding( - padding: const EdgeInsets.all(4), - child: SvgPicture.asset( - Assets.svg.swap, - width: 20, - height: 20, - color: Theme.of(context) - .extension()! - .accentColorDark, + child: Semantics( + label: "Swap Button. Reverse The Exchange Currencies.", + excludeSemantics: true, + child: RoundedContainer( + padding: isDesktop + ? const EdgeInsets.all(6) + : const EdgeInsets.all(2), + color: Theme.of(context) + .extension()! + .buttonBackSecondary, + radiusMultiplier: 0.75, + child: GestureDetector( + onTap: () async { + await _swap(); + }, + child: Padding( + padding: const EdgeInsets.all(4), + child: SvgPicture.asset( + Assets.svg.swap, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .accentColorDark, + ), ), ), ), @@ -1309,6 +924,8 @@ class _ExchangeFormState extends ConsumerState { height: isDesktop ? 10 : 7, ), ExchangeTextField( + key: Key( + "exchangeTextFieldKeyFor1_${Theme.of(context).extension()!.themeId}"), focusNode: _receiveFocusNode, controller: _receiveController, textStyle: STextStyles.smallMed14(context).copyWith( @@ -1319,76 +936,57 @@ class _ExchangeFormState extends ConsumerState { borderRadius: Constants.size.circularBorderRadius, background: Theme.of(context).extension()!.textFieldDefaultBG, - onTap: () { - if (!(ref.read(prefsChangeNotifierProvider).exchangeRateType == - ExchangeRateType.estimated) && - _receiveController.text == "-") { - _receiveController.text = ""; - } - }, + onTap: rateType == ExchangeRateType.estimated && + ref.watch(efExchangeProvider).name == + ChangeNowExchange.exchangeName + ? null + : () { + if (_sendController.text == "-") { + _sendController.text = ""; + } + }, onChanged: receiveFieldOnChanged, onButtonTap: selectReceiveCurrency, isWalletCoin: isWalletCoin(coin, true), - image: _fetchIconUrlFromTicker(ref.watch( - exchangeFormStateProvider.select((value) => value.toTicker))), - ticker: ref.watch( - exchangeFormStateProvider.select((value) => value.toTicker)), - readOnly: ref.watch(prefsChangeNotifierProvider - .select((value) => value.exchangeRateType)) == - ExchangeRateType.estimated || - ref.watch(exchangeProvider).name == - SimpleSwapExchange.exchangeName, + currency: ref + .watch(efCurrencyPairProvider.select((value) => value.receive)), + readOnly: rateType == ExchangeRateType.estimated && + ref.watch(efExchangeProvider).name == + ChangeNowExchange.exchangeName, ), - if (ref - .watch( - exchangeFormStateProvider.select((value) => value.warning)) - .isNotEmpty && - ref.watch( - exchangeFormStateProvider.select((value) => value.reversed))) - Text( - ref.watch( - exchangeFormStateProvider.select((value) => value.warning)), - style: STextStyles.errorSmall(context), - ), SizedBox( height: isDesktop ? 20 : 12, ), SizedBox( - height: 60, + height: isDesktop ? 60 : 40, child: RateTypeToggle( + key: UniqueKey(), onChanged: onRateTypeChanged, ), ), - if (ref.read(exchangeFormStateProvider).fromAmount != null && - ref.read(exchangeFormStateProvider).fromAmount != Decimal.zero) - SizedBox( - height: isDesktop ? 20 : 12, - ), - if (ref.read(exchangeFormStateProvider).fromAmount != null && - ref.read(exchangeFormStateProvider).fromAmount != Decimal.zero) - ExchangeProviderOptions( - from: ref.watch(exchangeFormStateProvider).fromTicker, - to: ref.watch(exchangeFormStateProvider).toTicker, - fromAmount: ref.watch(exchangeFormStateProvider).fromAmount, - toAmount: ref.watch(exchangeFormStateProvider).toAmount, - fixedRate: ref.watch(prefsChangeNotifierProvider - .select((value) => value.exchangeRateType)) == - ExchangeRateType.fixed, - reversed: ref.watch( - exchangeFormStateProvider.select((value) => value.reversed)), - ), + AnimatedSize( + duration: const Duration(milliseconds: 300), + child: ref.watch(efSendAmountProvider) == null && + ref.watch(efReceiveAmountProvider) == null + ? const SizedBox( + height: 0, + ) + : Padding( + padding: EdgeInsets.only(top: isDesktop ? 20 : 12), + child: ExchangeProviderOptions( + fixedRate: rateType == ExchangeRateType.fixed, + reversed: ref.watch(efReversedProvider), + ), + ), + ), SizedBox( height: isDesktop ? 20 : 12, ), PrimaryButton( buttonHeight: isDesktop ? ButtonHeight.l : null, - enabled: ref.watch( - exchangeFormStateProvider.select((value) => value.canExchange)), - onPressed: ref.watch(exchangeFormStateProvider - .select((value) => value.canExchange)) - ? onExchangePressed - : null, - label: "Exchange", + enabled: ref.watch(efCanExchangeProvider), + onPressed: onExchangePressed, + label: "Swap", ) ], ); diff --git a/lib/pages/exchange_view/exchange_loading_overlay.dart b/lib/pages/exchange_view/exchange_loading_overlay.dart index 15d8dccec..673aae5a4 100644 --- a/lib/pages/exchange_view/exchange_loading_overlay.dart +++ b/lib/pages/exchange_view/exchange_loading_overlay.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/providers/exchange/changenow_initial_load_status.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_loading_overlay.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; @@ -91,7 +91,7 @@ class _ExchangeLoadingOverlayViewState rightButton: TextButton( style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), child: Text( "OK", style: STextStyles.button(context).copyWith( diff --git a/lib/pages/exchange_view/exchange_step_views/step_1_view.dart b/lib/pages/exchange_view/exchange_step_views/step_1_view.dart index b51bccc55..22e39b244 100644 --- a/lib/pages/exchange_view/exchange_step_views/step_1_view.dart +++ b/lib/pages/exchange_view/exchange_step_views/step_1_view.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_2_view.dart'; -import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/step_row.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; +import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -56,7 +56,7 @@ class _Step1ViewState extends State { }, ), title: Text( - "Exchange", + "Swap", style: STextStyles.navBarTitle(context), ), ), @@ -188,7 +188,7 @@ class _Step1ViewState extends State { }, style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), child: Text( "Next", style: STextStyles.button(context), diff --git a/lib/pages/exchange_view/exchange_step_views/step_2_view.dart b/lib/pages/exchange_view/exchange_step_views/step_2_view.dart index d943adcfa..1177d09ef 100644 --- a/lib/pages/exchange_view/exchange_step_views/step_2_view.dart +++ b/lib/pages/exchange_view/exchange_step_views/step_2_view.dart @@ -8,6 +8,8 @@ import 'package:stackwallet/pages/exchange_view/choose_from_stack_view.dart'; import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_3_view.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/step_row.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/address_utils.dart'; import 'package:stackwallet/utilities/barcode_scanner_interface.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; @@ -15,7 +17,6 @@ import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; @@ -123,6 +124,9 @@ class _Step2ViewState extends ConsumerState { @override Widget build(BuildContext context) { + final supportsRefund = + ref.watch(efExchangeProvider).name != MajesticBankExchange.exchangeName; + return Background( child: Scaffold( backgroundColor: Theme.of(context).extension()!.background, @@ -139,7 +143,7 @@ class _Step2ViewState extends ConsumerState { }, ), title: Text( - "Exchange", + "Swap", style: STextStyles.navBarTitle(context), ), ), @@ -189,8 +193,8 @@ class _Step2ViewState extends ConsumerState { style: STextStyles.smallMed12(context), ), if (isStackCoin(model.receiveTicker)) - BlueTextButton( - text: "Choose from stack", + CustomTextButton( + text: "Choose from Stack", onTap: () { try { final coin = @@ -217,8 +221,9 @@ class _Step2ViewState extends ConsumerState { setState(() { enableNext = _toController.text.isNotEmpty && - _refundController - .text.isNotEmpty; + (_refundController + .text.isNotEmpty || + !supportsRefund); }); } }); @@ -257,7 +262,12 @@ class _Step2ViewState extends ConsumerState { focusNode: _toFocusNode, style: STextStyles.field(context), onChanged: (value) { - setState(() {}); + model.recipientAddress = _toController.text; + setState(() { + enableNext = _toController.text.isNotEmpty && + (_refundController.text.isNotEmpty || + !supportsRefund); + }); }, decoration: standardInputDecoration( "Enter the ${model.receiveTicker.toUpperCase()} payout address", @@ -291,8 +301,9 @@ class _Step2ViewState extends ConsumerState { setState(() { enableNext = _toController .text.isNotEmpty && - _refundController - .text.isNotEmpty; + (_refundController.text + .isNotEmpty || + !supportsRefund); }); }, child: const XIcon(), @@ -318,8 +329,10 @@ class _Step2ViewState extends ConsumerState { enableNext = _toController .text .isNotEmpty && - _refundController - .text.isNotEmpty; + (_refundController + .text + .isNotEmpty || + !supportsRefund); }); } }, @@ -367,8 +380,9 @@ class _Step2ViewState extends ConsumerState { setState(() { enableNext = _toController .text.isNotEmpty && - _refundController - .text.isNotEmpty; + (_refundController.text + .isNotEmpty || + !supportsRefund); }); }); }, @@ -396,8 +410,9 @@ class _Step2ViewState extends ConsumerState { setState(() { enableNext = _toController .text.isNotEmpty && - _refundController - .text.isNotEmpty; + (_refundController.text + .isNotEmpty || + !supportsRefund); }); } else { _toController.text = @@ -408,8 +423,9 @@ class _Step2ViewState extends ConsumerState { setState(() { enableNext = _toController .text.isNotEmpty && - _refundController - .text.isNotEmpty; + (_refundController.text + .isNotEmpty || + !supportsRefund); }); } } on PlatformException catch (e, s) { @@ -440,133 +456,235 @@ class _Step2ViewState extends ConsumerState { const SizedBox( height: 24, ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Refund Wallet (required)", - style: STextStyles.smallMed12(context), - ), - if (isStackCoin(model.sendTicker)) - BlueTextButton( - text: "Choose from stack", - onTap: () { - try { - final coin = - coinFromTickerCaseInsensitive( - model.sendTicker, - ); - Navigator.of(context) - .pushNamed( - ChooseFromStackView.routeName, - arguments: coin, - ) - .then((value) async { - if (value is String) { - final manager = ref - .read( - walletsChangeNotifierProvider) - .getManager(value); + if (supportsRefund) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Refund Wallet (required)", + style: STextStyles.smallMed12(context), + ), + if (isStackCoin(model.sendTicker)) + CustomTextButton( + text: "Choose from Stack", + onTap: () { + try { + final coin = + coinFromTickerCaseInsensitive( + model.sendTicker, + ); + Navigator.of(context) + .pushNamed( + ChooseFromStackView.routeName, + arguments: coin, + ) + .then((value) async { + if (value is String) { + final manager = ref + .read( + walletsChangeNotifierProvider) + .getManager(value); - _refundController.text = - manager.walletName; - model.refundAddress = await manager - .currentReceivingAddress; - } - setState(() { - enableNext = _toController - .text.isNotEmpty && - _refundController.text.isNotEmpty; + _refundController.text = + manager.walletName; + model.refundAddress = await manager + .currentReceivingAddress; + } + setState(() { + enableNext = + _toController.text.isNotEmpty && + _refundController + .text.isNotEmpty; + }); }); - }); - } catch (e, s) { - Logging.instance - .log("$e\n$s", level: LogLevel.Info); - } - }, - ), - ], - ), - const SizedBox( - height: 4, - ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + } catch (e, s) { + Logging.instance.log("$e\n$s", + level: LogLevel.Info); + } + }, + ), + ], ), - child: TextField( - key: const Key( - "refundExchangeStep2ViewAddressFieldKey"), - controller: _refundController, - readOnly: false, - autocorrect: false, - enableSuggestions: false, - // inputFormatters: [ - // FilteringTextInputFormatter.allow(RegExp("[a-zA-Z0-9]{34}")), - // ], - toolbarOptions: const ToolbarOptions( - copy: false, - cut: false, - paste: true, - selectAll: false, + if (supportsRefund) + const SizedBox( + height: 4, + ), + if (supportsRefund) + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), - focusNode: _refundFocusNode, - style: STextStyles.field(context), - onChanged: (value) { - setState(() {}); - }, - decoration: standardInputDecoration( - "Enter ${model.sendTicker.toUpperCase()} refund address", - _refundFocusNode, - context, - ).copyWith( - contentPadding: const EdgeInsets.only( - left: 16, - top: 6, - bottom: 8, - right: 5, + child: TextField( + key: const Key( + "refundExchangeStep2ViewAddressFieldKey"), + controller: _refundController, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + // inputFormatters: [ + // FilteringTextInputFormatter.allow(RegExp("[a-zA-Z0-9]{34}")), + // ], + toolbarOptions: const ToolbarOptions( + copy: false, + cut: false, + paste: true, + selectAll: false, ), - suffixIcon: Padding( - padding: _refundController.text.isEmpty - ? const EdgeInsets.only(right: 16) - : const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceAround, - children: [ - _refundController.text.isNotEmpty - ? TextFieldIconButton( - key: const Key( - "sendViewClearAddressFieldButtonKey"), - onTap: () { - _refundController.text = ""; - model.refundAddress = - _refundController.text; + focusNode: _refundFocusNode, + style: STextStyles.field(context), + onChanged: (value) { + model.refundAddress = _refundController.text; + setState(() { + enableNext = + _toController.text.isNotEmpty && + _refundController.text.isNotEmpty; + }); + }, + decoration: standardInputDecoration( + "Enter ${model.sendTicker.toUpperCase()} refund address", + _refundFocusNode, + context, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 16, + top: 6, + bottom: 8, + right: 5, + ), + suffixIcon: Padding( + padding: _refundController.text.isEmpty + ? const EdgeInsets.only(right: 16) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceAround, + children: [ + _refundController.text.isNotEmpty + ? TextFieldIconButton( + key: const Key( + "sendViewClearAddressFieldButtonKey"), + onTap: () { + _refundController.text = ""; + model.refundAddress = + _refundController.text; + setState(() { + enableNext = _toController + .text + .isNotEmpty && + _refundController + .text.isNotEmpty; + }); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + key: const Key( + "sendViewPasteAddressFieldButtonKey"), + onTap: () async { + final ClipboardData? data = + await clipboard.getData( + Clipboard + .kTextPlain); + if (data?.text != null && + data! + .text!.isNotEmpty) { + final content = + data.text!.trim(); + + _refundController.text = + content; + model.refundAddress = + _refundController + .text; + + setState(() { + enableNext = _toController + .text + .isNotEmpty && + _refundController + .text + .isNotEmpty; + }); + } + }, + child: _refundController + .text.isEmpty + ? const ClipboardIcon() + : const XIcon(), + ), + if (_refundController.text.isEmpty) + TextFieldIconButton( + key: const Key( + "sendViewAddressBookButtonKey"), + onTap: () { + ref + .read( + exchangeFlowIsActiveStateProvider + .state) + .state = true; + Navigator.of(context) + .pushNamed( + AddressBookView.routeName, + ) + .then((_) { + ref + .read( + exchangeFlowIsActiveStateProvider + .state) + .state = false; + final address = ref + .read( + exchangeFromAddressBookAddressStateProvider + .state) + .state; + if (address.isNotEmpty) { + _refundController.text = + address; + model.refundAddress = + _refundController.text; + } setState(() { enableNext = _toController .text.isNotEmpty && _refundController .text.isNotEmpty; }); - }, - child: const XIcon(), - ) - : TextFieldIconButton( - key: const Key( - "sendViewPasteAddressFieldButtonKey"), - onTap: () async { - final ClipboardData? data = - await clipboard.getData( - Clipboard.kTextPlain); - if (data?.text != null && - data!.text!.isNotEmpty) { - final content = - data.text!.trim(); + }); + }, + child: const AddressBookIcon(), + ), + if (_refundController.text.isEmpty) + TextFieldIconButton( + key: const Key( + "sendViewScanQrButtonKey"), + onTap: () async { + try { + final qrResult = + await scanner.scan(); + final results = + AddressUtils.parseUri( + qrResult.rawContent); + if (results.isNotEmpty) { + // auto fill address _refundController.text = - content; + results["address"] ?? + ""; + model.refundAddress = + _refundController.text; + + setState(() { + enableNext = _toController + .text + .isNotEmpty && + _refundController + .text.isNotEmpty; + }); + } else { + _refundController.text = + qrResult.rawContent; model.refundAddress = _refundController.text; @@ -578,115 +696,35 @@ class _Step2ViewState extends ConsumerState { .text.isNotEmpty; }); } - }, - child: _refundController - .text.isEmpty - ? const ClipboardIcon() - : const XIcon(), - ), - if (_refundController.text.isEmpty) - TextFieldIconButton( - key: const Key( - "sendViewAddressBookButtonKey"), - onTap: () { - ref - .read( - exchangeFlowIsActiveStateProvider - .state) - .state = true; - Navigator.of(context) - .pushNamed( - AddressBookView.routeName, - ) - .then((_) { - ref - .read( - exchangeFlowIsActiveStateProvider - .state) - .state = false; - final address = ref - .read( - exchangeFromAddressBookAddressStateProvider - .state) - .state; - if (address.isNotEmpty) { - _refundController.text = - address; - model.refundAddress = - _refundController.text; + } on PlatformException catch (e, s) { + Logging.instance.log( + "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s", + level: LogLevel.Warning, + ); } - setState(() { - enableNext = _toController - .text.isNotEmpty && - _refundController - .text.isNotEmpty; - }); - }); - }, - child: const AddressBookIcon(), - ), - if (_refundController.text.isEmpty) - TextFieldIconButton( - key: const Key( - "sendViewScanQrButtonKey"), - onTap: () async { - try { - final qrResult = - await scanner.scan(); - - final results = - AddressUtils.parseUri( - qrResult.rawContent); - if (results.isNotEmpty) { - // auto fill address - _refundController.text = - results["address"] ?? ""; - model.refundAddress = - _refundController.text; - - setState(() { - enableNext = _toController - .text.isNotEmpty && - _refundController - .text.isNotEmpty; - }); - } else { - _refundController.text = - qrResult.rawContent; - model.refundAddress = - _refundController.text; - - setState(() { - enableNext = _toController - .text.isNotEmpty && - _refundController - .text.isNotEmpty; - }); - } - } on PlatformException catch (e, s) { - Logging.instance.log( - "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s", - level: LogLevel.Warning, - ); - } - }, - child: const QrCodeIcon(), - ), - ], + }, + child: const QrCodeIcon(), + ), + ], + ), ), ), ), ), ), - ), - const SizedBox( - height: 6, - ), - RoundedWhiteContainer( - child: Text( - "In case something goes wrong during the exchange, we might need a refund address so we can return your coins back to you.", - style: STextStyles.label(context), + if (supportsRefund) + const SizedBox( + height: 6, ), + if (supportsRefund) + RoundedWhiteContainer( + child: Text( + "In case something goes wrong during the exchange, we might need a refund address so we can return your coins back to you.", + style: STextStyles.label(context), + ), + ), + const SizedBox( + height: 16, ), const Spacer(), Row( @@ -698,7 +736,7 @@ class _Step2ViewState extends ConsumerState { }, style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), child: Text( "Back", style: STextStyles.button(context).copyWith( diff --git a/lib/pages/exchange_view/exchange_step_views/step_3_view.dart b/lib/pages/exchange_view/exchange_step_views/step_3_view.dart index 467a3b9e7..43ba2fdf6 100644 --- a/lib/pages/exchange_view/exchange_step_views/step_3_view.dart +++ b/lib/pages/exchange_view/exchange_step_views/step_3_view.dart @@ -5,16 +5,17 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_4_view.dart'; -import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/step_row.dart'; -import 'package:stackwallet/providers/exchange/exchange_provider.dart'; import 'package:stackwallet/providers/global/trades_service_provider.dart'; +import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; +import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart'; import 'package:stackwallet/services/notifications_api.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; +import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_loading_overlay.dart'; @@ -51,6 +52,9 @@ class _Step3ViewState extends ConsumerState { @override Widget build(BuildContext context) { + final supportsRefund = + ref.watch(efExchangeProvider).name != MajesticBankExchange.exchangeName; + return Background( child: Scaffold( backgroundColor: Theme.of(context).extension()!.background, @@ -67,7 +71,7 @@ class _Step3ViewState extends ConsumerState { }, ), title: Text( - "Exchange", + "Swap", style: STextStyles.navBarTitle(context), ), ), @@ -174,27 +178,29 @@ class _Step3ViewState extends ConsumerState { ], ), ), - const SizedBox( - height: 8, - ), - RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Refund ${model.sendTicker.toUpperCase()} address", - style: STextStyles.itemSubtitle(context), - ), - const SizedBox( - height: 4, - ), - Text( - model.refundAddress!, - style: STextStyles.itemSubtitle12(context), - ) - ], + if (supportsRefund) + const SizedBox( + height: 8, + ), + if (supportsRefund) + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Refund ${model.sendTicker.toUpperCase()} address", + style: STextStyles.itemSubtitle(context), + ), + const SizedBox( + height: 4, + ), + Text( + model.refundAddress!, + style: STextStyles.itemSubtitle12(context), + ) + ], + ), ), - ), const SizedBox( height: 8, ), @@ -208,7 +214,7 @@ class _Step3ViewState extends ConsumerState { }, style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), child: Text( "Back", style: STextStyles.button(context).copyWith( @@ -247,7 +253,7 @@ class _Step3ViewState extends ConsumerState { final ExchangeResponse response = await ref - .read(exchangeProvider) + .read(efExchangeProvider) .createTrade( from: model.sendTicker, to: model.receiveTicker, @@ -259,27 +265,30 @@ class _Step3ViewState extends ConsumerState { addressTo: model.recipientAddress!, extraId: null, - addressRefund: - model.refundAddress!, + addressRefund: supportsRefund + ? model.refundAddress! + : "", refundExtraId: "", - rateId: model.rateId, + estimate: model.estimate, reversed: model.reversed, ); if (response.value == null) { if (mounted) { Navigator.of(context).pop(); - } - unawaited(showDialog( - context: context, - barrierDismissible: true, - builder: (_) => StackDialog( - title: "Failed to create trade", - message: - response.exception?.toString(), - ), - )); + unawaited( + showDialog( + context: context, + barrierDismissible: true, + builder: (_) => StackDialog( + title: "Failed to create trade", + message: response.exception + ?.toString(), + ), + ), + ); + } return; } @@ -322,7 +331,7 @@ class _Step3ViewState extends ConsumerState { }, style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), child: Text( "Next", style: STextStyles.button(context), diff --git a/lib/pages/exchange_view/exchange_step_views/step_4_view.dart b/lib/pages/exchange_view/exchange_step_views/step_4_view.dart index 101ac637f..853fed1cc 100644 --- a/lib/pages/exchange_view/exchange_step_views/step_4_view.dart +++ b/lib/pages/exchange_view/exchange_step_views/step_4_view.dart @@ -12,20 +12,25 @@ import 'package:stackwallet/pages/exchange_view/send_from_view.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/step_row.dart'; import 'package:stackwallet/pages/home_view/home_view.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dialog.dart'; +import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/route_generator.dart'; +import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; +import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; -import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; +import 'package:tuple/tuple.dart'; class Step4View extends ConsumerStatefulWidget { const Step4View({ @@ -66,7 +71,7 @@ class _Step4ViewState extends ConsumerState { Future _updateStatus() async { final statusResponse = - await ref.read(exchangeProvider).updateTrade(model.trade!); + await ref.read(efExchangeProvider).updateTrade(model.trade!); String status = "Waiting"; if (statusResponse.value != null) { status = statusResponse.value!.status; @@ -103,551 +108,697 @@ class _Step4ViewState extends ConsumerState { super.dispose(); } + Future _close() async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).popUntil( + ModalRoute.withName( + model.walletInitiated ? WalletView.routeName : HomeView.routeName, + ), + ); + } + } + + Future _showSendFromFiroBalanceSelectSheet(String walletId) async { + final firoWallet = ref + .read(walletsChangeNotifierProvider) + .getManager(walletId) + .wallet as FiroWallet; + final locale = ref.read(localeServiceChangeNotifierProvider).locale; + + return await showModalBottomSheet( + context: context, + backgroundColor: + Theme.of(context).extension()!.backgroundAppBar, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular( + Constants.size.circularBorderRadius * 3, + ), + ), + ), + builder: (context) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox( + height: 32, + ), + Text( + "Select Firo balance", + style: STextStyles.pageTitleH2(context), + ), + const SizedBox( + height: 32, + ), + SecondaryButton( + label: + "${firoWallet.balancePrivate.spendable.localizedStringAsFixed( + locale: locale, + )} (private)", + onPressed: () => Navigator.of(context).pop(false), + ), + const SizedBox( + height: 16, + ), + SecondaryButton( + label: "${firoWallet.balance.spendable.localizedStringAsFixed( + locale: locale, + )} (public)", + onPressed: () => Navigator.of(context).pop(true), + ), + const SizedBox( + height: 32, + ), + ], + ), + ); + }, + ); + } + + Future _confirmSend(Tuple2 tuple) async { + final bool firoPublicSend; + if (tuple.item2 == Coin.firo) { + final result = await _showSendFromFiroBalanceSelectSheet(tuple.item1); + if (result == null) { + return; + } else { + firoPublicSend = result; + } + } else { + firoPublicSend = false; + } + + final manager = + ref.read(walletsChangeNotifierProvider).getManager(tuple.item1); + + final Amount amount = model.sendAmount.toAmount( + fractionDigits: manager.coin.decimals, + ); + final address = model.trade!.payInAddress; + + bool wasCancelled = false; + try { + if (!mounted) return; + + unawaited( + showDialog( + context: context, + useSafeArea: false, + barrierDismissible: false, + builder: (context) { + return BuildingTransactionDialog( + coin: manager.coin, + onCancel: () { + wasCancelled = true; + }, + ); + }, + ), + ); + + final time = Future.delayed( + const Duration( + milliseconds: 2500, + ), + ); + + Future> txDataFuture; + + if (firoPublicSend) { + txDataFuture = (manager.wallet as FiroWallet).prepareSendPublic( + address: address, + amount: amount, + args: { + "feeRate": FeeRateType.average, + // ref.read(feeRateTypeStateProvider) + }, + ); + } else { + txDataFuture = manager.prepareSend( + address: address, + amount: amount, + args: { + "feeRate": FeeRateType.average, + // ref.read(feeRateTypeStateProvider) + }, + ); + } + + final results = await Future.wait([ + txDataFuture, + time, + ]); + + final txData = results.first as Map; + + if (!wasCancelled) { + // pop building dialog + + if (mounted) { + Navigator.of(context).pop(); + } + + txData["note"] = + "${model.trade!.payInCurrency.toUpperCase()}/${model.trade!.payOutCurrency.toUpperCase()} exchange"; + txData["address"] = address; + + if (mounted) { + unawaited( + Navigator.of(context).push( + RouteGenerator.getRoute( + shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute, + builder: (_) => ConfirmChangeNowSendView( + transactionInfo: txData, + walletId: tuple.item1, + routeOnSuccessName: HomeView.routeName, + trade: model.trade!, + ), + settings: const RouteSettings( + name: ConfirmChangeNowSendView.routeName, + ), + ), + ), + ); + } + } + } catch (e) { + if (mounted && !wasCancelled) { + // pop building dialog + Navigator.of(context).pop(); + + unawaited( + showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return StackDialog( + title: "Transaction failed", + message: e.toString(), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Ok", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .buttonTextSecondary, + ), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ); + }, + ), + ); + } + } + } + @override Widget build(BuildContext context) { final bool isWalletCoin = _isWalletCoinAndHasWallet(model.trade!.payInCurrency, ref); - return Background( - child: Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, + return WillPopScope( + onWillPop: () async { + await _close(); + return false; + }, + child: Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: Padding( + padding: const EdgeInsets.all(10), + child: AppBarIconButton( + size: 32, + color: Theme.of(context).extension()!.background, + shadows: const [], + icon: SvgPicture.asset( + Assets.svg.x, + width: 24, + height: 24, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, + ), + onPressed: _close, + ), + ), + title: Text( + "Swap", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Exchange", - style: STextStyles.navBarTitle(context), - ), - ), - body: LayoutBuilder( - builder: (context, constraints) { - final width = MediaQuery.of(context).size.width - 32; - return Padding( - padding: const EdgeInsets.all(12), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 24, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - StepRow( - count: 4, - current: 3, - width: width, - ), - const SizedBox( - height: 14, - ), - Text( - "Send ${model.sendTicker.toUpperCase()} to the address below", - style: STextStyles.pageTitleH1(context), - ), - const SizedBox( - height: 8, - ), - Text( - "Send ${model.sendTicker.toUpperCase()} to the address below. Once it is received, ${model.trade!.exchangeName} will send the ${model.receiveTicker.toUpperCase()} to the recipient address you provided. You can find this trade details and check its status in the list of trades.", - style: STextStyles.itemSubtitle(context), - ), - const SizedBox( - height: 12, - ), - RoundedContainer( - color: Theme.of(context) - .extension()! - .warningBackground, - child: RichText( - text: TextSpan( - text: - "You must send at least ${model.sendAmount.toString()} ${model.sendTicker}. ", - style: STextStyles.label700(context).copyWith( - color: Theme.of(context) - .extension()! - .warningForeground, + body: LayoutBuilder( + builder: (context, constraints) { + final width = MediaQuery.of(context).size.width - 32; + return Padding( + padding: const EdgeInsets.all(12), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + StepRow( + count: 4, + current: 3, + width: width, + ), + const SizedBox( + height: 14, + ), + Text( + "Send ${model.sendTicker.toUpperCase()} to the address below", + style: STextStyles.pageTitleH1(context), + ), + const SizedBox( + height: 8, + ), + Text( + "Send ${model.sendTicker.toUpperCase()} to the address below. Once it is received, ${model.trade!.exchangeName} will send the ${model.receiveTicker.toUpperCase()} to the recipient address you provided. You can find this trade details and check its status in the list of trades.", + style: STextStyles.itemSubtitle(context), + ), + const SizedBox( + height: 12, + ), + RoundedContainer( + color: Theme.of(context) + .extension()! + .warningBackground, + child: RichText( + text: TextSpan( + text: + "You must send at least ${model.sendAmount.toString()} ${model.sendTicker}. ", + style: STextStyles.label700(context).copyWith( + color: Theme.of(context) + .extension()! + .warningForeground, + ), + children: [ + TextSpan( + text: + "If you send less than ${model.sendAmount.toString()} ${model.sendTicker}, your transaction may not be converted and it may not be refunded.", + style: + STextStyles.label(context).copyWith( + color: Theme.of(context) + .extension()! + .warningForeground, + ), + ), + ], ), + ), + ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - TextSpan( - text: - "If you send less than ${model.sendAmount.toString()} ${model.sendTicker}, your transaction may not be converted and it may not be refunded.", - style: STextStyles.label(context).copyWith( + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Amount", + style: + STextStyles.itemSubtitle(context), + ), + GestureDetector( + onTap: () async { + final data = ClipboardData( + text: + model.sendAmount.toString()); + await clipboard.setData(data); + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + context: context, + ), + ); + } + }, + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.copy, + color: Theme.of(context) + .extension()! + .infoItemIcons, + width: 10, + ), + const SizedBox( + width: 4, + ), + Text( + "Copy", + style: STextStyles.link2(context), + ), + ], + ), + ), + ], + ), + const SizedBox( + height: 4, + ), + Text( + "${model.sendAmount.toString()} ${model.sendTicker.toUpperCase()}", + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Send ${model.sendTicker.toUpperCase()} to this address", + style: + STextStyles.itemSubtitle(context), + ), + GestureDetector( + onTap: () async { + final data = ClipboardData( + text: model.trade!.payInAddress); + await clipboard.setData(data); + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + context: context, + ), + ); + } + }, + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.copy, + color: Theme.of(context) + .extension()! + .infoItemIcons, + width: 10, + ), + const SizedBox( + width: 4, + ), + Text( + "Copy", + style: STextStyles.link2(context), + ), + ], + ), + ), + ], + ), + const SizedBox( + height: 4, + ), + Text( + model.trade!.payInAddress, + style: STextStyles.itemSubtitle12(context), + ), + ], + ), + ), + const SizedBox( + height: 6, + ), + RoundedWhiteContainer( + child: Row( + children: [ + Text( + "Trade ID", + style: STextStyles.itemSubtitle(context), + ), + const Spacer(), + Row( + children: [ + Text( + model.trade!.tradeId, + style: + STextStyles.itemSubtitle12(context), + ), + const SizedBox( + width: 10, + ), + GestureDetector( + onTap: () async { + final data = ClipboardData( + text: model.trade!.tradeId); + await clipboard.setData(data); + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + context: context, + ), + ); + } + }, + child: SvgPicture.asset( + Assets.svg.copy, + color: Theme.of(context) + .extension()! + .infoItemIcons, + width: 12, + ), + ) + ], + ) + ], + ), + ), + const SizedBox( + height: 6, + ), + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Status", + style: STextStyles.itemSubtitle(context), + ), + Text( + _statusString, + style: STextStyles.itemSubtitle(context) + .copyWith( color: Theme.of(context) .extension()! - .warningForeground, + .colorForStatus(_statusString), ), ), ], ), ), - ), - const SizedBox( - height: 8, - ), - RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - "Amount", - style: STextStyles.itemSubtitle(context), - ), - GestureDetector( - onTap: () async { - final data = ClipboardData( - text: model.sendAmount.toString()); - await clipboard.setData(data); - unawaited(showFloatingFlushBar( - type: FlushBarType.info, - message: "Copied to clipboard", - context: context, - )); - }, - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.copy, - color: Theme.of(context) - .extension()! - .infoItemIcons, - width: 10, - ), - const SizedBox( - width: 4, - ), - Text( - "Copy", - style: STextStyles.link2(context), - ), - ], - ), - ), - ], - ), - const SizedBox( - height: 4, - ), - Text( - "${model.sendAmount.toString()} ${model.sendTicker.toUpperCase()}", - style: STextStyles.itemSubtitle12(context), - ), - ], - ), - ), - const SizedBox( - height: 8, - ), - RoundedWhiteContainer( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - "Send ${model.sendTicker.toUpperCase()} to this address", - style: STextStyles.itemSubtitle(context), - ), - GestureDetector( - onTap: () async { - final data = ClipboardData( - text: model.trade!.payInAddress); - await clipboard.setData(data); - unawaited(showFloatingFlushBar( - type: FlushBarType.info, - message: "Copied to clipboard", - context: context, - )); - }, - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.copy, - color: Theme.of(context) - .extension()! - .infoItemIcons, - width: 10, - ), - const SizedBox( - width: 4, - ), - Text( - "Copy", - style: STextStyles.link2(context), - ), - ], - ), - ), - ], - ), - const SizedBox( - height: 4, - ), - Text( - model.trade!.payInAddress, - style: STextStyles.itemSubtitle12(context), - ), - ], - ), - ), - const SizedBox( - height: 6, - ), - RoundedWhiteContainer( - child: Row( - children: [ - Text( - "Trade ID", - style: STextStyles.itemSubtitle(context), - ), - const Spacer(), - Row( - children: [ - Text( - model.trade!.tradeId, - style: - STextStyles.itemSubtitle12(context), - ), - const SizedBox( - width: 10, - ), - GestureDetector( - onTap: () async { - final data = ClipboardData( - text: model.trade!.tradeId); - await clipboard.setData(data); - unawaited(showFloatingFlushBar( - type: FlushBarType.info, - message: "Copied to clipboard", - context: context, - )); - }, - child: SvgPicture.asset( - Assets.svg.copy, - color: Theme.of(context) - .extension()! - .infoItemIcons, - width: 12, - ), - ) - ], - ) - ], - ), - ), - const SizedBox( - height: 6, - ), - RoundedWhiteContainer( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Status", - style: STextStyles.itemSubtitle(context), - ), - Text( - _statusString, - style: STextStyles.itemSubtitle(context) - .copyWith( - color: Theme.of(context) - .extension()! - .colorForStatus(_statusString), - ), - ), - ], - ), - ), - const Spacer(), - const SizedBox( - height: 12, - ), - TextButton( - onPressed: () { - showDialog( - context: context, - barrierDismissible: true, - builder: (_) { - return StackDialogBase( - child: Column( - children: [ - const SizedBox( - height: 8, - ), - Center( - child: Text( - "Send ${model.sendTicker} to this address", - style: STextStyles.pageTitleH2( - context), - ), - ), - const SizedBox( - height: 24, - ), - Center( - child: QrImage( - // TODO: grab coin uri scheme from somewhere - // data: "${coin.uriScheme}:$receivingAddress", - data: model.trade!.payInAddress, - size: MediaQuery.of(context) - .size - .width / - 2, - foregroundColor: Theme.of(context) - .extension()! - .accentColorDark, - ), - ), - const SizedBox( - height: 24, - ), - Row( - children: [ - const Spacer(), - Expanded( - child: TextButton( - onPressed: () => - Navigator.of(context).pop(), - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor( - context), - child: Text( - "Cancel", - style: STextStyles.button( - context) - .copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .buttonTextSecondary, - ), - ), - ), - ), - ], - ) - ], - ), - ); - }, - ); - }, - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor(context), - child: Text( - "Show QR Code", - style: STextStyles.button(context), - ), - ), - if (isWalletCoin) + const Spacer(), const SizedBox( height: 12, ), - if (isWalletCoin) - Builder( - builder: (context) { - String buttonTitle = "Send from Stack Wallet"; - - final tuple = ref - .read(exchangeSendFromWalletIdStateProvider - .state) - .state; - if (tuple != null && - model.sendTicker.toLowerCase() == - tuple.item2.ticker.toLowerCase()) { - final walletName = ref - .read(walletsChangeNotifierProvider) - .getManager(tuple.item1) - .walletName; - buttonTitle = "Send from $walletName"; - } - - return TextButton( - onPressed: tuple != null && - model.sendTicker.toLowerCase() == - tuple.item2.ticker.toLowerCase() - ? () async { - final manager = ref - .read( - walletsChangeNotifierProvider) - .getManager(tuple.item1); - - final amount = - Format.decimalAmountToSatoshis( - model.sendAmount, - manager.coin); - final address = - model.trade!.payInAddress; - - try { - bool wasCancelled = false; - - unawaited(showDialog( - context: context, - useSafeArea: false, - barrierDismissible: false, - builder: (context) { - return BuildingTransactionDialog( - onCancel: () { - wasCancelled = true; - - Navigator.of(context).pop(); - }, - ); - }, - )); - - final txData = - await manager.prepareSend( - address: address, - satoshiAmount: amount, - args: { - "feeRate": FeeRateType.average, - // ref.read(feeRateTypeStateProvider) - }, - ); - - if (!wasCancelled) { - // pop building dialog - - if (mounted) { - Navigator.of(context).pop(); - } - - txData["note"] = - "${model.trade!.payInCurrency.toUpperCase()}/${model.trade!.payOutCurrency.toUpperCase()} exchange"; - txData["address"] = address; - - if (mounted) { - unawaited( - Navigator.of(context).push( - RouteGenerator.getRoute( - shouldUseMaterialRoute: - RouteGenerator - .useMaterialPageRoute, - builder: (_) => - ConfirmChangeNowSendView( - transactionInfo: txData, - walletId: tuple.item1, - routeOnSuccessName: - HomeView.routeName, - trade: model.trade!, - ), - settings: - const RouteSettings( - name: - ConfirmChangeNowSendView - .routeName, - ), - ), - )); - } - } - } catch (e) { - // if (mounted) { - // pop building dialog - Navigator.of(context).pop(); - - unawaited(showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) { - return StackDialog( - title: "Transaction failed", - message: e.toString(), - rightButton: TextButton( - style: Theme.of(context) - .extension< - StackColors>()! - .getSecondaryEnabledButtonColor( - context), - child: Text( - "Ok", - style: STextStyles.button( - context) - .copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .buttonTextSecondary, - ), - ), - onPressed: () { - Navigator.of(context) - .pop(); - }, - ), - ); - }, - )); - // } - } - } - : () { - Navigator.of(context).push( - RouteGenerator.getRoute( - shouldUseMaterialRoute: - RouteGenerator - .useMaterialPageRoute, - builder: (BuildContext context) { - return SendFromView( - coin: - coinFromTickerCaseInsensitive( - model.trade! - .payInCurrency), - amount: model.sendAmount, - address: - model.trade!.payInAddress, - trade: model.trade!, - ); - }, - settings: const RouteSettings( - name: SendFromView.routeName, - ), + TextButton( + onPressed: () { + showDialog( + context: context, + barrierDismissible: true, + builder: (_) { + return StackDialogBase( + child: Column( + children: [ + const SizedBox( + height: 8, + ), + Center( + child: Text( + "Send ${model.sendTicker} to this address", + style: STextStyles.pageTitleH2( + context), ), - ); - }, - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - child: Text( - buttonTitle, - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .buttonTextSecondary, - ), - ), + ), + const SizedBox( + height: 24, + ), + Center( + child: QrImage( + // TODO: grab coin uri scheme from somewhere + // data: "${coin.uriScheme}:$receivingAddress", + data: model.trade!.payInAddress, + size: MediaQuery.of(context) + .size + .width / + 2, + foregroundColor: Theme.of(context) + .extension()! + .accentColorDark, + ), + ), + const SizedBox( + height: 24, + ), + Row( + children: [ + const Spacer(), + Expanded( + child: TextButton( + onPressed: () => + Navigator.of(context) + .pop(), + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle( + context), + child: Text( + "Cancel", + style: STextStyles.button( + context) + .copyWith( + color: Theme.of(context) + .extension< + StackColors>()! + .buttonTextSecondary, + ), + ), + ), + ), + ], + ) + ], + ), + ); + }, ); }, + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonStyle(context), + child: Text( + "Show QR Code", + style: STextStyles.button(context), + ), ), - ], + if (isWalletCoin) + const SizedBox( + height: 12, + ), + if (isWalletCoin) + Builder( + builder: (context) { + String buttonTitle = "Send from Stack Wallet"; + + final tuple = ref + .read( + exchangeSendFromWalletIdStateProvider + .state) + .state; + if (tuple != null && + model.sendTicker.toLowerCase() == + tuple.item2.ticker.toLowerCase()) { + final walletName = ref + .read(walletsChangeNotifierProvider) + .getManager(tuple.item1) + .walletName; + buttonTitle = "Send from $walletName"; + } + + return TextButton( + onPressed: tuple != null && + model.sendTicker.toLowerCase() == + tuple.item2.ticker.toLowerCase() + ? () async { + await _confirmSend(tuple); + } + : () { + Navigator.of(context).push( + RouteGenerator.getRoute( + shouldUseMaterialRoute: + RouteGenerator + .useMaterialPageRoute, + builder: + (BuildContext context) { + final coin = + coinFromTickerCaseInsensitive( + model.trade! + .payInCurrency); + return SendFromView( + coin: coin, + amount: model.sendAmount + .toAmount( + fractionDigits: + coin.decimals, + ), + address: model + .trade!.payInAddress, + trade: model.trade!, + ); + }, + settings: const RouteSettings( + name: SendFromView.routeName, + ), + ), + ); + }, + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle( + context), + child: Text( + buttonTitle, + style: + STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .buttonTextSecondary, + ), + ), + ); + }, + ), + ], + ), ), ), ), ), - ), - ); - }, + ); + }, + ), ), ), ); diff --git a/lib/pages/exchange_view/exchange_view.dart b/lib/pages/exchange_view/exchange_view.dart index 8fada855d..b8b7e48ca 100644 --- a/lib/pages/exchange_view/exchange_view.dart +++ b/lib/pages/exchange_view/exchange_view.dart @@ -2,13 +2,19 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart'; import 'package:stackwallet/pages/exchange_view/exchange_form.dart'; import 'package:stackwallet/pages/exchange_view/trade_details_view.dart'; import 'package:stackwallet/providers/global/trades_service_provider.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_loading_overlay.dart'; import 'package:stackwallet/widgets/trade_card.dart'; import 'package:tuple/tuple.dart'; @@ -20,8 +26,44 @@ class ExchangeView extends ConsumerStatefulWidget { } class _ExchangeViewState extends ConsumerState { + bool _initialCachePopulationUnderway = false; + @override void initState() { + if (!ref.read(prefsChangeNotifierProvider).externalCalls) { + if (ExchangeDataLoadingService.currentCacheVersion < + ExchangeDataLoadingService.cacheVersion) { + _initialCachePopulationUnderway = true; + ExchangeDataLoadingService.instance.onLoadingComplete = () { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty( + ref.read(efCurrencyPairProvider), + ref.read(efRateTypeProvider), + ); + setState(() { + _initialCachePopulationUnderway = false; + }); + }); + }; + } + ExchangeDataLoadingService.instance.loadAll(); + } else if (ExchangeDataLoadingService.instance.isLoading && + ExchangeDataLoadingService.currentCacheVersion < + ExchangeDataLoadingService.cacheVersion) { + _initialCachePopulationUnderway = true; + ExchangeDataLoadingService.instance.onLoadingComplete = () { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty( + ref.read(efCurrencyPairProvider), + ref.read(efRateTypeProvider), + ); + setState(() { + _initialCachePopulationUnderway = false; + }); + }); + }; + } + super.initState(); } @@ -34,153 +76,176 @@ class _ExchangeViewState extends ConsumerState { Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - return SafeArea( - child: NestedScrollView( - floatHeaderSlivers: true, - headerSliverBuilder: (context, innerBoxIsScrolled) { - return [ - SliverOverlapAbsorber( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), - sliver: const SliverToBoxAdapter( - child: Padding( - padding: EdgeInsets.only( - left: 16, - right: 16, - top: 16, - ), - child: ExchangeForm(), - ), + return ConditionalParent( + condition: _initialCachePopulationUnderway, + builder: (child) { + return Stack( + children: [ + child, + Material( + color: Theme.of(context) + .extension()! + .overlay + .withOpacity(0.6), + child: const CustomLoadingOverlay( + message: "Updating exchange data", + subMessage: "This could take a few minutes", + eventBus: null, ), ) - ]; - }, - body: Builder( - builder: (buildContext) { - final trades = ref - .watch(tradesServiceProvider.select((value) => value.trades)); - final tradeCount = trades.length; - final hasHistory = tradeCount > 0; - - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 12), - child: CustomScrollView( - slivers: [ - SliverOverlapInjector( - handle: NestedScrollView.sliverOverlapAbsorberHandleFor( - buildContext, + ], + ); + }, + child: SafeArea( + child: NestedScrollView( + floatHeaderSlivers: true, + headerSliverBuilder: (context, innerBoxIsScrolled) { + return [ + SliverOverlapAbsorber( + handle: + NestedScrollView.sliverOverlapAbsorberHandleFor(context), + sliver: const SliverToBoxAdapter( + child: Padding( + padding: EdgeInsets.only( + left: 16, + right: 16, + top: 16, ), + child: ExchangeForm(), ), - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const SizedBox( - height: 12, - ), - Text( - "Trades", - style: STextStyles.itemSubtitle(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark3, - ), - ), - const SizedBox( - height: 12, - ), - ], + ), + ) + ]; + }, + body: Builder( + builder: (buildContext) { + final trades = ref + .watch(tradesServiceProvider.select((value) => value.trades)); + final tradeCount = trades.length; + final hasHistory = tradeCount > 0; + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: CustomScrollView( + slivers: [ + SliverOverlapInjector( + handle: NestedScrollView.sliverOverlapAbsorberHandleFor( + buildContext, ), ), - ), - if (hasHistory) - SliverList( - delegate: SliverChildBuilderDelegate((context, index) { - return Padding( - padding: const EdgeInsets.all(4), - child: TradeCard( - key: Key("tradeCard_${trades[index].uuid}"), - trade: trades[index], - onTap: () async { - final String tradeId = trades[index].tradeId; - - final lookup = ref - .read(tradeSentFromStackLookupProvider) - .all; - - //todo: check if print needed - // debugPrint("ALL: $lookup"); - - final String? txid = ref - .read(tradeSentFromStackLookupProvider) - .getTxidForTradeId(tradeId); - final List? walletIds = ref - .read(tradeSentFromStackLookupProvider) - .getWalletIdsForTradeId(tradeId); - - if (txid != null && - walletIds != null && - walletIds.isNotEmpty) { - final manager = ref - .read(walletsChangeNotifierProvider) - .getManager(walletIds.first); - - //todo: check if print needed - // debugPrint("name: ${manager.walletName}"); - - // TODO store tx data completely locally in isar so we don't lock up ui here when querying txData - final txData = await manager.transactionData; - - final tx = txData.getAllTransactions()[txid]; - - if (mounted) { - unawaited(Navigator.of(context).pushNamed( - TradeDetailsView.routeName, - arguments: Tuple4(tradeId, tx, - walletIds.first, manager.walletName), - )); - } - } else { - unawaited(Navigator.of(context).pushNamed( - TradeDetailsView.routeName, - arguments: Tuple4( - tradeId, null, walletIds?.first, null), - )); - } - }, - ), - ); - }, childCount: tradeCount), - ), - if (!hasHistory) SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 4), - child: Container( - decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .popupBG, - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox( + height: 12, ), - ), - child: Padding( - padding: const EdgeInsets.all(12), - child: Text( - "Trades will appear here", - textAlign: TextAlign.center, - style: STextStyles.itemSubtitle(context), + Text( + "Trades", + style: STextStyles.itemSubtitle(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark3, + ), + ), + const SizedBox( + height: 12, + ), + ], + ), + ), + ), + if (hasHistory) + SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + return Padding( + padding: const EdgeInsets.all(4), + child: TradeCard( + key: Key("tradeCard_${trades[index].uuid}"), + trade: trades[index], + onTap: () async { + final String tradeId = trades[index].tradeId; + + final lookup = ref + .read(tradeSentFromStackLookupProvider) + .all; + + //todo: check if print needed + // debugPrint("ALL: $lookup"); + + final String? txid = ref + .read(tradeSentFromStackLookupProvider) + .getTxidForTradeId(tradeId); + final List? walletIds = ref + .read(tradeSentFromStackLookupProvider) + .getWalletIdsForTradeId(tradeId); + + if (txid != null && + walletIds != null && + walletIds.isNotEmpty) { + final manager = ref + .read(walletsChangeNotifierProvider) + .getManager(walletIds.first); + + //todo: check if print needed + // debugPrint("name: ${manager.walletName}"); + + final tx = await MainDB.instance + .getTransactions(walletIds.first) + .filter() + .txidEqualTo(txid) + .findFirst(); + + if (mounted) { + unawaited(Navigator.of(context).pushNamed( + TradeDetailsView.routeName, + arguments: Tuple4(tradeId, tx, + walletIds.first, manager.walletName), + )); + } + } else { + unawaited(Navigator.of(context).pushNamed( + TradeDetailsView.routeName, + arguments: Tuple4( + tradeId, null, walletIds?.first, null), + )); + } + }, + ), + ); + }, childCount: tradeCount), + ), + if (!hasHistory) + SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Container( + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .popupBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: Padding( + padding: const EdgeInsets.all(12), + child: Text( + "Trades will appear here", + textAlign: TextAlign.center, + style: STextStyles.itemSubtitle(context), + ), ), ), ), ), - ), - ], - ), - ); - }, + ], + ), + ); + }, + ), ), ), ); diff --git a/lib/pages/exchange_view/send_from_view.dart b/lib/pages/exchange_view/send_from_view.dart index 6a7fe5285..28164e850 100644 --- a/lib/pages/exchange_view/send_from_view.dart +++ b/lib/pages/exchange_view/send_from_view.dart @@ -1,6 +1,6 @@ import 'dart:async'; +import 'dart:io'; -import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; @@ -13,15 +13,15 @@ import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; import 'package:stackwallet/services/coins/manager.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; -import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; -import 'package:stackwallet/widgets/animated_text.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; @@ -45,7 +45,7 @@ class SendFromView extends ConsumerStatefulWidget { static const String routeName = "/sendFrom"; final Coin coin; - final Decimal amount; + final Amount amount; final String address; final Trade trade; final bool shouldPopRoot; @@ -57,14 +57,10 @@ class SendFromView extends ConsumerStatefulWidget { class _SendFromViewState extends ConsumerState { late final Coin coin; - late final Decimal amount; + late final Amount amount; late final String address; late final Trade trade; - String formatAmount(Decimal amount, Coin coin) { - return amount.toStringAsFixed(Constants.decimalPlacesForCoin(coin)); - } - @override void initState() { coin = widget.coin; @@ -151,7 +147,13 @@ class _SendFromViewState extends ConsumerState { Row( children: [ Text( - "You need to send ${formatAmount(amount, coin)} ${coin.ticker}", + "You need to send ${amount.localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), + ), + )} ${coin.ticker}", style: isDesktop ? STextStyles.desktopTextExtraExtraSmall(context) : STextStyles.itemSubtitle(context), @@ -202,7 +204,7 @@ class SendFromCard extends ConsumerStatefulWidget { }) : super(key: key); final String walletId; - final Decimal amount; + final Amount amount; final String address; final Trade trade; final bool fromDesktopStep4; @@ -213,13 +215,11 @@ class SendFromCard extends ConsumerStatefulWidget { class _SendFromCardState extends ConsumerState { late final String walletId; - late final Decimal amount; + late final Amount amount; late final String address; late final Trade trade; Future _send(Manager manager, {bool? shouldSendPublicFiroFunds}) async { - final _amount = Format.decimalAmountToSatoshis(amount, manager.coin); - try { bool wasCancelled = false; @@ -240,6 +240,7 @@ class _SendFromCardState extends ConsumerState { ), ), child: BuildingTransactionDialog( + coin: manager.coin, onCancel: () { wasCancelled = true; @@ -251,13 +252,20 @@ class _SendFromCardState extends ConsumerState { ), ); - late Map txData; + final time = Future.delayed( + const Duration( + milliseconds: 2500, + ), + ); + + Map txData; + Future> txDataFuture; // if not firo then do normal send if (shouldSendPublicFiroFunds == null) { - txData = await manager.prepareSend( + txDataFuture = manager.prepareSend( address: address, - satoshiAmount: _amount, + amount: amount, args: { "feeRate": FeeRateType.average, // ref.read(feeRateTypeStateProvider) @@ -267,18 +275,18 @@ class _SendFromCardState extends ConsumerState { final firoWallet = manager.wallet as FiroWallet; // otherwise do firo send based on balance selected if (shouldSendPublicFiroFunds) { - txData = await firoWallet.prepareSendPublic( + txDataFuture = firoWallet.prepareSendPublic( address: address, - satoshiAmount: _amount, + amount: amount, args: { "feeRate": FeeRateType.average, // ref.read(feeRateTypeStateProvider) }, ); } else { - txData = await firoWallet.prepareSend( + txDataFuture = firoWallet.prepareSend( address: address, - satoshiAmount: _amount, + amount: amount, args: { "feeRate": FeeRateType.average, // ref.read(feeRateTypeStateProvider) @@ -287,6 +295,13 @@ class _SendFromCardState extends ConsumerState { } } + final results = await Future.wait([ + txDataFuture, + time, + ]); + + txData = results.first as Map; + if (!wasCancelled) { // pop building dialog @@ -338,7 +353,7 @@ class _SendFromCardState extends ConsumerState { rightButton: TextButton( style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), child: Text( "Ok", style: STextStyles.button(context).copyWith( @@ -437,35 +452,11 @@ class _SendFromCardState extends ConsumerState { "Use private balance", style: STextStyles.itemSubtitle(context), ), - FutureBuilder( - future: (manager.wallet as FiroWallet) - .availablePrivateBalance(), - builder: (builderContext, - AsyncSnapshot snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - return Text( - "${Format.localizedStringAsFixed( - value: snapshot.data!, - locale: locale, - decimalPlaces: - Constants.decimalPlacesForCoin(coin), - )} ${coin.ticker}", - style: STextStyles.itemSubtitle(context), - ); - } else { - return AnimatedText( - stringsToLoopThrough: const [ - "Loading balance", - "Loading balance.", - "Loading balance..", - "Loading balance..." - ], - style: STextStyles.itemSubtitle(context), - ); - } - }, + Text( + "${(manager.wallet as FiroWallet).availablePrivateBalance().localizedStringAsFixed( + locale: locale, + )} ${coin.ticker}", + style: STextStyles.itemSubtitle(context), ), ], ), @@ -523,35 +514,11 @@ class _SendFromCardState extends ConsumerState { "Use public balance", style: STextStyles.itemSubtitle(context), ), - FutureBuilder( - future: (manager.wallet as FiroWallet) - .availablePublicBalance(), - builder: (builderContext, - AsyncSnapshot snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - return Text( - "${Format.localizedStringAsFixed( - value: snapshot.data!, - locale: locale, - decimalPlaces: - Constants.decimalPlacesForCoin(coin), - )} ${coin.ticker}", - style: STextStyles.itemSubtitle(context), - ); - } else { - return AnimatedText( - stringsToLoopThrough: const [ - "Loading balance", - "Loading balance.", - "Loading balance..", - "Loading balance..." - ], - style: STextStyles.itemSubtitle(context), - ); - } - }, + Text( + "${(manager.wallet as FiroWallet).availablePublicBalance().localizedStringAsFixed( + locale: locale, + )} ${coin.ticker}", + style: STextStyles.itemSubtitle(context), ), ], ), @@ -609,8 +576,12 @@ class _SendFromCardState extends ConsumerState { ), child: Padding( padding: const EdgeInsets.all(6), - child: SvgPicture.asset( - Assets.svg.iconFor(coin: coin), + child: SvgPicture.file( + File( + ref.watch( + coinIconProvider(coin), + ), + ), width: 24, height: 24, ), @@ -633,34 +604,11 @@ class _SendFromCardState extends ConsumerState { height: 2, ), if (!isFiro) - FutureBuilder( - future: manager.totalBalance, - builder: - (builderContext, AsyncSnapshot snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - return Text( - "${Format.localizedStringAsFixed( - value: snapshot.data!, - locale: locale, - decimalPlaces: - Constants.decimalPlacesForCoin(coin), - )} ${coin.ticker}", - style: STextStyles.itemSubtitle(context), - ); - } else { - return AnimatedText( - stringsToLoopThrough: const [ - "Loading balance", - "Loading balance.", - "Loading balance..", - "Loading balance..." - ], - style: STextStyles.itemSubtitle(context), - ); - } - }, + Text( + "${manager.balance.spendable.localizedStringAsFixed( + locale: locale, + )} ${coin.ticker}", + style: STextStyles.itemSubtitle(context), ), ], ), diff --git a/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart b/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart new file mode 100644 index 000000000..6f13d00a2 --- /dev/null +++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_option.dart @@ -0,0 +1,349 @@ +import 'package:decimal/decimal.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; +import 'package:stackwallet/providers/exchange/exchange_form_state_provider.dart'; +import 'package:stackwallet/providers/global/locale_provider.dart'; +import 'package:stackwallet/services/exchange/exchange.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/animated_text.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/exchange/trocador/trocador_kyc_info_button.dart'; +import 'package:stackwallet/widgets/exchange/trocador/trocador_rating_type_enum.dart'; + +class ExchangeOption extends ConsumerStatefulWidget { + const ExchangeOption({ + Key? key, + required this.exchange, + required this.fixedRate, + required this.reversed, + }) : super(key: key); + + final Exchange exchange; + final bool fixedRate; + final bool reversed; + + @override + ConsumerState createState() => _ExchangeOptionState(); +} + +class _ExchangeOptionState extends ConsumerState { + final isDesktop = Util.isDesktop; + + @override + Widget build(BuildContext context) { + final sendCurrency = + ref.watch(efCurrencyPairProvider.select((value) => value.send)); + final receivingCurrency = + ref.watch(efCurrencyPairProvider.select((value) => value.receive)); + final reversed = ref.watch(efReversedProvider); + final amount = reversed + ? ref.watch(efReceiveAmountProvider) + : ref.watch(efSendAmountProvider); + + final data = ref.watch(efEstimatesListProvider(widget.exchange.name)); + final estimates = data?.item1.value; + + return AnimatedSize( + duration: const Duration(milliseconds: 500), + curve: Curves.easeInOutCubicEmphasized, + child: Builder( + builder: (_) { + if (ref.watch(efRefreshingProvider)) { + // show loading + return _ProviderOption( + exchange: widget.exchange, + estimate: null, + rateString: "", + loadingString: true, + ); + } else if (sendCurrency != null && + receivingCurrency != null && + amount != null && + amount > Decimal.zero) { + if (estimates != null && estimates.isNotEmpty) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + for (int i = 0; i < estimates.length; i++) + Builder( + builder: (context) { + final e = estimates[i]; + + int decimals; + try { + decimals = coinFromTickerCaseInsensitive( + receivingCurrency.ticker) + .decimals; + } catch (_) { + decimals = 8; // some reasonable alternative + } + Amount rate; + if (e.reversed) { + rate = (amount / e.estimatedAmount) + .toDecimal(scaleOnInfinitePrecision: 18) + .toAmount(fractionDigits: decimals); + } else { + rate = (e.estimatedAmount / amount) + .toDecimal(scaleOnInfinitePrecision: 18) + .toAmount(fractionDigits: decimals); + } + + final rateString = + "1 ${sendCurrency.ticker.toUpperCase()} ~ ${rate.localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider + .select((value) => value.locale), + ), + )} ${receivingCurrency.ticker.toUpperCase()}"; + + return ConditionalParent( + condition: i > 0, + builder: (child) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + isDesktop + ? Container( + height: 1, + color: Theme.of(context) + .extension()! + .background, + ) + : const SizedBox( + height: 16, + ), + child, + ], + ), + child: _ProviderOption( + key: Key(widget.exchange.name + e.exchangeProvider), + exchange: widget.exchange, + estimate: e, + rateString: rateString, + kycRating: e.kycRating, + ), + ); + }, + ), + ], + ); + } else { + Logging.instance.log( + "$runtimeType rate unavailable for ${widget.exchange.name}: $data", + level: LogLevel.Warning, + ); + + return Consumer( + builder: (_, ref, __) { + String? message; + + final range = data?.item2; + if (range != null) { + if (range.min != null && amount < range.min!) { + message ??= "Amount too small"; + } else if (range.max != null && amount > range.max!) { + message ??= "Amount too large"; + } + } else if (data?.item1.value == null) { + final rateType = ref.watch(efRateTypeProvider) == + ExchangeRateType.estimated + ? "estimated" + : "fixed"; + message ??= "Pair unavailable on $rateType rate flow"; + } + + return _ProviderOption( + exchange: widget.exchange, + estimate: null, + rateString: message ?? "Failed to fetch rate", + rateColor: + Theme.of(context).extension()!.textError, + ); + }, + ); + } + } else { + // show n/a + return _ProviderOption( + exchange: widget.exchange, + estimate: null, + rateString: "n/a", + ); + } + }, + ), + ); + } +} + +class _ProviderOption extends ConsumerStatefulWidget { + const _ProviderOption({ + Key? key, + required this.exchange, + required this.estimate, + required this.rateString, + this.kycRating, + this.loadingString = false, + this.rateColor, + }) : super(key: key); + + final Exchange exchange; + final Estimate? estimate; + final String rateString; + final String? kycRating; + final bool loadingString; + final Color? rateColor; + + @override + ConsumerState<_ProviderOption> createState() => _ProviderOptionState(); +} + +class _ProviderOptionState extends ConsumerState<_ProviderOption> { + final isDesktop = Util.isDesktop; + + late final String _id; + + @override + void initState() { + _id = + "${widget.exchange.name} (${widget.estimate?.exchangeProvider ?? widget.exchange.name})"; + super.initState(); + } + + @override + Widget build(BuildContext context) { + String groupValue = ref.watch(currentCombinedExchangeIdProvider); + + if (ref.watch(efExchangeProvider).name == + (widget.estimate?.exchangeProvider ?? widget.exchange.name)) { + groupValue = _id; + } + + return ConditionalParent( + condition: isDesktop, + builder: (child) => MouseRegion( + cursor: SystemMouseCursors.click, + child: child, + ), + child: GestureDetector( + onTap: () { + ref.read(efExchangeProvider.notifier).state = widget.exchange; + ref.read(efExchangeProviderNameProvider.notifier).state = + widget.estimate?.exchangeProvider ?? widget.exchange.name; + }, + child: Container( + color: Colors.transparent, + child: Padding( + padding: + isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 20, + height: 20, + child: Padding( + padding: EdgeInsets.only(top: isDesktop ? 20.0 : 15.0), + child: Radio( + activeColor: Theme.of(context) + .extension()! + .radioButtonIconEnabled, + value: _id, + groupValue: groupValue, + onChanged: (_) { + ref.read(efExchangeProvider.notifier).state = + widget.exchange; + ref + .read(efExchangeProviderNameProvider.notifier) + .state = + widget.estimate?.exchangeProvider ?? + widget.exchange.name; + }, + ), + ), + ), + const SizedBox( + width: 14, + ), + Padding( + padding: const EdgeInsets.only(top: 5.0), + child: SizedBox( + width: isDesktop ? 32 : 24, + height: isDesktop ? 32 : 24, + child: SvgPicture.asset( + Assets.exchange.getIconFor( + exchangeName: widget.exchange.name, + ), + width: isDesktop ? 32 : 24, + height: isDesktop ? 32 : 24, + ), + ), + ), + const SizedBox( + width: 10, + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.estimate?.exchangeProvider ?? + widget.exchange.name, + style: STextStyles.titleBold12(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark2, + ), + ), + widget.loadingString + ? AnimatedText( + stringsToLoopThrough: const [ + "Loading", + "Loading.", + "Loading..", + "Loading...", + ], + style: + STextStyles.itemSubtitle12(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ) + : Text( + widget.rateString, + style: + STextStyles.itemSubtitle12(context).copyWith( + color: widget.rateColor ?? + Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ], + ), + ), + if (widget.kycRating != null) + TrocadorKYCInfoButton( + kycType: TrocadorKYCType.fromString( + widget.kycRating!, + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart index ebaf68066..c8566fff9 100644 --- a/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart +++ b/lib/pages/exchange_view/sub_widgets/exchange_provider_options.dart @@ -1,46 +1,75 @@ -import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; +import 'package:stackwallet/models/exchange/aggregate_currency.dart'; +import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_provider_option.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; -import 'package:stackwallet/services/exchange/exchange.dart'; -import 'package:stackwallet/services/exchange/exchange_response.dart'; -import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; -import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/format.dart'; -import 'package:stackwallet/utilities/logger.dart'; -import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart'; +import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; -import 'package:stackwallet/widgets/animated_text.dart'; -import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; -class ExchangeProviderOptions extends ConsumerWidget { +class ExchangeProviderOptions extends ConsumerStatefulWidget { const ExchangeProviderOptions({ Key? key, - required this.from, - required this.to, - required this.fromAmount, - required this.toAmount, required this.fixedRate, required this.reversed, }) : super(key: key); - final String? from; - final String? to; - final Decimal? fromAmount; - final Decimal? toAmount; final bool fixedRate; final bool reversed; @override - Widget build(BuildContext context, WidgetRef ref) { - final isDesktop = Util.isDesktop; + ConsumerState createState() => + _ExchangeProviderOptionsState(); +} + +class _ExchangeProviderOptionsState + extends ConsumerState { + final isDesktop = Util.isDesktop; + + bool exchangeSupported({ + required String exchangeName, + required AggregateCurrency? sendCurrency, + required AggregateCurrency? receiveCurrency, + }) { + final send = sendCurrency?.forExchange(exchangeName); + if (send == null) return false; + + final rcv = receiveCurrency?.forExchange(exchangeName); + if (rcv == null) return false; + + if (widget.fixedRate) { + return send.supportsFixedRate && rcv.supportsFixedRate; + } else { + return send.supportsEstimatedRate && rcv.supportsEstimatedRate; + } + } + + @override + Widget build(BuildContext context) { + final sendCurrency = + ref.watch(efCurrencyPairProvider.select((value) => value.send)); + final receivingCurrency = + ref.watch(efCurrencyPairProvider.select((value) => value.receive)); + + final showChangeNow = exchangeSupported( + exchangeName: ChangeNowExchange.exchangeName, + sendCurrency: sendCurrency, + receiveCurrency: receivingCurrency, + ); + final showMajesticBank = exchangeSupported( + exchangeName: MajesticBankExchange.exchangeName, + sendCurrency: sendCurrency, + receiveCurrency: receivingCurrency, + ); + final showTrocador = exchangeSupported( + exchangeName: TrocadorExchange.exchangeName, + sendCurrency: sendCurrency, + receiveCurrency: receivingCurrency, + ); + return RoundedWhiteContainer( padding: isDesktop ? const EdgeInsets.all(0) : const EdgeInsets.all(12), borderColor: isDesktop @@ -48,409 +77,44 @@ class ExchangeProviderOptions extends ConsumerWidget { : null, child: Column( children: [ - ConditionalParent( - condition: isDesktop, - builder: (child) => MouseRegion( - cursor: SystemMouseCursors.click, - child: child, + if (showChangeNow) + ExchangeOption( + exchange: ChangeNowExchange.instance, + fixedRate: widget.fixedRate, + reversed: widget.reversed, ), - child: GestureDetector( - onTap: () { - if (ref.read(currentExchangeNameStateProvider.state).state != - ChangeNowExchange.exchangeName) { - ref.read(currentExchangeNameStateProvider.state).state = - ChangeNowExchange.exchangeName; - ref.read(exchangeFormStateProvider).exchange = - Exchange.fromName(ref - .read(currentExchangeNameStateProvider.state) - .state); - } - }, - child: Container( - color: Colors.transparent, - child: Padding( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 20, - height: 20, - child: Radio( - activeColor: Theme.of(context) - .extension()! - .radioButtonIconEnabled, - value: ChangeNowExchange.exchangeName, - groupValue: ref - .watch(currentExchangeNameStateProvider.state) - .state, - onChanged: (value) { - if (value is String) { - ref - .read(currentExchangeNameStateProvider.state) - .state = value; - ref.read(exchangeFormStateProvider).exchange = - Exchange.fromName(ref - .read(currentExchangeNameStateProvider - .state) - .state); - } - }, - ), - ), - const SizedBox( - width: 14, - ), - SvgPicture.asset( - Assets.exchange.changeNow, - width: isDesktop ? 32 : 24, - height: isDesktop ? 32 : 24, - ), - const SizedBox( - width: 10, - ), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - ChangeNowExchange.exchangeName, - style: STextStyles.titleBold12(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark2, - ), - ), - if (from != null && - to != null && - toAmount != null && - toAmount! > Decimal.zero && - fromAmount != null && - fromAmount! > Decimal.zero) - FutureBuilder( - future: ChangeNowExchange().getEstimate( - from!, - to!, - reversed ? toAmount! : fromAmount!, - fixedRate, - reversed, - ), - builder: (context, - AsyncSnapshot> - snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - final estimate = snapshot.data?.value; - if (estimate != null) { - Decimal rate; - if (estimate.reversed) { - rate = (toAmount! / - estimate.estimatedAmount) - .toDecimal( - scaleOnInfinitePrecision: 12); - } else { - rate = (estimate.estimatedAmount / - fromAmount!) - .toDecimal( - scaleOnInfinitePrecision: 12); - } - Coin coin; - try { - coin = - coinFromTickerCaseInsensitive(to!); - } catch (_) { - coin = Coin.bitcoin; - } - - return Text( - "1 ${from!.toUpperCase()} ~ ${Format.localizedStringAsFixed( - value: rate, - locale: ref.watch( - localeServiceChangeNotifierProvider - .select( - (value) => value.locale), - ), - decimalPlaces: - Constants.decimalPlacesForCoin( - coin), - )} ${to!.toUpperCase()}", - style: - STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } else { - Logging.instance.log( - "$runtimeType failed to fetch rate for ChangeNOW: ${snapshot.data}", - level: LogLevel.Warning, - ); - return Text( - "Failed to fetch rate", - style: - STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } - } else { - return AnimatedText( - stringsToLoopThrough: const [ - "Loading", - "Loading.", - "Loading..", - "Loading...", - ], - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } - }, - ), - if (!(from != null && - to != null && - toAmount != null && - toAmount! > Decimal.zero && - fromAmount != null && - fromAmount! > Decimal.zero)) - Text( - "n/a", - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), - ], - ), - ), - ], + if (showChangeNow && showMajesticBank) + isDesktop + ? Container( + height: 1, + color: + Theme.of(context).extension()!.background, + ) + : const SizedBox( + height: 16, ), - ), - ), + if (showMajesticBank) + ExchangeOption( + exchange: MajesticBankExchange.instance, + fixedRate: widget.fixedRate, + reversed: widget.reversed, ), - ), - if (isDesktop) - Container( - height: 1, - color: Theme.of(context).extension()!.background, - ), - if (!isDesktop) - const SizedBox( - height: 16, - ), - ConditionalParent( - condition: isDesktop, - builder: (child) => MouseRegion( - cursor: SystemMouseCursors.click, - child: child, - ), - child: GestureDetector( - onTap: () { - if (ref.read(currentExchangeNameStateProvider.state).state != - SimpleSwapExchange.exchangeName) { - ref.read(currentExchangeNameStateProvider.state).state = - SimpleSwapExchange.exchangeName; - ref.read(exchangeFormStateProvider).exchange = - Exchange.fromName(ref - .read(currentExchangeNameStateProvider.state) - .state); - } - }, - child: Container( - color: Colors.transparent, - child: Padding( - padding: isDesktop - ? const EdgeInsets.all(16) - : const EdgeInsets.all(0), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 20, - height: 20, - child: Radio( - activeColor: Theme.of(context) - .extension()! - .radioButtonIconEnabled, - value: SimpleSwapExchange.exchangeName, - groupValue: ref - .watch(currentExchangeNameStateProvider.state) - .state, - onChanged: (value) { - if (value is String) { - ref - .read(currentExchangeNameStateProvider.state) - .state = value; - ref.read(exchangeFormStateProvider).exchange = - Exchange.fromName(ref - .read(currentExchangeNameStateProvider - .state) - .state); - } - }, - ), - ), - const SizedBox( - width: 14, - ), - SvgPicture.asset( - Assets.exchange.simpleSwap, - width: isDesktop ? 32 : 24, - height: isDesktop ? 32 : 24, - ), - const SizedBox( - width: 10, - ), - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - SimpleSwapExchange.exchangeName, - style: STextStyles.titleBold12(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark2, - ), - ), - if (from != null && - to != null && - toAmount != null && - toAmount! > Decimal.zero && - fromAmount != null && - fromAmount! > Decimal.zero) - FutureBuilder( - future: SimpleSwapExchange().getEstimate( - from!, - to!, - // reversed ? toAmount! : fromAmount!, - fromAmount!, - fixedRate, - // reversed, - false, - ), - builder: (context, - AsyncSnapshot> - snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - final estimate = snapshot.data?.value; - if (estimate != null) { - Decimal rate = (estimate.estimatedAmount / - fromAmount!) - .toDecimal( - scaleOnInfinitePrecision: 12); - - Coin coin; - try { - coin = - coinFromTickerCaseInsensitive(to!); - } catch (_) { - coin = Coin.bitcoin; - } - return Text( - "1 ${from!.toUpperCase()} ~ ${Format.localizedStringAsFixed( - value: rate, - locale: ref.watch( - localeServiceChangeNotifierProvider - .select( - (value) => value.locale), - ), - decimalPlaces: - Constants.decimalPlacesForCoin( - coin), - )} ${to!.toUpperCase()}", - style: - STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } else { - Logging.instance.log( - "$runtimeType failed to fetch rate for SimpleSwap: ${snapshot.data}", - level: LogLevel.Warning, - ); - return Text( - "Failed to fetch rate", - style: - STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } - } else { - return AnimatedText( - stringsToLoopThrough: const [ - "Loading", - "Loading.", - "Loading..", - "Loading...", - ], - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } - }, - ), - // if (!(from != null && - // to != null && - // (reversed - // ? toAmount != null && toAmount! > Decimal.zero - // : fromAmount != null && - // fromAmount! > Decimal.zero))) - if (!(from != null && - to != null && - toAmount != null && - toAmount! > Decimal.zero && - fromAmount != null && - fromAmount! > Decimal.zero)) - Text( - "n/a", - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), - ], - ), - ), - ], + if ((showChangeNow || showMajesticBank) && showTrocador) + isDesktop + ? Container( + height: 1, + color: + Theme.of(context).extension()!.background, + ) + : const SizedBox( + height: 16, ), - ), - ), + if (showTrocador) + ExchangeOption( + fixedRate: widget.fixedRate, + reversed: widget.reversed, + exchange: TrocadorExchange.instance, ), - ), ], ), ); diff --git a/lib/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart b/lib/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart deleted file mode 100644 index b56d2fbff..000000000 --- a/lib/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart +++ /dev/null @@ -1,210 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; - -enum ExchangeRateType { estimated, fixed } - -class ExchangeRateSheet extends ConsumerWidget { - const ExchangeRateSheet({ - Key? key, - }) : super(key: key); - - @override - Widget build(BuildContext context, WidgetRef ref) { - return Container( - decoration: BoxDecoration( - color: Theme.of(context).extension()!.popupBG, - borderRadius: const BorderRadius.vertical( - top: Radius.circular(20), - ), - ), - child: Padding( - padding: const EdgeInsets.only( - left: 24, - right: 24, - top: 10, - bottom: 0, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Center( - child: Container( - decoration: BoxDecoration( - color: - Theme.of(context).extension()!.textSubtitle4, - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - width: 60, - height: 4, - ), - ), - const SizedBox( - height: 36, - ), - Text( - "Exchange rate", - style: STextStyles.pageTitleH2(context), - textAlign: TextAlign.left, - ), - const SizedBox( - height: 16, - ), - GestureDetector( - onTap: () { - final state = - ref.read(prefsChangeNotifierProvider).exchangeRateType; - if (state != ExchangeRateType.estimated) { - ref.read(prefsChangeNotifierProvider).exchangeRateType = - ExchangeRateType.estimated; - } - Navigator.of(context).pop(ExchangeRateType.estimated); - }, - child: Container( - color: Colors.transparent, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - children: [ - SizedBox( - width: 20, - height: 20, - child: Radio( - activeColor: Theme.of(context) - .extension()! - .radioButtonIconEnabled, - value: ExchangeRateType.estimated, - groupValue: ref.watch(prefsChangeNotifierProvider - .select((value) => value.exchangeRateType)), - onChanged: (x) { - //todo: check if print needed - // debugPrint(x.toString()); - ref - .read(prefsChangeNotifierProvider) - .exchangeRateType = - ExchangeRateType.estimated; - Navigator.of(context) - .pop(ExchangeRateType.estimated); - }, - ), - ) - ], - ), - const SizedBox( - width: 12, - ), - Flexible( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Estimated rate", - style: STextStyles.titleBold12(context), - textAlign: TextAlign.left, - ), - const SizedBox( - height: 2, - ), - Text( - "ChangeNOW will pick the best rate for you during the moment of the exchange.", - style: STextStyles.itemSubtitle(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - textAlign: TextAlign.left, - ), - ], - ), - ) - ], - ), - ), - ), - const SizedBox( - height: 16, - ), - GestureDetector( - onTap: () { - final state = - ref.read(prefsChangeNotifierProvider).exchangeRateType; - if (state != ExchangeRateType.fixed) { - ref.read(prefsChangeNotifierProvider).exchangeRateType = - ExchangeRateType.fixed; - } - Navigator.of(context).pop(ExchangeRateType.fixed); - }, - child: Container( - color: Colors.transparent, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - children: [ - SizedBox( - width: 20, - height: 20, - child: Radio( - activeColor: Theme.of(context) - .extension()! - .radioButtonIconEnabled, - value: ExchangeRateType.fixed, - groupValue: ref.watch(prefsChangeNotifierProvider - .select((value) => value.exchangeRateType)), - onChanged: (x) { - ref - .read(prefsChangeNotifierProvider) - .exchangeRateType = ExchangeRateType.fixed; - Navigator.of(context).pop(ExchangeRateType.fixed); - }, - ), - ), - ], - ), - const SizedBox( - width: 12, - ), - Flexible( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Fixed rate", - style: STextStyles.titleBold12(context), - textAlign: TextAlign.left, - ), - const SizedBox( - height: 2, - ), - Text( - "You will get the exact exchange amount displayed - ChangeNOW takes all the rate risks.", - style: STextStyles.itemSubtitle(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - textAlign: TextAlign.left, - ) - ], - ), - ), - ], - ), - ), - ), - const SizedBox( - height: 24, - ), - ], - ), - ), - ); - } -} diff --git a/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart b/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart index 284d4eddd..98e09211c 100644 --- a/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart +++ b/lib/pages/exchange_view/sub_widgets/rate_type_toggle.dart @@ -1,14 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/toggle.dart'; -import '../../../utilities/constants.dart'; - class RateTypeToggle extends ConsumerWidget { const RateTypeToggle({ Key? key, @@ -22,25 +21,25 @@ class RateTypeToggle extends ConsumerWidget { debugPrint("BUILD: $runtimeType"); final isDesktop = Util.isDesktop; - final estimated = ref.watch(prefsChangeNotifierProvider - .select((value) => value.exchangeRateType)) == - ExchangeRateType.estimated; - return Toggle( onValueChanged: (value) { - if (!estimated) { - ref.read(prefsChangeNotifierProvider).exchangeRateType = - ExchangeRateType.estimated; - onChanged?.call(ExchangeRateType.estimated); - } else { + if (value) { onChanged?.call(ExchangeRateType.fixed); + } else { + onChanged?.call(ExchangeRateType.estimated); } }, - isOn: !estimated, - onColor: Theme.of(context).extension()!.textFieldDefaultBG, + isOn: ref.watch(efRateTypeProvider) == ExchangeRateType.fixed, + onColor: isDesktop + ? Theme.of(context) + .extension()! + .rateTypeToggleDesktopColorOn + : Theme.of(context).extension()!.rateTypeToggleColorOn, offColor: isDesktop - ? Theme.of(context).extension()!.buttonBackSecondary - : Theme.of(context).extension()!.popupBG, + ? Theme.of(context) + .extension()! + .rateTypeToggleDesktopColorOff + : Theme.of(context).extension()!.rateTypeToggleColorOff, decoration: BoxDecoration( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -51,178 +50,5 @@ class RateTypeToggle extends ConsumerWidget { offIcon: Assets.svg.lock, offText: "Fixed rate", ); - // - // return RoundedContainer( - // padding: const EdgeInsets.all(0), - // color: isDesktop - // ? Theme.of(context).extension()!.buttonBackSecondary - // : Theme.of(context).extension()!.popupBG, - // child: Row( - // children: [ - // Expanded( - // child: ConditionalParent( - // condition: isDesktop, - // builder: (child) => MouseRegion( - // cursor: estimated - // ? SystemMouseCursors.basic - // : SystemMouseCursors.click, - // child: child, - // ), - // child: GestureDetector( - // onTap: () { - // if (!estimated) { - // ref.read(prefsChangeNotifierProvider).exchangeRateType = - // ExchangeRateType.estimated; - // onChanged?.call(ExchangeRateType.estimated); - // } - // }, - // child: RoundedContainer( - // padding: isDesktop - // ? const EdgeInsets.all(17) - // : const EdgeInsets.all(12), - // color: estimated - // ? Theme.of(context) - // .extension()! - // .textFieldDefaultBG - // : Colors.transparent, - // child: Row( - // mainAxisAlignment: MainAxisAlignment.center, - // children: [ - // SvgPicture.asset( - // Assets.svg.lockOpen, - // width: 12, - // height: 14, - // color: isDesktop - // ? estimated - // ? Theme.of(context) - // .extension()! - // .accentColorBlue - // : Theme.of(context) - // .extension()! - // .buttonTextSecondary - // : estimated - // ? Theme.of(context) - // .extension()! - // .textDark - // : Theme.of(context) - // .extension()! - // .textSubtitle1, - // ), - // const SizedBox( - // width: 5, - // ), - // Text( - // "Estimate rate", - // style: isDesktop - // ? STextStyles.desktopTextExtraExtraSmall(context) - // .copyWith( - // color: estimated - // ? Theme.of(context) - // .extension()! - // .accentColorBlue - // : Theme.of(context) - // .extension()! - // .buttonTextSecondary, - // ) - // : STextStyles.smallMed12(context).copyWith( - // color: estimated - // ? Theme.of(context) - // .extension()! - // .textDark - // : Theme.of(context) - // .extension()! - // .textSubtitle1, - // ), - // ), - // ], - // ), - // ), - // ), - // ), - // ), - // Expanded( - // child: ConditionalParent( - // condition: isDesktop, - // builder: (child) => MouseRegion( - // cursor: !estimated - // ? SystemMouseCursors.basic - // : SystemMouseCursors.click, - // child: child, - // ), - // child: GestureDetector( - // onTap: () { - // if (estimated) { - // ref.read(prefsChangeNotifierProvider).exchangeRateType = - // ExchangeRateType.fixed; - // onChanged?.call(ExchangeRateType.fixed); - // } - // }, - // child: RoundedContainer( - // padding: isDesktop - // ? const EdgeInsets.all(17) - // : const EdgeInsets.all(12), - // color: !estimated - // ? Theme.of(context) - // .extension()! - // .textFieldDefaultBG - // : Colors.transparent, - // child: Row( - // mainAxisAlignment: MainAxisAlignment.center, - // children: [ - // SvgPicture.asset( - // Assets.svg.lock, - // width: 12, - // height: 14, - // color: isDesktop - // ? !estimated - // ? Theme.of(context) - // .extension()! - // .accentColorBlue - // : Theme.of(context) - // .extension()! - // .buttonTextSecondary - // : !estimated - // ? Theme.of(context) - // .extension()! - // .textDark - // : Theme.of(context) - // .extension()! - // .textSubtitle1, - // ), - // const SizedBox( - // width: 5, - // ), - // Text( - // "Fixed rate", - // style: isDesktop - // ? STextStyles.desktopTextExtraExtraSmall(context) - // .copyWith( - // color: !estimated - // ? Theme.of(context) - // .extension()! - // .accentColorBlue - // : Theme.of(context) - // .extension()! - // .buttonTextSecondary, - // ) - // : STextStyles.smallMed12(context).copyWith( - // color: !estimated - // ? Theme.of(context) - // .extension()! - // .textDark - // : Theme.of(context) - // .extension()! - // .textSubtitle1, - // ), - // ), - // ], - // ), - // ), - // ), - // ), - // ), - // ], - // ), - // ); } } diff --git a/lib/pages/exchange_view/sub_widgets/step_indicator.dart b/lib/pages/exchange_view/sub_widgets/step_indicator.dart index 96e1d4a4c..8eff8c52c 100644 --- a/lib/pages/exchange_view/sub_widgets/step_indicator.dart +++ b/lib/pages/exchange_view/sub_widgets/step_indicator.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; enum StepIndicatorStatus { current, completed, incomplete } diff --git a/lib/pages/exchange_view/sub_widgets/step_row.dart b/lib/pages/exchange_view/sub_widgets/step_row.dart index 398ac1c16..9c2ee29c9 100644 --- a/lib/pages/exchange_view/sub_widgets/step_row.dart +++ b/lib/pages/exchange_view/sub_widgets/step_row.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/step_indicator.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; class StepRow extends StatelessWidget { const StepRow({ diff --git a/lib/pages/exchange_view/trade_details_view.dart b/lib/pages/exchange_view/trade_details_view.dart index 479600569..89501a74c 100644 --- a/lib/pages/exchange_view/trade_details_view.dart +++ b/lib/pages/exchange_view/trade_details_view.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; @@ -7,7 +8,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'; -import 'package:stackwallet/models/paymint/transactions_model.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/exchange_view/edit_trade_note_view.dart'; import 'package:stackwallet/pages/exchange_view/send_from_view.dart'; @@ -18,15 +20,18 @@ import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; import 'package:stackwallet/services/exchange/exchange.dart'; +import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart'; import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; +import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; @@ -108,7 +113,7 @@ class _TradeDetailsViewState extends ConsumerState { super.initState(); } - String _fetchIconAssetForStatus(String statusString) { + String _fetchIconAssetForStatus(String statusString, IThemeAssets assets) { ChangeNowTransactionStatus? status; try { if (statusString.toLowerCase().startsWith("waiting")) { @@ -116,7 +121,17 @@ class _TradeDetailsViewState extends ConsumerState { } status = changeNowTransactionStatusFromStringIgnoreCase(statusString); } on ArgumentError catch (_) { - status = ChangeNowTransactionStatus.Failed; + switch (statusString.toLowerCase()) { + case "funds confirming": + case "processing payment": + return assets.txExchangePending; + + case "completed": + return assets.txExchange; + + default: + status = ChangeNowTransactionStatus.Failed; + } } switch (status) { @@ -127,11 +142,11 @@ class _TradeDetailsViewState extends ConsumerState { case ChangeNowTransactionStatus.Sending: case ChangeNowTransactionStatus.Refunded: case ChangeNowTransactionStatus.Verifying: - return Assets.svg.txExchangePending(context); + return assets.txExchangePending; case ChangeNowTransactionStatus.Finished: - return Assets.svg.txExchange(context); + return assets.txExchange; case ChangeNowTransactionStatus.Failed: - return Assets.svg.txExchangeFailed(context); + return assets.txExchangeFailed; } } @@ -158,6 +173,8 @@ class _TradeDetailsViewState extends ConsumerState { trade.status == "failed"); //todo: check if print needed + // debugPrint("walletId: $walletId"); + // debugPrint("transactionIfSentFromStack: $transactionIfSentFromStack"); // debugPrint("sentFromStack: $sentFromStack"); // debugPrint("hasTx: $hasTx"); // debugPrint("trade: ${trade.toString()}"); @@ -244,11 +261,11 @@ class _TradeDetailsViewState extends ConsumerState { label: "Send from Stack", buttonHeight: ButtonHeight.l, onPressed: () { - final amount = sendAmount; - final address = trade.payInAddress; - final coin = coinFromTickerCaseInsensitive(trade.payInCurrency); + final amount = + sendAmount.toAmount(fractionDigits: coin.decimals); + final address = trade.payInAddress; Navigator.of(context).pushNamed( SendFromView.routeName, @@ -301,8 +318,13 @@ class _TradeDetailsViewState extends ConsumerState { if (isDesktop) Row( children: [ - SvgPicture.asset( - _fetchIconAssetForStatus(trade.status), + SvgPicture.file( + File( + _fetchIconAssetForStatus( + trade.status, + ref.watch(themeAssetsProvider), + ), + ), width: 32, height: 32, ), @@ -310,7 +332,7 @@ class _TradeDetailsViewState extends ConsumerState { width: 16, ), SelectableText( - "Exchange", + "Swap service", style: STextStyles.desktopTextMedium(context), ), ], @@ -327,13 +349,32 @@ class _TradeDetailsViewState extends ConsumerState { const SizedBox( height: 4, ), - SelectableText( - "-${Format.localizedStringAsFixed(value: sendAmount, locale: ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), - ), decimalPlaces: trade.payInCurrency.toLowerCase() == "xmr" ? 12 : 8)} ${trade.payInCurrency.toUpperCase()}", - style: STextStyles.itemSubtitle(context), - ), + Builder(builder: (context) { + String text; + try { + final coin = coinFromTickerCaseInsensitive( + trade.payInCurrency); + final amount = sendAmount.toAmount( + fractionDigits: coin.decimals); + text = amount.localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), + ), + ); + } catch (_) { + text = sendAmount.toStringAsFixed( + trade.payInCurrency.toLowerCase() == "xmr" + ? 12 + : 8); + } + + return SelectableText( + "-$text ${trade.payInCurrency.toUpperCase()}", + style: STextStyles.itemSubtitle(context), + ); + }), ], ), if (!isDesktop) @@ -344,8 +385,13 @@ class _TradeDetailsViewState extends ConsumerState { borderRadius: BorderRadius.circular(32), ), child: Center( - child: SvgPicture.asset( - _fetchIconAssetForStatus(trade.status), + child: SvgPicture.file( + File( + _fetchIconAssetForStatus( + trade.status, + ref.watch(themeAssetsProvider), + ), + ), width: 32, height: 32, ), @@ -365,12 +411,46 @@ class _TradeDetailsViewState extends ConsumerState { padding: isDesktop ? const EdgeInsets.all(16) : const EdgeInsets.all(12), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "Status", - style: STextStyles.itemSubtitle(context), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Status", + style: STextStyles.itemSubtitle(context), + ), + if (trade.exchangeName == + MajesticBankExchange.exchangeName && + trade.status == "Completed") + Row( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + onTap: () { + showDialog( + context: context, + builder: (context) => const StackOkDialog( + title: "Trade Info", + message: + "Majestic Bank does not store order data indefinitely", + ), + ); + }, + child: SvgPicture.asset( + Assets.svg.circleInfo, + height: 20, + width: 20, + color: Theme.of(context) + .extension()! + .infoItemIcons, + ), + ), + ], + ) + ], ), const SizedBox( height: 4, @@ -383,8 +463,6 @@ class _TradeDetailsViewState extends ConsumerState { .colorForStatus(trade.status), ), ), - // ), - // ), ], ), ), @@ -517,7 +595,7 @@ class _TradeDetailsViewState extends ConsumerState { const SizedBox( height: 10, ), - BlueTextButton( + CustomTextButton( text: "View transaction", onTap: () { final Coin coin = @@ -624,11 +702,15 @@ class _TradeDetailsViewState extends ConsumerState { text: address, ), ); - unawaited(showFloatingFlushBar( - type: FlushBarType.info, - message: "Copied to clipboard", - context: context, - )); + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + context: context, + ), + ); + } }, child: Row( children: [ @@ -714,7 +796,7 @@ class _TradeDetailsViewState extends ConsumerState { }, style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor( + .getSecondaryEnabledButtonStyle( context), child: Text( "Cancel", @@ -1004,7 +1086,7 @@ class _TradeDetailsViewState extends ConsumerState { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Exchange", + "Swap service", style: STextStyles.itemSubtitle(context), ), if (isDesktop) @@ -1079,11 +1161,15 @@ class _TradeDetailsViewState extends ConsumerState { onTap: () async { final data = ClipboardData(text: trade.tradeId); await clipboard.setData(data); - unawaited(showFloatingFlushBar( - type: FlushBarType.info, - message: "Copied to clipboard", - context: context, - )); + if (mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + context: context, + ), + ); + } }, child: SvgPicture.asset( Assets.svg.copy, @@ -1129,6 +1215,17 @@ class _TradeDetailsViewState extends ConsumerState { url = "https://simpleswap.io/exchange?id=${trade.tradeId}"; break; + case MajesticBankExchange.exchangeName: + url = + "https://majesticbank.sc/track?trx=${trade.tradeId}"; + break; + + default: + if (trade.exchangeName + .startsWith(TrocadorExchange.exchangeName)) { + url = + "https://trocador.app/en/checkout/${trade.tradeId}"; + } } return ConditionalParent( condition: isDesktop, diff --git a/lib/pages/exchange_view/wallet_initiated_exchange_view.dart b/lib/pages/exchange_view/wallet_initiated_exchange_view.dart index 915fb33ba..f52cb0699 100644 --- a/lib/pages/exchange_view/wallet_initiated_exchange_view.dart +++ b/lib/pages/exchange_view/wallet_initiated_exchange_view.dart @@ -2,25 +2,33 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; import 'package:stackwallet/pages/exchange_view/exchange_form.dart'; import 'package:stackwallet/pages/exchange_view/sub_widgets/step_row.dart'; +import 'package:stackwallet/providers/exchange/exchange_form_state_provider.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/custom_loading_overlay.dart'; class WalletInitiatedExchangeView extends ConsumerStatefulWidget { const WalletInitiatedExchangeView({ Key? key, required this.walletId, required this.coin, + this.contract, }) : super(key: key); static const String routeName = "/walletInitiatedExchange"; final String walletId; final Coin coin; + final EthContract? contract; @override ConsumerState createState() => @@ -32,10 +40,47 @@ class _WalletInitiatedExchangeViewState late final String walletId; late final Coin coin; + bool _initialCachePopulationUnderway = false; + @override void initState() { walletId = widget.walletId; coin = widget.coin; + + if (!ref.read(prefsChangeNotifierProvider).externalCalls) { + if (ExchangeDataLoadingService.currentCacheVersion < + ExchangeDataLoadingService.cacheVersion) { + _initialCachePopulationUnderway = true; + ExchangeDataLoadingService.instance.onLoadingComplete = () { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty( + ref.read(efCurrencyPairProvider), + ref.read(efRateTypeProvider), + ); + setState(() { + _initialCachePopulationUnderway = false; + }); + }); + }; + } + ExchangeDataLoadingService.instance.loadAll(); + } else if (ExchangeDataLoadingService.instance.isLoading && + ExchangeDataLoadingService.currentCacheVersion < + ExchangeDataLoadingService.cacheVersion) { + _initialCachePopulationUnderway = true; + ExchangeDataLoadingService.instance.onLoadingComplete = () { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty( + ref.read(efCurrencyPairProvider), + ref.read(efRateTypeProvider), + ); + setState(() { + _initialCachePopulationUnderway = false; + }); + }); + }; + } + super.initState(); } @@ -48,76 +93,99 @@ class _WalletInitiatedExchangeViewState Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - return Background( - child: Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed(const Duration(milliseconds: 75)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, + return ConditionalParent( + condition: _initialCachePopulationUnderway, + builder: (child) { + return Stack( + children: [ + child, + Material( + color: Theme.of(context) + .extension()! + .overlay + .withOpacity(0.6), + child: const CustomLoadingOverlay( + message: "Updating exchange data", + subMessage: "This could take a few minutes", + eventBus: null, + ), + ) + ], + ); + }, + child: Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Swap", + style: STextStyles.navBarTitle(context), + ), ), - title: Text( - "Exchange", - style: STextStyles.navBarTitle(context), - ), - ), - body: LayoutBuilder( - builder: (context, constraints) { - final width = MediaQuery.of(context).size.width - 32; - return Padding( - padding: const EdgeInsets.all(12), - child: SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight - 24, - ), - child: IntrinsicHeight( - child: Padding( - padding: const EdgeInsets.all(4), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - StepRow( - count: 4, - current: 0, - width: width, - ), - const SizedBox( - height: 14, - ), - Text( - "Exchange amount", - style: STextStyles.pageTitleH1(context), - ), - const SizedBox( - height: 8, - ), - Text( - "Network fees and other exchange charges are included in the rate.", - style: STextStyles.itemSubtitle(context), - ), - const SizedBox( - height: 24, - ), - ExchangeForm( - walletId: walletId, - coin: coin, - ), - ], + body: LayoutBuilder( + builder: (context, constraints) { + final width = MediaQuery.of(context).size.width - 32; + return Padding( + padding: const EdgeInsets.all(12), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + StepRow( + count: 4, + current: 0, + width: width, + ), + const SizedBox( + height: 14, + ), + Text( + "Exchange amount", + style: STextStyles.pageTitleH1(context), + ), + const SizedBox( + height: 8, + ), + Text( + "Network fees and other exchange charges are included in the rate.", + style: STextStyles.itemSubtitle(context), + ), + const SizedBox( + height: 24, + ), + ExchangeForm( + walletId: walletId, + coin: coin, + contract: widget.contract, + ), + ], + ), ), ), ), ), - ), - ); - }, + ); + }, + ), ), ), ); diff --git a/lib/pages/generic/single_field_edit_view.dart b/lib/pages/generic/single_field_edit_view.dart new file mode 100644 index 000000000..17687a271 --- /dev/null +++ b/lib/pages/generic/single_field_edit_view.dart @@ -0,0 +1,230 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_native_splash/cli_commands.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; + +class SingleFieldEditView extends StatefulWidget { + const SingleFieldEditView({ + Key? key, + required this.initialValue, + required this.label, + }) : super(key: key); + + static const String routeName = "/singleFieldEdit"; + + final String initialValue; + final String label; + + @override + State createState() => _SingleFieldEditViewState(); +} + +class _SingleFieldEditViewState extends State { + late final TextEditingController _textController; + final _textFocusNode = FocusNode(); + + late final bool isDesktop; + + @override + void initState() { + isDesktop = Util.isDesktop; + _textController = TextEditingController()..text = widget.initialValue; + super.initState(); + } + + @override + void dispose() { + _textController.dispose(); + _textFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ConditionalParent( + condition: !isDesktop, + builder: (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Edit ${widget.label}", + style: STextStyles.navBarTitle(context), + ), + ), + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: child, + ), + ), + ); + }, + ), + ), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (!isDesktop) + const SizedBox( + height: 10, + ), + if (isDesktop) + Padding( + padding: const EdgeInsets.only( + left: 32, + bottom: 12, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Edit ${widget.label}", + style: STextStyles.desktopH3(context), + ), + const DesktopDialogCloseButton(), + ], + ), + ), + Padding( + padding: isDesktop + ? const EdgeInsets.symmetric( + horizontal: 32, + ) + : const EdgeInsets.all(0), + child: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: _textController, + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + focusNode: _textFocusNode, + decoration: standardInputDecoration( + widget.label.capitalize(), + _textFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + contentPadding: isDesktop + ? const EdgeInsets.only( + left: 16, + top: 11, + bottom: 12, + right: 5, + ) + : null, + suffixIcon: _textController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _textController.text = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), + ), + // if (!isDesktop) + const Spacer(), + + ConditionalParent( + condition: isDesktop, + builder: (child) => Padding( + padding: const EdgeInsets.all(32), + child: Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + buttonHeight: ButtonHeight.l, + onPressed: () { + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: child, + ), + ], + ), + ), + child: PrimaryButton( + label: "Save", + buttonHeight: isDesktop ? ButtonHeight.l : null, + onPressed: () { + if (mounted) { + Navigator.of(context).pop(_textController.text); + } + }, + ), + ), + if (!isDesktop) + const SizedBox( + height: 16, + ), + ], + ), + ); + } +} diff --git a/lib/pages/home_view/home_view.dart b/lib/pages/home_view/home_view.dart index 7d394d7ce..c9cf5f478 100644 --- a/lib/pages/home_view/home_view.dart +++ b/lib/pages/home_view/home_view.dart @@ -1,9 +1,10 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/pages/exchange_view/exchange_loading_overlay.dart'; +import 'package:stackwallet/pages/buy_view/buy_view.dart'; import 'package:stackwallet/pages/exchange_view/exchange_view.dart'; import 'package:stackwallet/pages/home_view/sub_widgets/home_view_button_bar.dart'; import 'package:stackwallet/pages/notification_views/notifications_view.dart'; @@ -11,15 +12,14 @@ import 'package:stackwallet/pages/settings_views/global_settings_view/global_set import 'package:stackwallet/pages/settings_views/global_settings_view/hidden_settings.dart'; import 'package:stackwallet/pages/wallets_view/wallets_view.dart'; import 'package:stackwallet/providers/global/notifications_provider.dart'; -import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/providers/ui/home_view_index_provider.dart'; import 'package:stackwallet/providers/ui/unread_notifications_provider.dart'; -import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/animated_widgets/rotate_icon.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; @@ -37,6 +37,7 @@ class _HomeViewState extends ConsumerState { final GlobalKey _key = GlobalKey(); late final PageController _pageController; + late final RotateIconController _rotateIconController; late final List _children; @@ -44,11 +45,11 @@ class _HomeViewState extends ConsumerState { bool _exitEnabled = false; - final _exchangeDataLoadingService = ExchangeDataLoadingService(); + // final _buyDataLoadingService = BuyDataLoadingService(); Future _onWillPop() async { // go to home view when tapping back on the main exchange view - if (ref.read(homeViewPageIndexStateProvider.state).state == 1) { + if (ref.read(homeViewPageIndexStateProvider.state).state != 0) { ref.read(homeViewPageIndexStateProvider.state).state = 0; return false; } @@ -82,31 +83,34 @@ class _HomeViewState extends ConsumerState { return _exitEnabled; } - void _loadCNData() { - // unawaited future - if (ref.read(prefsChangeNotifierProvider).externalCalls) { - _exchangeDataLoadingService.loadAll(ref); - } else { - Logging.instance.log("User does not want to use external calls", - level: LogLevel.Info); - } + // void _loadSimplexData() { + // // unawaited future + // if (ref.read(prefsChangeNotifierProvider).externalCalls) { + // _buyDataLoadingService.loadAll(ref); + // } else { + // Logging.instance.log("User does not want to use external calls", + // level: LogLevel.Info); + // } + // } + + bool _lock = false; + + Future _animateToPage(int index) async { + await _pageController.animateToPage( + index, + duration: const Duration(milliseconds: 300), + curve: Curves.decelerate, + ); } @override void initState() { _pageController = PageController(); + _rotateIconController = RotateIconController(); _children = [ const WalletsView(), - if (Constants.enableExchange) - Stack( - children: [ - const ExchangeView(), - ExchangeLoadingOverlayView( - unawaitedLoad: _loadCNData, - ), - ], - ), - // const BuyView(), + if (Constants.enableExchange) const ExchangeView(), + if (Constants.enableExchange) const BuyView(), ]; ref.read(notificationsProvider).startCheckingWatchedNotifications(); @@ -117,6 +121,9 @@ class _HomeViewState extends ConsumerState { @override dispose() { _pageController.dispose(); + _rotateIconController.forward = null; + _rotateIconController.reverse = null; + _rotateIconController.reset = null; super.dispose(); } @@ -124,6 +131,8 @@ class _HomeViewState extends ConsumerState { int _hiddenCount = 0; void _hiddenOptions() { + _rotateIconController.reset?.call(); + _rotateIconController.forward?.call(); if (_hiddenCount == 5) { Navigator.of(context).pushNamed(HiddenSettings.routeName); } @@ -154,10 +163,21 @@ class _HomeViewState extends ConsumerState { children: [ GestureDetector( onTap: _hiddenOptions, - child: SvgPicture.asset( - Assets.svg.stackIcon(context), - width: 24, - height: 24, + child: RotateIcon( + icon: SvgPicture.file( + File( + ref.watch( + themeProvider.select( + (value) => value.assets.stackIcon, + ), + ), + ), + width: 24, + height: 24, + ), + curve: Curves.easeInOutCubic, + rotationPercent: 1.0, + controller: _rotateIconController, ), ), const SizedBox( @@ -179,26 +199,44 @@ class _HomeViewState extends ConsumerState { child: AspectRatio( aspectRatio: 1, child: AppBarIconButton( + semanticsLabel: + "Notifications Button. Takes To Notifications Page.", key: const Key("walletsViewAlertsButton"), size: 36, shadows: const [], color: Theme.of(context) .extension()! .backgroundAppBar, - icon: SvgPicture.asset( - ref.watch(notificationsProvider - .select((value) => value.hasUnreadNotifications)) - ? Assets.svg.bellNew(context) - : Assets.svg.bell, - width: 20, - height: 20, - color: ref.watch(notificationsProvider - .select((value) => value.hasUnreadNotifications)) - ? null - : Theme.of(context) - .extension()! - .topNavIconPrimary, - ), + icon: ref.watch(notificationsProvider + .select((value) => value.hasUnreadNotifications)) + ? SvgPicture.file( + File( + ref.watch( + themeProvider.select( + (value) => value.assets.bellNew, + ), + ), + ), + width: 20, + height: 20, + color: ref.watch(notificationsProvider.select( + (value) => value.hasUnreadNotifications)) + ? null + : Theme.of(context) + .extension()! + .topNavIconPrimary, + ) + : SvgPicture.asset( + Assets.svg.bell, + width: 20, + height: 20, + color: ref.watch(notificationsProvider.select( + (value) => value.hasUnreadNotifications)) + ? null + : Theme.of(context) + .extension()! + .topNavIconPrimary, + ), onPressed: () { // reset unread state ref.refresh(unreadNotificationsStateProvider); @@ -242,6 +280,7 @@ class _HomeViewState extends ConsumerState { child: AspectRatio( aspectRatio: 1, child: AppBarIconButton( + semanticsLabel: "Settings Button. Takes To Settings Page.", key: const Key("walletsViewSettingsButton"), size: 36, shadows: const [], @@ -275,11 +314,16 @@ class _HomeViewState extends ConsumerState { color: Theme.of(context) .extension()! .backgroundAppBar, - boxShadow: [ - Theme.of(context) - .extension()! - .standardBoxShadow, - ], + boxShadow: Theme.of(context) + .extension()! + .homeViewButtonBarBoxShadow != + null + ? [ + Theme.of(context) + .extension()! + .homeViewButtonBarBoxShadow!, + ] + : null, ), child: const Padding( padding: EdgeInsets.only( @@ -296,35 +340,31 @@ class _HomeViewState extends ConsumerState { builder: (_, _ref, __) { _ref.listen(homeViewPageIndexStateProvider, (previous, next) { - if (next is int) { - if (next == 1) { - _exchangeDataLoadingService.loadAll(ref); - } - if (next >= 0 && next <= 1) { - _pageController.animateToPage( - next, - duration: const Duration(milliseconds: 300), - curve: Curves.decelerate, - ); - } + if (next is int && next >= 0 && next <= 2) { + // if (next == 1) { + // _exchangeDataLoadingService.loadAll(ref); + // } + // if (next == 2) { + // _buyDataLoadingService.loadAll(ref); + // } + + _lock = true; + _animateToPage(next).then((value) => _lock = false); } }); return PageView( controller: _pageController, children: _children, onPageChanged: (pageIndex) { - ref.read(homeViewPageIndexStateProvider.state).state = - pageIndex; + if (!_lock) { + ref.read(homeViewPageIndexStateProvider.state).state = + pageIndex; + } }, ); }, ), ), - // Expanded( - // child: HomeStack( - // children: _children, - // ), - // ), ], ), ), diff --git a/lib/pages/home_view/sub_widgets/home_view_button_bar.dart b/lib/pages/home_view/sub_widgets/home_view_button_bar.dart index 83a445cbf..58fda72a9 100644 --- a/lib/pages/home_view/sub_widgets/home_view_button_bar.dart +++ b/lib/pages/home_view/sub_widgets/home_view_button_bar.dart @@ -1,10 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; -import 'package:stackwallet/widgets/stack_dialog.dart'; class HomeViewButtonBar extends ConsumerStatefulWidget { const HomeViewButtonBar({Key? key}) : super(key: key); @@ -19,16 +17,16 @@ class _HomeViewButtonBarState extends ConsumerState { @override void initState() { - ref.read(exchangeFormStateProvider).setOnError( - onError: (String message) => showDialog( - context: context, - barrierDismissible: true, - builder: (_) => StackDialog( - title: "Exchange API Call Failed", - message: message, - ), - ), - ); + // ref.read(exchangeFormStateProvider).setOnError( + // onError: (String message) => showDialog( + // context: context, + // barrierDismissible: true, + // builder: (_) => StackDialog( + // title: "Exchange API Call Failed", + // message: message, + // ), + // ), + // ); super.initState(); } @@ -45,14 +43,14 @@ class _HomeViewButtonBarState extends ConsumerState { style: selectedIndex == 0 ? Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context)! + .getPrimaryEnabledButtonStyle(context)! .copyWith( minimumSize: MaterialStateProperty.all(const Size(46, 36)), ) : Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context)! + .getSecondaryEnabledButtonStyle(context)! .copyWith( minimumSize: MaterialStateProperty.all(const Size(46, 36)), @@ -71,7 +69,9 @@ class _HomeViewButtonBarState extends ConsumerState { ? Theme.of(context) .extension()! .buttonTextPrimary - : Theme.of(context).extension()!.textDark, + : Theme.of(context) + .extension()! + .buttonTextSecondary, ), ), ), @@ -84,14 +84,14 @@ class _HomeViewButtonBarState extends ConsumerState { style: selectedIndex == 1 ? Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context)! + .getPrimaryEnabledButtonStyle(context)! .copyWith( minimumSize: MaterialStateProperty.all(const Size(46, 36)), ) : Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context)! + .getSecondaryEnabledButtonStyle(context)! .copyWith( minimumSize: MaterialStateProperty.all(const Size(46, 36)), @@ -104,56 +104,69 @@ class _HomeViewButtonBarState extends ConsumerState { // DateTime now = DateTime.now(); // if (ref.read(prefsChangeNotifierProvider).externalCalls) { // print("loading?"); - await ExchangeDataLoadingService().loadAll(ref); + // await ExchangeDataLoadingService().loadAll(ref); // } // if (now.difference(_lastRefreshed) > _refreshInterval) { // await ExchangeDataLoadingService().loadAll(ref); // } }, child: Text( - "Exchange", + "Swap", style: STextStyles.button(context).copyWith( fontSize: 14, color: selectedIndex == 1 ? Theme.of(context) .extension()! .buttonTextPrimary - : Theme.of(context).extension()!.textDark, + : Theme.of(context) + .extension()! + .buttonTextSecondary, + ), + ), + ), + ), + const SizedBox( + width: 8, + ), + Expanded( + child: TextButton( + style: selectedIndex == 2 + ? Theme.of(context) + .extension()! + .getPrimaryEnabledButtonStyle(context)! + .copyWith( + minimumSize: + MaterialStateProperty.all(const Size(46, 36)), + ) + : Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context)! + .copyWith( + minimumSize: + MaterialStateProperty.all(const Size(46, 36)), + ), + onPressed: () async { + FocusScope.of(context).unfocus(); + if (selectedIndex != 2) { + ref.read(homeViewPageIndexStateProvider.state).state = 2; + } + // await BuyDataLoadingService().loadAll(ref); + }, + child: Text( + "Buy", + style: STextStyles.button(context).copyWith( + fontSize: 14, + color: selectedIndex == 2 + ? Theme.of(context) + .extension()! + .buttonTextPrimary + : Theme.of(context) + .extension()! + .buttonTextSecondary, ), ), ), ), - // TODO: Do not delete this code. - // only temporarily disabled - // SizedBox( - // width: 8, - // ), - // Expanded( - // child: TextButton( - // style: ButtonStyle( - // minimumSize: MaterialStateProperty.all(Size(46, 36)), - // backgroundColor: MaterialStateProperty.all( - // selectedIndex == 2 - // ? CFColors.stackAccent - // : CFColors.disabledButton, - // ), - // ), - // onPressed: () { - // FocusScope.of(context).unfocus(); - // if (selectedIndex != 2) { - // ref.read(homeViewPageIndexStateProvider.state).state = 2; - // } - // }, - // child: Text( - // "Buy", - // style: STextStyles.button(context).copyWith( - // fontSize: 14, - // color: - // selectedIndex == 2 ? CFColors.light1 : Theme.of(context).extension()!.accentColorDark - // ), - // ), - // ), - // ), ], ); } diff --git a/lib/pages/intro_view.dart b/lib/pages/intro_view.dart index 9218c0610..5771f4bca 100644 --- a/lib/pages/intro_view.dart +++ b/lib/pages/intro_view.dart @@ -1,27 +1,30 @@ +import 'dart:io'; + import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages/stack_privacy_calls.dart'; import 'package:stackwallet/pages_desktop_specific/password/create_password_view.dart'; -import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:url_launcher/url_launcher.dart'; -class IntroView extends StatefulWidget { +class IntroView extends ConsumerStatefulWidget { const IntroView({Key? key}) : super(key: key); static const String routeName = "/introView"; @override - State createState() => _IntroViewState(); + ConsumerState createState() => _IntroViewState(); } -class _IntroViewState extends State { +class _IntroViewState extends ConsumerState { late final bool isDesktop; @override @@ -52,10 +55,16 @@ class _IntroViewState extends State { constraints: const BoxConstraints( maxWidth: 300, ), - child: Image( - image: AssetImage( - Assets.png.stack, + child: SvgPicture.file( + File( + ref.watch( + themeProvider.select( + (value) => value.assets.stack, + ), + ), ), + width: isDesktop ? 324 : 266, + height: isDesktop ? 324 : 266, ), ), ), @@ -115,8 +124,14 @@ class _IntroViewState extends State { SizedBox( width: 130, height: 130, - child: SvgPicture.asset( - Assets.svg.stackIcon(context), + child: SvgPicture.file( + File( + ref.watch( + themeProvider.select( + (value) => value.assets.stackIcon, + ), + ), + ), ), ), const Spacer( @@ -259,7 +274,7 @@ class GetStartedButton extends StatelessWidget { ? TextButton( style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), onPressed: () { Prefs.instance.externalCalls = true; Navigator.of(context).pushNamed( @@ -278,7 +293,7 @@ class GetStartedButton extends StatelessWidget { child: TextButton( style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), onPressed: () { Navigator.of(context).pushNamed( StackPrivacyCalls.routeName, diff --git a/lib/pages/loading_view.dart b/lib/pages/loading_view.dart index b7db1aa58..7d05f8c82 100644 --- a/lib/pages/loading_view.dart +++ b/lib/pages/loading_view.dart @@ -1,29 +1,57 @@ +import 'dart:io'; import 'dart:math'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lottie/lottie.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; -class LoadingView extends StatelessWidget { +class LoadingView extends ConsumerWidget { const LoadingView({Key? key}) : super(key: key); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final size = MediaQuery.of(context).size; + final width = min(size.width, size.height) * 0.5; + + final assetPath = ref.watch( + themeProvider.select((value) => value.assets.loadingGif), + ); + return Background( child: Scaffold( backgroundColor: Theme.of(context).extension()!.background, body: Container( color: Theme.of(context).extension()!.background, child: Center( - child: SizedBox( - width: min(size.width, size.height) * 0.5, - child: Lottie.asset( - Assets.lottie.test2, - animate: true, - repeat: true, + child: ConditionalParent( + condition: Theme.of(context).extension()!.themeId == + "oled_black", + builder: (child) => RoundedContainer( + color: const Color(0xFFDEDEDE), + radiusMultiplier: 100, + width: width * 1.35, + height: width * 1.35, + child: child, + ), + child: SizedBox( + width: width, + child: assetPath != null + ? Image.file( + File( + assetPath, + ), + ) + : Lottie.asset( + Assets.lottie.test2, + animate: true, + repeat: true, + ), ), ), // child: Image( diff --git a/lib/pages/manage_favorites_view/manage_favorites_view.dart b/lib/pages/manage_favorites_view/manage_favorites_view.dart index 7d15974a8..148c8a67b 100644 --- a/lib/pages/manage_favorites_view/manage_favorites_view.dart +++ b/lib/pages/manage_favorites_view/manage_favorites_view.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; diff --git a/lib/pages/notification_views/notifications_view.dart b/lib/pages/notification_views/notifications_view.dart index a574e083d..fda479fb7 100644 --- a/lib/pages/notification_views/notifications_view.dart +++ b/lib/pages/notification_views/notifications_view.dart @@ -3,8 +3,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/notifications/notification_card.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/ui/unread_notifications_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; diff --git a/lib/pages/paynym/add_new_paynym_follow_view.dart b/lib/pages/paynym/add_new_paynym_follow_view.dart new file mode 100644 index 000000000..e27b218f9 --- /dev/null +++ b/lib/pages/paynym/add_new_paynym_follow_view.dart @@ -0,0 +1,459 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/paynym/paynym_account.dart'; +import 'package:stackwallet/pages/paynym/subwidgets/featured_paynyms_widget.dart'; +import 'package:stackwallet/pages/paynym/subwidgets/paynym_card.dart'; +import 'package:stackwallet/providers/global/paynym_api_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/barcode_scanner_interface.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; +import 'package:stackwallet/widgets/desktop/paynym_search_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/loading_indicator.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; + +class AddNewPaynymFollowView extends ConsumerStatefulWidget { + const AddNewPaynymFollowView({ + Key? key, + required this.walletId, + }) : super(key: key); + + final String walletId; + + static const String routeName = "/addNewPaynymFollow"; + + @override + ConsumerState createState() => + _AddNewPaynymFollowViewState(); +} + +class _AddNewPaynymFollowViewState + extends ConsumerState { + late final TextEditingController _searchController; + late final FocusNode searchFieldFocusNode; + + String _searchString = ""; + + bool _didSearch = false; + PaynymAccount? _searchResult; + + final isDesktop = Util.isDesktop; + + Future _search() async { + _didSearch = true; + bool didPopLoading = false; + unawaited( + showDialog( + barrierDismissible: false, + context: context, + builder: (context) => const LoadingIndicator( + width: 200, + ), + ).then((_) => didPopLoading = true), + ); + + final paynymAccount = await ref.read(paynymAPIProvider).nym(_searchString); + + if (mounted) { + if (!didPopLoading) { + Navigator.of(context).pop(); + } + + setState(() { + _searchResult = paynymAccount.value; + }); + } + } + + Future _clear() async { + _searchString = ""; + setState(() { + _searchController.text = ""; + }); + } + + Future _paste() async { + final ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain); + if (data?.text != null && data!.text!.isNotEmpty) { + String content = data.text!.trim(); + if (content.contains("\n")) { + content = content.substring( + 0, + content.indexOf( + "\n", + ), + ); + } + + _searchString = content; + setState(() { + _searchController.text = content; + _searchController.selection = TextSelection.collapsed( + offset: content.length, + ); + }); + } + } + + Future _scanQr() async { + try { + if (!isDesktop && FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + + final qrResult = await const BarcodeScannerWrapper().scan(); + + final pCodeString = qrResult.rawContent; + + _searchString = pCodeString; + + setState(() { + _searchController.text = pCodeString; + _searchController.selection = TextSelection.collapsed( + offset: pCodeString.length, + ); + }); + } catch (_) { + // scan failed + } + } + + @override + void initState() { + _searchController = TextEditingController(); + searchFieldFocusNode = FocusNode(); + super.initState(); + } + + @override + void dispose() { + _searchController.dispose(); + searchFieldFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + + return ConditionalParent( + condition: !isDesktop, + builder: (child) => MasterScaffold( + isDesktop: isDesktop, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + titleSpacing: 0, + title: Text( + "New follow", + style: STextStyles.navBarTitle(context), + overflow: TextOverflow.ellipsis, + ), + ), + body: SafeArea( + child: LayoutBuilder( + builder: (context, constraints) => SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(16), + child: child, + ), + ), + ), + ), + ), + ), + ), + child: ConditionalParent( + condition: isDesktop, + builder: (child) => DesktopDialog( + maxWidth: 580, + maxHeight: double.infinity, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "New follow", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: child, + ), + ], + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 10, + ), + Text( + "Featured PayNyms", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.sectionLabelMedium12(context), + ), + const SizedBox( + height: 12, + ), + FeaturedPaynymsWidget( + walletId: widget.walletId, + ), + const SizedBox( + height: 24, + ), + Text( + "Add new", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.sectionLabelMedium12(context), + ), + const SizedBox( + height: 12, + ), + if (isDesktop) + Row( + children: [ + Expanded( + child: Stack( + children: [ + RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + height: 56, + child: Center( + child: TextField( + autocorrect: !isDesktop, + enableSuggestions: !isDesktop, + controller: _searchController, + focusNode: searchFieldFocusNode, + onChanged: (value) { + setState(() { + _searchString = value; + }); + }, + style: STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveText, + // height: 1.8, + ), + decoration: InputDecoration( + hintText: "Paste payment code", + hoverColor: Colors.transparent, + fillColor: Colors.transparent, + contentPadding: const EdgeInsets.all(16), + hintStyle: + STextStyles.desktopTextFieldLabel(context) + .copyWith( + fontSize: 14, + ), + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + errorBorder: InputBorder.none, + disabledBorder: InputBorder.none, + focusedErrorBorder: InputBorder.none, + suffixIcon: Padding( + padding: const EdgeInsets.only(right: 8), + child: UnconstrainedBox( + child: Row( + children: [ + _searchController.text.isNotEmpty + ? TextFieldIconButton( + onTap: _clear, + child: RoundedContainer( + padding: + const EdgeInsets.all(8), + color: Theme.of(context) + .extension()! + .buttonBackSecondary, + child: const XIcon(), + ), + ) + : TextFieldIconButton( + key: const Key( + "paynymPasteAddressFieldButtonKey"), + onTap: _paste, + child: RoundedContainer( + padding: + const EdgeInsets.all(8), + color: Theme.of(context) + .extension()! + .buttonBackSecondary, + child: const ClipboardIcon(), + ), + ), + TextFieldIconButton( + key: const Key( + "paynymScanQrButtonKey"), + onTap: _scanQr, + child: RoundedContainer( + padding: const EdgeInsets.all(8), + color: Theme.of(context) + .extension()! + .buttonBackSecondary, + child: const QrCodeIcon(), + ), + ) + ], + ), + ), + ), + ), + ), + ), + ), + ], + ), + ), + const SizedBox( + width: 10, + ), + PaynymSearchButton(onPressed: _search), + ], + ), + if (!isDesktop) + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: !isDesktop, + enableSuggestions: !isDesktop, + controller: _searchController, + focusNode: searchFieldFocusNode, + onChanged: (value) { + setState(() { + _searchString = value; + }); + }, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Paste payment code", + searchFieldFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + suffixIcon: Padding( + padding: const EdgeInsets.only(right: 8), + child: UnconstrainedBox( + child: Row( + children: [ + _searchController.text.isNotEmpty + ? TextFieldIconButton( + onTap: _clear, + child: const XIcon(), + ) + : TextFieldIconButton( + key: const Key( + "paynymPasteAddressFieldButtonKey"), + onTap: _paste, + child: const ClipboardIcon(), + ), + TextFieldIconButton( + key: const Key("paynymScanQrButtonKey"), + onTap: _scanQr, + child: const QrCodeIcon(), + ) + ], + ), + ), + ), + ), + ), + ), + if (!isDesktop) + const SizedBox( + height: 12, + ), + if (!isDesktop) + SecondaryButton( + label: "Search", + onPressed: _search, + ), + if (_didSearch) + const SizedBox( + height: 20, + ), + if (_didSearch && _searchResult == null) + RoundedWhiteContainer( + borderColor: isDesktop + ? Theme.of(context) + .extension()! + .backgroundAppBar + : null, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Nothing found. Please check the payment code.", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.label(context), + ), + ], + ), + ), + if (_didSearch && _searchResult != null) + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + borderColor: isDesktop + ? Theme.of(context) + .extension()! + .backgroundAppBar + : null, + child: PaynymCard( + key: UniqueKey(), + label: _searchResult!.nymName, + paymentCodeString: _searchResult!.nonSegwitPaymentCode.code, + walletId: widget.walletId, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/paynym/dialogs/claiming_paynym_dialog.dart b/lib/pages/paynym/dialogs/claiming_paynym_dialog.dart new file mode 100644 index 000000000..339d5a838 --- /dev/null +++ b/lib/pages/paynym/dialogs/claiming_paynym_dialog.dart @@ -0,0 +1,98 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/animated_widgets/rotating_arrows.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; + +class ClaimingPaynymDialog extends StatefulWidget { + const ClaimingPaynymDialog({ + Key? key, + }) : super(key: key); + + @override + State createState() => _RestoringDialogState(); +} + +class _RestoringDialogState extends State { + @override + Widget build(BuildContext context) { + if (Util.isDesktop) { + return DesktopDialog( + maxWidth: 580, + maxHeight: double.infinity, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + DesktopDialogCloseButton( + onPressedOverride: () => Navigator.of(context).pop(true), + ), + ], + ), + const RotatingArrows( + width: 40, + height: 40, + ), + Padding( + padding: const EdgeInsets.all(40), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Claiming PayNym", + style: STextStyles.desktopH2(context), + ), + const SizedBox( + height: 20, + ), + Text( + "We are generating your PayNym", + style: STextStyles.desktopTextMedium(context).copyWith( + color: + Theme.of(context).extension()!.textDark3, + ), + ), + const SizedBox( + height: 40, + ), + SecondaryButton( + label: "Cancel", + width: 272, + onPressed: () { + Navigator.of(context).pop(true); + }, + ), + ], + ), + ), + ], + ), + ); + } else { + return WillPopScope( + onWillPop: () async { + return false; + }, + child: StackDialog( + title: "Claiming PayNym", + message: "We are generating your PayNym", + icon: const RotatingArrows( + width: 24, + height: 24, + ), + rightButton: SecondaryButton( + label: "Cancel", + onPressed: () { + Navigator.of(context).pop(true); + }, + ), + ), + ); + } + } +} diff --git a/lib/pages/paynym/dialogs/confirm_paynym_connect_dialog.dart b/lib/pages/paynym/dialogs/confirm_paynym_connect_dialog.dart new file mode 100644 index 000000000..f2f886241 --- /dev/null +++ b/lib/pages/paynym/dialogs/confirm_paynym_connect_dialog.dart @@ -0,0 +1,140 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; + +class ConfirmPaynymConnectDialog extends StatelessWidget { + const ConfirmPaynymConnectDialog({ + Key? key, + required this.nymName, + required this.locale, + required this.onConfirmPressed, + required this.amount, + required this.coin, + }) : super(key: key); + + final String nymName; + final String locale; + final VoidCallback onConfirmPressed; + final Amount amount; + final Coin coin; + + String get title => "Connect to $nymName"; + + String get message => "A one-time connection fee of " + "${amount.localizedStringAsFixed(locale: locale)} ${coin.ticker} " + "will be charged to connect to this PayNym.\n\nThis fee " + "covers the cost of creating a one-time transaction to create a " + "record on the blockchain. This keeps PayNyms decentralized."; + + @override + Widget build(BuildContext context) { + if (Util.isDesktop) { + return DesktopDialog( + maxHeight: double.infinity, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 40), + child: SvgPicture.asset( + Assets.svg.userPlus, + color: Theme.of(context).extension()!.textDark, + width: 32, + height: 32, + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Padding( + padding: const EdgeInsets.only( + left: 40, + bottom: 32, + right: 40, + ), + child: Text( + title, + style: STextStyles.desktopH3(context), + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 40, + right: 40, + ), + child: Text( + message, + style: STextStyles.desktopTextMedium(context).copyWith( + color: Theme.of(context).extension()!.textDark3, + ), + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 40, + bottom: 40, + right: 40, + top: 32, + ), + child: Row( + children: [ + Expanded( + child: SecondaryButton( + buttonHeight: ButtonHeight.l, + label: "Cancel", + onPressed: Navigator.of(context).pop, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + buttonHeight: ButtonHeight.l, + label: "Connect", + onPressed: onConfirmPressed, + ), + ), + ], + ), + ) + ], + ), + ); + } else { + return StackDialog( + title: title, + icon: SvgPicture.asset( + Assets.svg.userPlus, + color: Theme.of(context).extension()!.textDark, + width: 24, + height: 24, + ), + message: message, + leftButton: SecondaryButton( + buttonHeight: ButtonHeight.xl, + label: "Cancel", + onPressed: Navigator.of(context).pop, + ), + rightButton: PrimaryButton( + buttonHeight: ButtonHeight.xl, + label: "Connect", + onPressed: onConfirmPressed, + ), + ); + } + } +} diff --git a/lib/pages/paynym/dialogs/paynym_details_popup.dart b/lib/pages/paynym/dialogs/paynym_details_popup.dart new file mode 100644 index 000000000..653ef415b --- /dev/null +++ b/lib/pages/paynym/dialogs/paynym_details_popup.dart @@ -0,0 +1,437 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:stackwallet/exceptions/wallet/insufficient_balance_exception.dart'; +import 'package:stackwallet/models/paynym/paynym_account_lite.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages/paynym/dialogs/confirm_paynym_connect_dialog.dart'; +import 'package:stackwallet/pages/paynym/paynym_home_view.dart'; +import 'package:stackwallet/pages/paynym/subwidgets/paynym_bot.dart'; +import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart'; +import 'package:stackwallet/pages/send_view/send_view.dart'; +import 'package:stackwallet/providers/global/locale_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/route_generator.dart'; +import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/custom_buttons/paynym_follow_toggle_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/loading_indicator.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; +import 'package:tuple/tuple.dart'; + +class PaynymDetailsPopup extends ConsumerStatefulWidget { + const PaynymDetailsPopup({ + Key? key, + required this.walletId, + required this.accountLite, + }) : super(key: key); + + final String walletId; + final PaynymAccountLite accountLite; + + @override + ConsumerState createState() => _PaynymDetailsPopupState(); +} + +class _PaynymDetailsPopupState extends ConsumerState { + bool _showInsufficientFundsInfo = false; + + Future _onSend() async { + final manager = + ref.read(walletsChangeNotifierProvider).getManager(widget.walletId); + await Navigator.of(context).pushNamed( + SendView.routeName, + arguments: Tuple3( + manager.walletId, + manager.coin, + widget.accountLite, + ), + ); + } + + Future _onConnectPressed() async { + bool canPop = false; + unawaited( + showDialog( + context: context, + builder: (context) => WillPopScope( + onWillPop: () async => canPop, + child: const LoadingIndicator( + width: 200, + ), + ), + ), + ); + + final manager = + ref.read(walletsChangeNotifierProvider).getManager(widget.walletId); + + final wallet = manager.wallet as PaynymWalletInterface; + + if (await wallet.hasConnected(widget.accountLite.code)) { + canPop = true; + Navigator.of(context).pop(); + // TODO show info popup + return; + } + + final rates = await manager.fees; + + Map preparedTx; + + try { + preparedTx = await wallet.prepareNotificationTx( + selectedTxFeeRate: rates.medium, + targetPaymentCodeString: widget.accountLite.code, + ); + } on InsufficientBalanceException catch (_) { + if (mounted) { + canPop = true; + Navigator.of(context).pop(); + } + setState(() { + _showInsufficientFundsInfo = true; + }); + return; + } catch (e) { + if (mounted) { + canPop = true; + Navigator.of(context).pop(); + } + + await showDialog( + context: context, + builder: (context) => StackOkDialog( + title: "Error", + message: e.toString(), + ), + ); + return; + } + + if (mounted) { + // We have enough balance and prepared tx should be good to go. + + canPop = true; + // close loading + Navigator.of(context).pop(); + + // Close details + Navigator.of(context).pop(); + + // show info pop up + await showDialog( + context: context, + builder: (context) => ConfirmPaynymConnectDialog( + nymName: widget.accountLite.nymName, + locale: ref.read(localeServiceChangeNotifierProvider).locale, + onConfirmPressed: () { + // + print("CONFIRM NOTIF TX: $preparedTx"); + + Navigator.of(context).push( + RouteGenerator.getRoute( + builder: (_) => ConfirmTransactionView( + walletId: manager.walletId, + routeOnSuccessName: PaynymHomeView.routeName, + isPaynymNotificationTransaction: true, + transactionInfo: { + "hex": preparedTx["hex"], + "address": preparedTx["recipientPaynym"], + "recipientAmt": preparedTx["amount"], + "fee": preparedTx["fee"], + "vSize": preparedTx["vSize"], + "note": "PayNym connect" + }, + ), + ), + ); + }, + amount: (preparedTx["amount"] as Amount) + + (preparedTx["fee"] as int).toAmountAsRaw( + fractionDigits: manager.coin.decimals, + ), + coin: manager.coin, + ), + ); + } + } + + @override + Widget build(BuildContext context) { + final manager = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(widget.walletId))); + + final wallet = manager.wallet as PaynymWalletInterface; + + return DesktopDialog( + maxWidth: MediaQuery.of(context).size.width - 32, + maxHeight: double.infinity, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only( + left: 24, + top: 24, + right: 24, + bottom: 16, + ), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + PayNymBot( + paymentCodeString: widget.accountLite.code, + size: 36, + ), + const SizedBox( + width: 12, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.accountLite.nymName, + style: STextStyles.w600_14(context), + ), + FutureBuilder( + future: + wallet.hasConnected(widget.accountLite.code), + builder: (context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == + ConnectionState.done && + snapshot.data == true) { + return Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 2, + ), + Text( + "Connected", + style: STextStyles.w500_12(context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorGreen, + ), + ) + ], + ); + } else { + return Container(); + } + }, + ), + ], + ), + ], + ), + FutureBuilder( + future: wallet.hasConnected(widget.accountLite.code), + builder: (context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData) { + if (snapshot.data!) { + return PrimaryButton( + label: "Send", + buttonHeight: ButtonHeight.xl, + icon: SvgPicture.asset( + Assets.svg.circleArrowUpRight, + width: 14, + height: 14, + color: Theme.of(context) + .extension()! + .buttonTextPrimary, + ), + iconSpacing: 8, + width: 100, + onPressed: _onSend, + ); + } else { + return PrimaryButton( + label: "Connect", + buttonHeight: ButtonHeight.xl, + icon: SvgPicture.asset( + Assets.svg.circlePlusFilled, + width: 13, + height: 13, + color: Theme.of(context) + .extension()! + .buttonTextPrimary, + ), + iconSpacing: 8, + width: 128, + onPressed: _onConnectPressed, + ); + } + } else { + return const SizedBox( + height: 32, + child: LoadingIndicator(), + ); + } + }, + ), + ], + ), + if (_showInsufficientFundsInfo) + Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: 24, + ), + RoundedContainer( + color: Theme.of(context) + .extension()! + .warningBackground, + child: Text( + "Adding a PayNym to your contacts requires a one-time " + "transaction fee for creating the record on the " + "blockchain. Please deposit more " + "${ref.read(walletsChangeNotifierProvider).getManager(widget.walletId).wallet.coin.ticker} " + "into your wallet and try again.", + style: STextStyles.infoSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .warningForeground, + fontSize: 12, + ), + ), + ), + ], + ), + ], + ), + ), + Container( + color: Theme.of(context).extension()!.backgroundAppBar, + height: 1, + ), + Padding( + padding: const EdgeInsets.only( + left: 24, + top: 16, + right: 24, + bottom: 16, + ), + child: Row( + children: [ + Expanded( + child: ConstrainedBox( + constraints: const BoxConstraints(minHeight: 86), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "PayNym address", + style: STextStyles.infoSmall(context).copyWith( + fontSize: 12, + ), + ), + const SizedBox( + height: 6, + ), + Text( + widget.accountLite.code, + style: STextStyles.infoSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark, + fontSize: 12, + ), + ), + const SizedBox( + height: 6, + ), + ], + ), + ), + ), + const SizedBox( + width: 20, + ), + QrImage( + padding: const EdgeInsets.all(0), + size: 100, + data: widget.accountLite.code, + foregroundColor: + Theme.of(context).extension()!.textDark, + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 24, + right: 24, + bottom: 24, + ), + child: Row( + children: [ + Expanded( + child: PaynymFollowToggleButton( + walletId: widget.walletId, + paymentCodeStringToFollow: widget.accountLite.code, + style: PaynymFollowToggleButtonStyle.detailsPopup, + ), + ), + const SizedBox( + width: 12, + ), + Expanded( + child: SecondaryButton( + label: "Copy", + buttonHeight: ButtonHeight.xl, + iconSpacing: 8, + icon: SvgPicture.asset( + Assets.svg.copy, + width: 12, + height: 12, + color: Theme.of(context) + .extension()! + .buttonTextSecondary, + ), + onPressed: () async { + await Clipboard.setData( + ClipboardData( + text: widget.accountLite.code, + ), + ); + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + ), + ); + }, + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/paynym/dialogs/paynym_qr_popup.dart b/lib/pages/paynym/dialogs/paynym_qr_popup.dart new file mode 100644 index 000000000..7b6326d23 --- /dev/null +++ b/lib/pages/paynym/dialogs/paynym_qr_popup.dart @@ -0,0 +1,164 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:stackwallet/models/paynym/paynym_account.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages/paynym/subwidgets/paynym_bot.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; + +class PaynymQrPopup extends StatelessWidget { + const PaynymQrPopup({ + Key? key, + required this.paynymAccount, + }) : super(key: key); + + final PaynymAccount paynymAccount; + + @override + Widget build(BuildContext context) { + final isDesktop = Util.isDesktop; + + return DesktopDialog( + maxWidth: isDesktop ? 580 : MediaQuery.of(context).size.width - 32, + maxHeight: double.infinity, + child: Column( + children: [ + if (isDesktop) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Address details", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Padding( + padding: EdgeInsets.only( + left: isDesktop ? 32 : 24, + top: isDesktop ? 16 : 24, + right: 24, + bottom: 16, + ), + child: Row( + children: [ + PayNymBot( + paymentCodeString: paynymAccount.nonSegwitPaymentCode.code, + size: isDesktop ? 56 : 36, + ), + const SizedBox( + width: 12, + ), + Text( + paynymAccount.nymName, + style: isDesktop + ? STextStyles.w500_24(context) + : STextStyles.w600_14(context), + ), + ], + ), + ), + if (!isDesktop) + Container( + color: + Theme.of(context).extension()!.backgroundAppBar, + height: 1, + ), + Padding( + padding: const EdgeInsets.only( + left: 24, + top: 16, + right: 24, + bottom: 24, + ), + child: Row( + children: [ + Expanded( + child: ConstrainedBox( + constraints: const BoxConstraints(minHeight: 130), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + isDesktop ? "PayNym address" : "Your PayNym address", + style: isDesktop + ? STextStyles.desktopTextSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ) + : STextStyles.infoSmall(context).copyWith( + fontSize: 12, + ), + ), + const SizedBox( + height: 6, + ), + Text( + paynymAccount.nonSegwitPaymentCode.code, + style: isDesktop + ? STextStyles.desktopTextSmall(context) + : STextStyles.infoSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark, + fontSize: 12, + ), + ), + const SizedBox( + height: 6, + ), + CustomTextButton( + text: "Copy", + textSize: isDesktop ? 18 : 14, + onTap: () async { + await Clipboard.setData( + ClipboardData( + text: paynymAccount.nonSegwitPaymentCode.code, + ), + ); + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + ), + ); + }, + ), + ], + ), + ), + ), + const SizedBox( + width: 20, + ), + QrImage( + padding: const EdgeInsets.all(0), + size: 130, + data: paynymAccount.nonSegwitPaymentCode.code, + foregroundColor: + Theme.of(context).extension()!.textDark, + ), + ], + ), + ) + ], + ), + ); + } +} diff --git a/lib/pages/paynym/paynym_claim_view.dart b/lib/pages/paynym/paynym_claim_view.dart new file mode 100644 index 000000000..c703c581d --- /dev/null +++ b/lib/pages/paynym/paynym_claim_view.dart @@ -0,0 +1,302 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/models/paynym/paynym_account.dart'; +import 'package:stackwallet/pages/paynym/dialogs/claiming_paynym_dialog.dart'; +import 'package:stackwallet/pages/paynym/paynym_home_view.dart'; +import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; +import 'package:stackwallet/providers/global/paynym_api_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/providers/wallet/my_paynym_account_state_provider.dart'; +import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; +import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; + +class PaynymClaimView extends ConsumerStatefulWidget { + const PaynymClaimView({ + Key? key, + required this.walletId, + }) : super(key: key); + + final String walletId; + + static const String routeName = "/claimPaynym"; + + @override + ConsumerState createState() => _PaynymClaimViewState(); +} + +class _PaynymClaimViewState extends ConsumerState { + Future _addSegwitCode(PaynymAccount myAccount) async { + final manager = + ref.read(walletsChangeNotifierProvider).getManager(widget.walletId); + + // get wallet to access paynym calls + final wallet = manager.wallet as PaynymWalletInterface; + + final token = await ref + .read(paynymAPIProvider) + .token(myAccount.nonSegwitPaymentCode.code); + final signature = await wallet.signStringWithNotificationKey(token.value!); + + final pCodeSegwit = await wallet.getPaymentCode(isSegwit: true); + final addResult = await ref.read(paynymAPIProvider).add( + token.value!, + signature, + myAccount.nymID, + pCodeSegwit.toString(), + ); + + return addResult.value ?? false; + } + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + final isDesktop = Util.isDesktop; + + return MasterScaffold( + isDesktop: isDesktop, + appBar: isDesktop + ? DesktopAppBar( + isCompactHeight: true, + background: Theme.of(context).extension()!.popupBG, + leading: Row( + children: [ + Padding( + padding: const EdgeInsets.only( + left: 24, + right: 20, + ), + child: AppBarIconButton( + size: 32, + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + shadows: const [], + icon: SvgPicture.asset( + Assets.svg.arrowLeft, + width: 18, + height: 18, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, + ), + onPressed: Navigator.of(context).pop, + ), + ), + SvgPicture.asset( + Assets.svg.user, + width: 42, + height: 42, + color: Theme.of(context).extension()!.textDark, + ), + const SizedBox( + width: 10, + ), + Text( + "PayNym", + style: STextStyles.desktopH3(context), + ) + ], + ), + ) + : AppBar( + leading: const AppBarBackButton(), + titleSpacing: 0, + title: Text( + "PayNym", + style: STextStyles.navBarTitle(context), + overflow: TextOverflow.ellipsis, + ), + ), + body: ConditionalParent( + condition: !isDesktop, + builder: (child) => SafeArea( + child: Padding( + padding: const EdgeInsets.all(16), + child: child, + ), + ), + child: ConditionalParent( + condition: isDesktop, + builder: (child) => SizedBox( + width: 328, + child: child, + ), + child: Column( + children: [ + const Spacer( + flex: 1, + ), + Image( + image: AssetImage( + Assets.svg.unclaimedPaynym, + ), + width: MediaQuery.of(context).size.width / 2, + ), + const SizedBox( + height: 20, + ), + Text( + "You do not have a PayNym yet.\nClaim yours now!", + style: isDesktop + ? STextStyles.desktopSubtitleH2(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ) + : STextStyles.baseXS(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + textAlign: TextAlign.center, + ), + if (isDesktop) + const SizedBox( + height: 30, + ), + if (!isDesktop) + const Spacer( + flex: 2, + ), + PrimaryButton( + label: "Claim", + onPressed: () async { + bool shouldCancel = false; + unawaited( + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => const ClaimingPaynymDialog(), + ).then((value) => shouldCancel = value == true), + ); + + final manager = ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId); + + // get wallet to access paynym calls + final wallet = manager.wallet as PaynymWalletInterface; + + if (shouldCancel) return; + + // get payment code + final pCode = await wallet.getPaymentCode(isSegwit: false); + + if (shouldCancel) return; + + // attempt to create new entry in paynym.is db + final created = await ref + .read(paynymAPIProvider) + .create(pCode.toString()); + + debugPrint("created:$created"); + if (shouldCancel) return; + + if (created.value!.claimed) { + // payment code already claimed + debugPrint("pcode already claimed!!"); + + final account = + await ref.read(paynymAPIProvider).nym(pCode.toString()); + if (!account.value!.segwit) { + for (int i = 0; i < 100; i++) { + final result = await _addSegwitCode(account.value!); + if (result == true) { + break; + } + } + } + + if (mounted) { + if (isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + Navigator.of(context).pop(); + } else { + Navigator.of(context).popUntil( + ModalRoute.withName( + WalletView.routeName, + ), + ); + } + } + return; + } + + if (shouldCancel) return; + + final token = + await ref.read(paynymAPIProvider).token(pCode.toString()); + + if (shouldCancel) return; + + // sign token with notification private key + final signature = + await wallet.signStringWithNotificationKey(token.value!); + + if (shouldCancel) return; + + // claim paynym account + final claim = await ref + .read(paynymAPIProvider) + .claim(token.value!, signature); + + if (shouldCancel) return; + + if (claim.value?.claimed == pCode.toString()) { + final account = + await ref.read(paynymAPIProvider).nym(pCode.toString()); + if (!account.value!.segwit) { + for (int i = 0; i < 100; i++) { + final result = await _addSegwitCode(account.value!); + if (result == true) { + break; + } + } + } + + ref.read(myPaynymAccountStateProvider.state).state = + account.value!; + if (mounted) { + if (isDesktop) { + Navigator.of(context, rootNavigator: true).pop(); + Navigator.of(context).pop(); + } else { + Navigator.of(context).popUntil( + ModalRoute.withName( + WalletView.routeName, + ), + ); + } + await Navigator.of(context).pushNamed( + PaynymHomeView.routeName, + arguments: widget.walletId, + ); + } + } else if (mounted && !shouldCancel) { + Navigator.of(context, rootNavigator: isDesktop).pop(); + } + }, + ), + if (isDesktop) + const Spacer( + flex: 2, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/paynym/paynym_home_view.dart b/lib/pages/paynym/paynym_home_view.dart new file mode 100644 index 000000000..2a4ae4aaa --- /dev/null +++ b/lib/pages/paynym/paynym_home_view.dart @@ -0,0 +1,650 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:share_plus/share_plus.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages/paynym/add_new_paynym_follow_view.dart'; +import 'package:stackwallet/pages/paynym/dialogs/paynym_qr_popup.dart'; +import 'package:stackwallet/pages/paynym/subwidgets/desktop_paynym_details.dart'; +import 'package:stackwallet/pages/paynym/subwidgets/paynym_bot.dart'; +import 'package:stackwallet/pages/paynym/subwidgets/paynym_followers_list.dart'; +import 'package:stackwallet/pages/paynym/subwidgets/paynym_following_list.dart'; +import 'package:stackwallet/providers/ui/selected_paynym_details_item_Provider.dart'; +import 'package:stackwallet/providers/wallet/my_paynym_account_state_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/format.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; +import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/icon_widgets/copy_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/share_icon.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/toggle.dart'; + +class PaynymHomeView extends ConsumerStatefulWidget { + const PaynymHomeView({ + Key? key, + required this.walletId, + }) : super(key: key); + + final String walletId; + + static const String routeName = "/paynymHome"; + + @override + ConsumerState createState() => _PaynymHomeViewState(); +} + +class _PaynymHomeViewState extends ConsumerState { + bool showFollowers = false; + int secretCount = 0; + Timer? timer; + + bool _followButtonHoverState = false; + + @override + void dispose() { + timer?.cancel(); + timer = null; + super.dispose(); + } + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + final isDesktop = Util.isDesktop; + + return MasterScaffold( + isDesktop: isDesktop, + appBar: isDesktop + ? DesktopAppBar( + isCompactHeight: true, + background: Theme.of(context).extension()!.popupBG, + leading: Row( + children: [ + Padding( + padding: const EdgeInsets.only( + left: 24, + right: 20, + ), + child: AppBarIconButton( + size: 32, + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + shadows: const [], + icon: SvgPicture.asset( + Assets.svg.arrowLeft, + width: 18, + height: 18, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, + ), + onPressed: Navigator.of(context).pop, + ), + ), + SvgPicture.asset( + Assets.svg.user, + width: 32, + height: 32, + color: Theme.of(context).extension()!.textDark, + ), + const SizedBox( + width: 10, + ), + Text( + "PayNym", + style: STextStyles.desktopH3(context), + ) + ], + ), + trailing: Padding( + padding: const EdgeInsets.only(right: 12), + child: SizedBox( + height: 56, + child: MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (_) => setState(() { + _followButtonHoverState = true; + }), + onExit: (_) => setState(() { + _followButtonHoverState = false; + }), + child: GestureDetector( + onTap: () { + showDialog( + context: context, + builder: (context) => AddNewPaynymFollowView( + walletId: widget.walletId, + ), + ); + }, + child: RoundedContainer( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + color: _followButtonHoverState + ? Theme.of(context) + .extension()! + .highlight + : Colors.transparent, + radiusMultiplier: 100, + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.plus, + width: 16, + height: 16, + color: Theme.of(context) + .extension()! + .textDark, + ), + const SizedBox( + width: 8, + ), + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Follow", + style: + STextStyles.desktopButtonSecondaryEnabled( + context) + .copyWith( + fontSize: 16, + ), + ), + const SizedBox( + height: 2, + ), + ], + ), + ], + ), + ), + ), + ), + ), + ), + ) + : AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + titleSpacing: 0, + title: Text( + "PayNym", + style: STextStyles.navBarTitle(context), + overflow: TextOverflow.ellipsis, + ), + actions: [ + Padding( + padding: const EdgeInsets.symmetric(vertical: 6), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + icon: SvgPicture.asset( + Assets.svg.circlePlusFilled, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + onPressed: () { + Navigator.of(context).pushNamed( + AddNewPaynymFollowView.routeName, + arguments: widget.walletId, + ); + }, + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 6), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + icon: SvgPicture.asset( + Assets.svg.circleQuestion, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + onPressed: () { + // todo info ? + }, + ), + ), + ), + const SizedBox( + width: 4, + ), + ], + ), + body: ConditionalParent( + condition: !isDesktop, + builder: (child) => SafeArea( + child: Padding( + padding: const EdgeInsets.all(16), + child: child, + ), + ), + child: Column( + crossAxisAlignment: + isDesktop ? CrossAxisAlignment.start : CrossAxisAlignment.center, + children: [ + if (!isDesktop) + Column( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + onTap: () { + secretCount++; + if (secretCount > 5) { + debugPrint( + "My Account: ${ref.read(myPaynymAccountStateProvider.state).state}"); + debugPrint( + "My Account: ${ref.read(myPaynymAccountStateProvider.state).state!.following}"); + secretCount = 0; + } + + timer ??= Timer( + const Duration(milliseconds: 1500), + () { + secretCount = 0; + timer = null; + }, + ); + }, + child: PayNymBot( + paymentCodeString: ref + .watch(myPaynymAccountStateProvider.state) + .state! + .nonSegwitPaymentCode + .code, + ), + ), + const SizedBox( + height: 10, + ), + Text( + ref + .watch(myPaynymAccountStateProvider.state) + .state! + .nymName, + style: STextStyles.desktopMenuItemSelected(context), + ), + const SizedBox( + height: 4, + ), + Text( + Format.shorten( + ref + .watch(myPaynymAccountStateProvider.state) + .state! + .nonSegwitPaymentCode + .code, + 12, + 5), + style: STextStyles.label(context).copyWith( + fontSize: 14, + ), + ), + const SizedBox( + height: 11, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Copy", + buttonHeight: ButtonHeight.xl, + iconSpacing: 8, + icon: CopyIcon( + width: 12, + height: 12, + color: Theme.of(context) + .extension()! + .buttonTextSecondary, + ), + onPressed: () async { + await Clipboard.setData( + ClipboardData( + text: ref + .read(myPaynymAccountStateProvider.state) + .state! + .nonSegwitPaymentCode + .code, + ), + ); + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + ), + ); + }, + ), + ), + const SizedBox( + width: 13, + ), + Expanded( + child: SecondaryButton( + label: "Share", + buttonHeight: ButtonHeight.xl, + iconSpacing: 8, + icon: ShareIcon( + width: 12, + height: 12, + color: Theme.of(context) + .extension()! + .buttonTextSecondary, + ), + onPressed: () async { + Rect? sharePositionOrigin; + if (await Util.isIPad) { + final box = + context.findRenderObject() as RenderBox?; + if (box != null) { + sharePositionOrigin = + box.localToGlobal(Offset.zero) & box.size; + } + } + + await Share.share( + ref + .read(myPaynymAccountStateProvider.state) + .state! + .nonSegwitPaymentCode + .code, + sharePositionOrigin: sharePositionOrigin); + }, + ), + ), + const SizedBox( + width: 13, + ), + Expanded( + child: SecondaryButton( + label: "Address", + buttonHeight: ButtonHeight.xl, + iconSpacing: 8, + icon: QrCodeIcon( + width: 12, + height: 12, + color: Theme.of(context) + .extension()! + .buttonTextSecondary, + ), + onPressed: () { + showDialog( + context: context, + builder: (context) => PaynymQrPopup( + paynymAccount: ref + .read(myPaynymAccountStateProvider.state) + .state!, + ), + ); + }, + ), + ), + ], + ), + ], + ), + if (isDesktop) + Padding( + padding: const EdgeInsets.all(24), + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + const SizedBox( + width: 4, + ), + GestureDetector( + onTap: () { + secretCount++; + if (secretCount > 5) { + debugPrint( + "My Account: ${ref.read(myPaynymAccountStateProvider.state).state}"); + debugPrint( + "My Account: ${ref.read(myPaynymAccountStateProvider.state).state!.following}"); + secretCount = 0; + } + + timer ??= Timer( + const Duration(milliseconds: 1500), + () { + secretCount = 0; + timer = null; + }, + ); + }, + child: PayNymBot( + paymentCodeString: ref + .watch(myPaynymAccountStateProvider.state) + .state! + .nonSegwitPaymentCode + .code, + ), + ), + const SizedBox( + width: 16, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + ref + .watch(myPaynymAccountStateProvider.state) + .state! + .nymName, + style: STextStyles.desktopH3(context), + ), + const SizedBox( + height: 4, + ), + Text( + Format.shorten( + ref + .watch(myPaynymAccountStateProvider.state) + .state! + .nonSegwitPaymentCode + .code, + 12, + 5), + style: + STextStyles.desktopTextExtraExtraSmall(context), + ), + ], + ), + const Spacer(), + SecondaryButton( + label: "Copy", + buttonHeight: ButtonHeight.l, + width: 160, + icon: CopyIcon( + width: 18, + height: 18, + color: Theme.of(context) + .extension()! + .textDark, + ), + onPressed: () async { + await Clipboard.setData( + ClipboardData( + text: ref + .read(myPaynymAccountStateProvider.state) + .state! + .nonSegwitPaymentCode + .code, + ), + ); + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + ), + ); + }, + ), + const SizedBox( + width: 16, + ), + SecondaryButton( + label: "Address", + width: 160, + buttonHeight: ButtonHeight.l, + icon: QrCodeIcon( + width: 18, + height: 18, + color: Theme.of(context) + .extension()! + .textDark, + ), + onPressed: () { + showDialog( + context: context, + builder: (context) => PaynymQrPopup( + paynymAccount: ref + .read(myPaynymAccountStateProvider.state) + .state!, + ), + ); + }, + ), + ], + ), + ), + ), + if (!isDesktop) + const SizedBox( + height: 24, + ), + ConditionalParent( + condition: isDesktop, + builder: (child) => Padding( + padding: const EdgeInsets.only(left: 24), + child: child, + ), + child: SizedBox( + height: isDesktop ? 56 : 48, + width: isDesktop ? 490 : null, + child: Toggle( + key: UniqueKey(), + onColor: Theme.of(context).extension()!.popupBG, + onText: + "Following (${ref.watch(myPaynymAccountStateProvider.state).state?.following.length ?? 0})", + offColor: Theme.of(context) + .extension()! + .textFieldDefaultBG, + offText: + "Followers (${ref.watch(myPaynymAccountStateProvider.state).state?.followers.length ?? 0})", + isOn: showFollowers, + onValueChanged: (value) { + setState(() { + showFollowers = value; + }); + }, + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + ), + ), + SizedBox( + height: isDesktop ? 20 : 16, + ), + Expanded( + child: ConditionalParent( + condition: isDesktop, + builder: (child) => Padding( + padding: const EdgeInsets.only(left: 24), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 490, + child: child, + ), + const SizedBox( + width: 24, + ), + if (ref + .watch(selectedPaynymDetailsItemProvider.state) + .state != + null) + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ConstrainedBox( + constraints: const BoxConstraints( + maxWidth: 600, + ), + child: DesktopPaynymDetails( + walletId: widget.walletId, + accountLite: ref + .watch(selectedPaynymDetailsItemProvider + .state) + .state!, + ), + ), + ], + ), + ), + if (ref + .watch(selectedPaynymDetailsItemProvider.state) + .state != + null) + const SizedBox( + width: 24, + ), + ], + ), + ), + child: ConditionalParent( + condition: !isDesktop, + builder: (child) => Container( + child: child, + ), + child: !showFollowers + ? PaynymFollowingList( + walletId: widget.walletId, + ) + : PaynymFollowersList( + walletId: widget.walletId, + ), + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/paynym/subwidgets/desktop_paynym_details.dart b/lib/pages/paynym/subwidgets/desktop_paynym_details.dart new file mode 100644 index 000000000..ee70223f9 --- /dev/null +++ b/lib/pages/paynym/subwidgets/desktop_paynym_details.dart @@ -0,0 +1,396 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:stackwallet/exceptions/wallet/insufficient_balance_exception.dart'; +import 'package:stackwallet/models/paynym/paynym_account_lite.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages/paynym/dialogs/confirm_paynym_connect_dialog.dart'; +import 'package:stackwallet/pages/paynym/subwidgets/paynym_bot.dart'; +import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart'; +import 'package:stackwallet/pages_desktop_specific/my_stack_view/paynym/desktop_paynym_send_dialog.dart'; +import 'package:stackwallet/providers/global/locale_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; +import 'package:stackwallet/widgets/custom_buttons/paynym_follow_toggle_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/loading_indicator.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class DesktopPaynymDetails extends ConsumerStatefulWidget { + const DesktopPaynymDetails({ + Key? key, + required this.walletId, + required this.accountLite, + }) : super(key: key); + + final String walletId; + final PaynymAccountLite accountLite; + + @override + ConsumerState createState() => + _PaynymDetailsPopupState(); +} + +class _PaynymDetailsPopupState extends ConsumerState { + bool _showInsufficientFundsInfo = false; + + Future _onConnectPressed() async { + bool canPop = false; + unawaited( + showDialog( + context: context, + builder: (context) => WillPopScope( + onWillPop: () async => canPop, + child: const LoadingIndicator( + width: 200, + ), + ), + ), + ); + + final manager = + ref.read(walletsChangeNotifierProvider).getManager(widget.walletId); + + final wallet = manager.wallet as PaynymWalletInterface; + + if (await wallet.hasConnected(widget.accountLite.code)) { + canPop = true; + Navigator.of(context, rootNavigator: true).pop(); + // TODO show info popup + return; + } + + final rates = await manager.fees; + + Map preparedTx; + + try { + preparedTx = await wallet.prepareNotificationTx( + selectedTxFeeRate: rates.medium, + targetPaymentCodeString: widget.accountLite.code, + ); + } on InsufficientBalanceException catch (e) { + if (mounted) { + canPop = true; + Navigator.of(context, rootNavigator: true).pop(); + } + setState(() { + _showInsufficientFundsInfo = true; + }); + return; + } + + if (mounted) { + // We have enough balance and prepared tx should be good to go. + + canPop = true; + // close loading + Navigator.of(context, rootNavigator: true).pop(); + + // show info pop up + await showDialog( + context: context, + builder: (context) => ConfirmPaynymConnectDialog( + nymName: widget.accountLite.nymName, + locale: ref.read(localeServiceChangeNotifierProvider).locale, + onConfirmPressed: () { + Navigator.of(context, rootNavigator: true).pop(); + unawaited( + showDialog( + context: context, + builder: (context) => DesktopDialog( + maxHeight: double.infinity, + maxWidth: 580, + child: ConfirmTransactionView( + walletId: manager.walletId, + isPaynymNotificationTransaction: true, + transactionInfo: { + "hex": preparedTx["hex"], + "address": preparedTx["recipientPaynym"], + "recipientAmt": preparedTx["amount"], + "fee": preparedTx["fee"], + "vSize": preparedTx["vSize"], + "note": "PayNym connect" + }, + onSuccessInsteadOfRouteOnSuccess: () { + Navigator.of(context, rootNavigator: true).pop(); + Navigator.of(context, rootNavigator: true).pop(); + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: + "Connection initiated to ${widget.accountLite.nymName}", + iconAsset: Assets.svg.copy, + context: context, + ), + ); + }, + ), + ), + ), + ); + }, + amount: (preparedTx["amount"] as Amount) + + (preparedTx["fee"] as int).toAmountAsRaw( + fractionDigits: manager.coin.decimals, + ), + coin: manager.coin, + ), + ); + } + } + + Future _onSend() async { + await showDialog( + context: context, + builder: (context) => DesktopPaynymSendDialog( + walletId: widget.walletId, + accountLite: widget.accountLite, + ), + ); + } + + @override + Widget build(BuildContext context) { + final manager = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(widget.walletId))); + + final wallet = manager.wallet as PaynymWalletInterface; + + return RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + PayNymBot( + paymentCodeString: widget.accountLite.code, + size: 36, + ), + const SizedBox( + width: 12, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.accountLite.nymName, + style: STextStyles.desktopTextSmall(context), + ), + FutureBuilder( + future: wallet.hasConnected(widget.accountLite.code), + builder: (context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == + ConnectionState.done && + snapshot.data == true) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 2, + ), + Text( + "Connected", + style: STextStyles.desktopTextSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorGreen, + ), + ) + ], + ); + } else { + return Container(); + } + }, + ), + ], + ), + ], + ), + const SizedBox( + height: 20, + ), + Row( + children: [ + Expanded( + child: FutureBuilder( + future: wallet.hasConnected(widget.accountLite.code), + builder: (context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + if (snapshot.data!) { + return PrimaryButton( + label: "Send", + buttonHeight: ButtonHeight.s, + icon: SvgPicture.asset( + Assets.svg.circleArrowUpRight, + width: 16, + height: 16, + color: Theme.of(context) + .extension()! + .buttonTextPrimary, + ), + iconSpacing: 6, + onPressed: _onSend, + ); + } else { + return PrimaryButton( + label: "Connect", + buttonHeight: ButtonHeight.s, + icon: SvgPicture.asset( + Assets.svg.circlePlusFilled, + width: 16, + height: 16, + color: Theme.of(context) + .extension()! + .buttonTextPrimary, + ), + iconSpacing: 6, + onPressed: _onConnectPressed, + ); + } + } else { + return const SizedBox( + height: 100, + child: LoadingIndicator(), + ); + } + }, + ), + ), + const SizedBox( + width: 20, + ), + Expanded( + child: PaynymFollowToggleButton( + walletId: widget.walletId, + paymentCodeStringToFollow: widget.accountLite.code, + style: PaynymFollowToggleButtonStyle.detailsDesktop, + ), + ), + ], + ), + if (_showInsufficientFundsInfo) + Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: 24, + ), + RoundedContainer( + color: Theme.of(context) + .extension()! + .warningBackground, + child: Text( + "Adding a PayNym to your contacts requires a one-time " + "transaction fee for creating the record on the " + "blockchain. Please deposit more " + "${ref.read(walletsChangeNotifierProvider).getManager(widget.walletId).wallet.coin.ticker} " + "into your wallet and try again.", + style: STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .warningForeground, + ), + ), + ), + ], + ), + ], + ), + ), + Container( + color: Theme.of(context).extension()!.backgroundAppBar, + height: 1, + ), + Padding( + padding: const EdgeInsets.all(24), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "PayNym address", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + const SizedBox( + height: 8, + ), + Row( + children: [ + Expanded( + child: ConstrainedBox( + constraints: const BoxConstraints(minHeight: 100), + child: Text( + widget.accountLite.code, + style: STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + ), + ), + const SizedBox( + width: 20, + ), + QrImage( + padding: const EdgeInsets.all(0), + size: 100, + data: widget.accountLite.code, + foregroundColor: + Theme.of(context).extension()!.textDark, + ), + ], + ), + const SizedBox( + height: 8, + ), + CustomTextButton( + text: "Copy", + onTap: () async { + await Clipboard.setData( + ClipboardData( + text: widget.accountLite.code, + ), + ); + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + ), + ); + }, + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/paynym/subwidgets/featured_paynyms_widget.dart b/lib/pages/paynym/subwidgets/featured_paynyms_widget.dart new file mode 100644 index 000000000..f88e25c7f --- /dev/null +++ b/lib/pages/paynym/subwidgets/featured_paynyms_widget.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/pages/paynym/subwidgets/paynym_card.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/featured_paynyms.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class FeaturedPaynymsWidget extends StatelessWidget { + const FeaturedPaynymsWidget({ + Key? key, + required this.walletId, + }) : super(key: key); + + final String walletId; + + @override + Widget build(BuildContext context) { + final entries = FeaturedPaynyms.featured.entries.toList(growable: false); + final isDesktop = Util.isDesktop; + + return ConditionalParent( + condition: !isDesktop, + builder: (child) => RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: child, + ), + child: Column( + children: [ + for (int i = 0; i < entries.length; i++) + Column( + children: [ + if (i > 0) + isDesktop + ? const SizedBox( + height: 10, + ) + : Container( + color: Theme.of(context) + .extension()! + .backgroundAppBar, + height: 1, + ), + ConditionalParent( + condition: isDesktop, + builder: (child) => RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + borderColor: Theme.of(context) + .extension()! + .backgroundAppBar, + child: child, + ), + child: PaynymCard( + walletId: walletId, + label: entries[i].key, + paymentCodeString: entries[i].value, + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/pages/paynym/subwidgets/paynym_bot.dart b/lib/pages/paynym/subwidgets/paynym_bot.dart new file mode 100644 index 000000000..40dadf812 --- /dev/null +++ b/lib/pages/paynym/subwidgets/paynym_bot.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/widgets/loading_indicator.dart'; + +class PayNymBot extends StatelessWidget { + const PayNymBot({ + Key? key, + required this.paymentCodeString, + this.size = 60.0, + }) : super(key: key); + + final String paymentCodeString; + final double size; + + @override + Widget build(BuildContext context) { + return ClipRRect( + borderRadius: BorderRadius.circular(size / 2), + child: SizedBox( + width: size, + height: size, + child: Image.network( + "https://paynym.is/$paymentCodeString/avatar", + loadingBuilder: (context, child, loadingProgress) => + loadingProgress == null + ? child + : const Center( + child: LoadingIndicator(), + ), + ), + ), + ); + } +} diff --git a/lib/pages/paynym/subwidgets/paynym_card.dart b/lib/pages/paynym/subwidgets/paynym_card.dart new file mode 100644 index 000000000..7738424bc --- /dev/null +++ b/lib/pages/paynym/subwidgets/paynym_card.dart @@ -0,0 +1,85 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/pages/paynym/subwidgets/paynym_bot.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/format.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/custom_buttons/paynym_follow_toggle_button.dart'; + +class PaynymCard extends StatefulWidget { + const PaynymCard({ + Key? key, + required this.walletId, + required this.label, + required this.paymentCodeString, + }) : super(key: key); + + final String walletId; + final String label; + final String paymentCodeString; + + @override + State createState() => _PaynymCardState(); +} + +class _PaynymCardState extends State { + final isDesktop = Util.isDesktop; + + @override + Widget build(BuildContext context) { + return Padding( + padding: isDesktop + ? const EdgeInsets.symmetric( + vertical: 16, + horizontal: 20, + ) + : const EdgeInsets.all(12), + child: Row( + children: [ + PayNymBot( + size: 36, + paymentCodeString: widget.paymentCodeString, + ), + const SizedBox( + width: 12, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.label, + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveText, + ) + : STextStyles.w500_14(context), + ), + const SizedBox( + height: 2, + ), + Text( + Format.shorten(widget.paymentCodeString, 12, 5), + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.w500_14(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ], + ), + ), + PaynymFollowToggleButton( + walletId: widget.walletId, + paymentCodeStringToFollow: widget.paymentCodeString, + ), + ], + ), + ); + } +} diff --git a/lib/pages/paynym/subwidgets/paynym_card_button.dart b/lib/pages/paynym/subwidgets/paynym_card_button.dart new file mode 100644 index 000000000..2697b78ee --- /dev/null +++ b/lib/pages/paynym/subwidgets/paynym_card_button.dart @@ -0,0 +1,124 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/paynym/paynym_account_lite.dart'; +import 'package:stackwallet/pages/paynym/dialogs/paynym_details_popup.dart'; +import 'package:stackwallet/pages/paynym/subwidgets/paynym_bot.dart'; +import 'package:stackwallet/providers/ui/selected_paynym_details_item_Provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/format.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; + +class PaynymCardButton extends ConsumerStatefulWidget { + const PaynymCardButton({ + Key? key, + required this.walletId, + required this.accountLite, + }) : super(key: key); + + final String walletId; + final PaynymAccountLite accountLite; + + @override + ConsumerState createState() => _PaynymCardButtonState(); +} + +class _PaynymCardButtonState extends ConsumerState { + final isDesktop = Util.isDesktop; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(4), + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: isDesktop && + ref + .watch(selectedPaynymDetailsItemProvider.state) + .state + ?.nymId == + widget.accountLite.nymId + ? Theme.of(context) + .extension()! + .accentColorDark + .withOpacity(0.08) + : Colors.transparent, + child: RawMaterialButton( + padding: const EdgeInsets.all(0), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + if (isDesktop) { + ref.read(selectedPaynymDetailsItemProvider.state).state = + widget.accountLite; + } else { + showDialog( + context: context, + builder: (context) => PaynymDetailsPopup( + accountLite: widget.accountLite, + walletId: widget.walletId, + ), + ); + } + }, + child: Padding( + padding: isDesktop + ? const EdgeInsets.symmetric( + vertical: 8, + horizontal: 12, + ) + : const EdgeInsets.all(8.0), + child: Row( + children: [ + PayNymBot( + size: 36, + paymentCodeString: widget.accountLite.code, + ), + const SizedBox( + width: 12, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + widget.accountLite.nymName, + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveText, + ) + : STextStyles.w500_14(context), + ), + const SizedBox( + height: 2, + ), + Text( + Format.shorten(widget.accountLite.code, 12, 5), + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + : STextStyles.w500_14(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/paynym/subwidgets/paynym_followers_list.dart b/lib/pages/paynym/subwidgets/paynym_followers_list.dart new file mode 100644 index 000000000..fa581d700 --- /dev/null +++ b/lib/pages/paynym/subwidgets/paynym_followers_list.dart @@ -0,0 +1,155 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/pages/paynym/subwidgets/paynym_card_button.dart'; +import 'package:stackwallet/providers/global/paynym_api_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/providers/wallet/my_paynym_account_state_provider.dart'; +import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class PaynymFollowersList extends ConsumerStatefulWidget { + const PaynymFollowersList({ + Key? key, + required this.walletId, + }) : super(key: key); + + final String walletId; + + @override + ConsumerState createState() => + _PaynymFollowersListState(); +} + +class _PaynymFollowersListState extends ConsumerState { + final isDesktop = Util.isDesktop; + + BorderRadius get _borderRadiusFirst { + return BorderRadius.only( + topLeft: Radius.circular( + Constants.size.circularBorderRadius, + ), + topRight: Radius.circular( + Constants.size.circularBorderRadius, + ), + ); + } + + BorderRadius get _borderRadiusLast { + return BorderRadius.only( + bottomLeft: Radius.circular( + Constants.size.circularBorderRadius, + ), + bottomRight: Radius.circular( + Constants.size.circularBorderRadius, + ), + ); + } + + @override + Widget build(BuildContext context) { + final followers = + ref.watch(myPaynymAccountStateProvider.state).state?.followers; + final count = followers?.length ?? 0; + + return ConditionalParent( + condition: !isDesktop, + builder: (child) => RefreshIndicator( + child: child, + onRefresh: () async { + try { + final manager = ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId); + + // get wallet to access paynym calls + final wallet = manager.wallet as PaynymWalletInterface; + + // get payment code + final pCode = await wallet.getPaymentCode( + isSegwit: false, + ); + + // get account from api + final account = + await ref.read(paynymAPIProvider).nym(pCode.toString()); + + // update my account + if (account.value != null) { + ref.read(myPaynymAccountStateProvider.state).state = + account.value!; + } + } catch (e) { + Logging.instance.log( + "Failed pull down refresh of paynym home page: $e", + level: LogLevel.Warning, + ); + } + }, + ), + child: ListView.separated( + itemCount: max(count, 1), + separatorBuilder: (BuildContext context, int index) => Container( + height: 1.5, + color: Colors.transparent, + ), + itemBuilder: (BuildContext context, int index) { + if (count == 0) { + return RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Your PayNym followers will appear here", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ) + : STextStyles.label(context), + ), + ], + ), + ); + } else if (count == 1) { + return RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: PaynymCardButton( + walletId: widget.walletId, + accountLite: followers![0], + ), + ); + } else { + BorderRadius? borderRadius; + if (index == 0) { + borderRadius = _borderRadiusFirst; + } else if (index == count - 1) { + borderRadius = _borderRadiusLast; + } + + return Container( + key: Key("paynymCardKey_${followers![index].nymId}"), + decoration: BoxDecoration( + borderRadius: borderRadius, + color: Theme.of(context).extension()!.popupBG, + ), + child: PaynymCardButton( + walletId: widget.walletId, + accountLite: followers[index], + ), + ); + } + }, + ), + ); + } +} diff --git a/lib/pages/paynym/subwidgets/paynym_following_list.dart b/lib/pages/paynym/subwidgets/paynym_following_list.dart new file mode 100644 index 000000000..c1d2157d2 --- /dev/null +++ b/lib/pages/paynym/subwidgets/paynym_following_list.dart @@ -0,0 +1,155 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/pages/paynym/subwidgets/paynym_card_button.dart'; +import 'package:stackwallet/providers/global/paynym_api_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/providers/wallet/my_paynym_account_state_provider.dart'; +import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class PaynymFollowingList extends ConsumerStatefulWidget { + const PaynymFollowingList({ + Key? key, + required this.walletId, + }) : super(key: key); + + final String walletId; + + @override + ConsumerState createState() => + _PaynymFollowingListState(); +} + +class _PaynymFollowingListState extends ConsumerState { + final isDesktop = Util.isDesktop; + + BorderRadius get _borderRadiusFirst { + return BorderRadius.only( + topLeft: Radius.circular( + Constants.size.circularBorderRadius, + ), + topRight: Radius.circular( + Constants.size.circularBorderRadius, + ), + ); + } + + BorderRadius get _borderRadiusLast { + return BorderRadius.only( + bottomLeft: Radius.circular( + Constants.size.circularBorderRadius, + ), + bottomRight: Radius.circular( + Constants.size.circularBorderRadius, + ), + ); + } + + @override + Widget build(BuildContext context) { + final following = + ref.watch(myPaynymAccountStateProvider.state).state?.following; + final count = following?.length ?? 0; + + return ConditionalParent( + condition: !isDesktop, + builder: (child) => RefreshIndicator( + child: child, + onRefresh: () async { + try { + final manager = ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId); + + // get wallet to access paynym calls + final wallet = manager.wallet as PaynymWalletInterface; + + // get payment code + final pCode = await wallet.getPaymentCode( + isSegwit: false, + ); + + // get account from api + final account = + await ref.read(paynymAPIProvider).nym(pCode.toString()); + + // update my account + if (account.value != null) { + ref.read(myPaynymAccountStateProvider.state).state = + account.value!; + } + } catch (e) { + Logging.instance.log( + "Failed pull down refresh of paynym home page: $e", + level: LogLevel.Warning, + ); + } + }, + ), + child: ListView.separated( + itemCount: max(count, 1), + separatorBuilder: (BuildContext context, int index) => Container( + height: 1.5, + color: Colors.transparent, + ), + itemBuilder: (BuildContext context, int index) { + if (count == 0) { + return RoundedWhiteContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Your PayNym contacts will appear here", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ) + : STextStyles.label(context), + ), + ], + ), + ); + } else if (count == 1) { + return RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: PaynymCardButton( + walletId: widget.walletId, + accountLite: following![0], + ), + ); + } else { + BorderRadius? borderRadius; + if (index == 0) { + borderRadius = _borderRadiusFirst; + } else if (index == count - 1) { + borderRadius = _borderRadiusLast; + } + + return Container( + key: Key("paynymCardKey_${following![index].nymId}"), + decoration: BoxDecoration( + borderRadius: borderRadius, + color: Theme.of(context).extension()!.popupBG, + ), + child: PaynymCardButton( + walletId: widget.walletId, + accountLite: following[index], + ), + ); + } + }, + ), + ); + } +} diff --git a/lib/pages/pinpad_views/create_pin_view.dart b/lib/pages/pinpad_views/create_pin_view.dart index 3180689c2..7ad3344ec 100644 --- a/lib/pages/pinpad_views/create_pin_view.dart +++ b/lib/pages/pinpad_views/create_pin_view.dart @@ -6,13 +6,12 @@ import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/home_view/home_view.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/biometrics.dart'; import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_pin_put/custom_pin_put.dart'; @@ -139,6 +138,8 @@ class _CreatePinViewState extends ConsumerState { .background, counterText: "", ), + isRandom: + ref.read(prefsChangeNotifierProvider).randomizePIN, submittedFieldDecoration: _pinPutDecoration.copyWith( color: Theme.of(context) .extension()! @@ -221,6 +222,8 @@ class _CreatePinViewState extends ConsumerState { ), selectedFieldDecoration: _pinPutDecoration, followingFieldDecoration: _pinPutDecoration, + isRandom: + ref.read(prefsChangeNotifierProvider).randomizePIN, onSubmit: (String pin) async { // _onSubmitCount++; // if (_onSubmitCount - _onSubmitFailCount > 1) return; diff --git a/lib/pages/pinpad_views/lock_screen_view.dart b/lib/pages/pinpad_views/lock_screen_view.dart index 06323bb94..802488fca 100644 --- a/lib/pages/pinpad_views/lock_screen_view.dart +++ b/lib/pages/pinpad_views/lock_screen_view.dart @@ -9,16 +9,18 @@ import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; // import 'package:stackwallet/providers/global/should_show_lockscreen_on_resume_state_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/biometrics.dart'; import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +import 'package:stackwallet/utilities/show_loading.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/custom_pin_put/custom_pin_put.dart'; import 'package:stackwallet/widgets/shake/shake.dart'; import 'package:tuple/tuple.dart'; @@ -36,6 +38,7 @@ class LockscreenView extends ConsumerStatefulWidget { this.routeOnSuccessArguments, this.biometrics = const Biometrics(), this.onSuccess, + this.customKeyLabel = "Button", }) : super(key: key); static const String routeName = "/lockscreen"; @@ -50,6 +53,7 @@ class LockscreenView extends ConsumerStatefulWidget { final String biometricsCancelButtonString; final Biometrics biometrics; final VoidCallback? onSuccess; + final String customKeyLabel; @override ConsumerState createState() => _LockscreenViewState(); @@ -79,19 +83,47 @@ class _LockscreenViewState extends ConsumerState { if (widget.popOnSuccess) { Navigator.of(context).pop(widget.routeOnSuccessArguments); } else { - unawaited(Navigator.of(context).pushReplacementNamed( - widget.routeOnSuccess, - arguments: widget.routeOnSuccessArguments, - )); - if (widget.routeOnSuccess == HomeView.routeName && - widget.routeOnSuccessArguments is String) { + final loadIntoWallet = widget.routeOnSuccess == HomeView.routeName && + widget.routeOnSuccessArguments is String; + + if (loadIntoWallet) { final walletId = widget.routeOnSuccessArguments as String; - unawaited(Navigator.of(context).pushNamed(WalletView.routeName, - arguments: Tuple2( + + final manager = + ref.read(walletsChangeNotifierProvider).getManager(walletId); + if (manager.coin == Coin.monero) { + await showLoading( + opaqueBG: true, + whileFuture: manager.initializeExisting(), + context: context, + message: "Loading ${manager.coin.prettyName} wallet...", + ); + } + } + + if (mounted) { + unawaited( + Navigator.of(context).pushReplacementNamed( + widget.routeOnSuccess, + arguments: widget.routeOnSuccessArguments, + ), + ); + + if (loadIntoWallet) { + final walletId = widget.routeOnSuccessArguments as String; + + unawaited( + Navigator.of(context).pushNamed( + WalletView.routeName, + arguments: Tuple2( walletId, ref .read(walletsChangeNotifierProvider) - .getManagerProvider(walletId)))); + .getManagerProvider(walletId), + ), + ), + ); + } } } } @@ -128,6 +160,14 @@ class _LockscreenViewState extends ConsumerState { } } + @override + void didChangeDependencies() { + if (widget.isInitialAppLogin) { + // unawaited(Assets.precache(context)); + } + super.didChangeDependencies(); + } + @override void initState() { _shakeController = ShakeController(); @@ -164,27 +204,56 @@ class _LockscreenViewState extends ConsumerState { late Biometrics biometrics; Widget get _body => Background( - child: Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: widget.showBackButton - ? AppBarBackButton( - onPressed: () async { - if (FocusScope.of(context).hasFocus) { - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 70)); - } - if (mounted) { - Navigator.of(context).pop(); - } - }, - ) - : Container(), - ), - body: SafeArea( - child: Column( + child: SafeArea( + child: Scaffold( + extendBodyBehindAppBar: true, + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: widget.showBackButton + ? AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 70)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ) + : Container(), + actions: [ + // check prefs and hide if user has biometrics toggle off? + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only( + right: 16.0, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + if (ref + .read(prefsChangeNotifierProvider) + .useBiometrics == + true) + CustomTextButton( + text: "Use biometrics", + onTap: () async { + await _checkUseBiometrics(); + }, + ), + ], + ), + ), + ], + ), + ], + ), + body: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Shake( @@ -205,6 +274,12 @@ class _LockscreenViewState extends ConsumerState { height: 52, ), CustomPinPut( + // customKey: CustomKey( + // onPressed: _checkUseBiometrics, + // iconAssetName: Platform.isIOS + // ? Assets.svg.faceId + // : Assets.svg.fingerprint, + // ), fieldsCount: Constants.pinLength, eachFieldHeight: 12, eachFieldWidth: 12, @@ -240,6 +315,9 @@ class _LockscreenViewState extends ConsumerState { ), selectedFieldDecoration: _pinPutDecoration, followingFieldDecoration: _pinPutDecoration, + isRandom: ref + .read(prefsChangeNotifierProvider) + .randomizePIN, onSubmit: (String pin) async { _attempts++; diff --git a/lib/pages/receive_view/addresses/address_card.dart b/lib/pages/receive_view/addresses/address_card.dart new file mode 100644 index 000000000..6a8abd78d --- /dev/null +++ b/lib/pages/receive_view/addresses/address_card.dart @@ -0,0 +1,152 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/pages/receive_view/addresses/address_tag.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/utilities/clipboard_interface.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class AddressCard extends ConsumerStatefulWidget { + const AddressCard({ + Key? key, + required this.addressId, + required this.walletId, + required this.coin, + this.onPressed, + this.clipboard = const ClipboardWrapper(), + }) : super(key: key); + + final int addressId; + final String walletId; + final Coin coin; + final ClipboardInterface clipboard; + final VoidCallback? onPressed; + + @override + ConsumerState createState() => _AddressCardState(); +} + +class _AddressCardState extends ConsumerState { + final isDesktop = Util.isDesktop; + + late Stream stream; + late final Address address; + + AddressLabel? label; + + @override + void initState() { + address = MainDB.instance.isar.addresses + .where() + .idEqualTo(widget.addressId) + .findFirstSync()!; + + label = MainDB.instance.getAddressLabelSync(widget.walletId, address.value); + Id? id = label?.id; + if (id == null) { + label = AddressLabel( + walletId: widget.walletId, + addressString: address.value, + value: "", + tags: address.subType == AddressSubType.receiving + ? ["receiving"] + : address.subType == AddressSubType.change + ? ["change"] + : null, + ); + id = MainDB.instance.putAddressLabelSync(label!); + } + stream = MainDB.instance.watchAddressLabel(id: id); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return RoundedWhiteContainer( + onPressed: widget.onPressed, + child: StreamBuilder( + stream: stream, + builder: (context, snapshot) { + if (snapshot.hasData) { + label = snapshot.data!; + } + + return ConditionalParent( + condition: isDesktop, + builder: (child) => Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SvgPicture.file( + File( + ref.watch( + coinIconProvider(widget.coin), + ), + ), + width: 32, + height: 32, + ), + const SizedBox( + width: 12, + ), + Expanded( + child: child, + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (label!.value.isNotEmpty) + Text( + label!.value, + style: STextStyles.itemSubtitle(context), + textAlign: TextAlign.left, + ), + if (label!.value.isNotEmpty) + SizedBox( + height: isDesktop ? 2 : 8, + ), + Row( + children: [ + Expanded( + child: Text( + address.value, + style: STextStyles.itemSubtitle12(context), + ), + ), + ], + ), + const SizedBox( + height: 10, + ), + if (label!.tags != null && label!.tags!.isNotEmpty) + Wrap( + spacing: 10, + runSpacing: 10, + children: label!.tags! + .map( + (e) => AddressTag( + tag: e, + ), + ) + .toList(), + ), + ], + ), + ); + }, + ), + ); + } +} diff --git a/lib/pages/receive_view/addresses/address_details_view.dart b/lib/pages/receive_view/addresses/address_details_view.dart new file mode 100644 index 000000000..1a98c989d --- /dev/null +++ b/lib/pages/receive_view/addresses/address_details_view.dart @@ -0,0 +1,579 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:isar/isar.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/pages/receive_view/addresses/address_tag.dart'; +import 'package:stackwallet/pages/wallet_view/sub_widgets/no_transactions_found.dart'; +import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/address_utils.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; +import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart'; +import 'package:stackwallet/widgets/custom_buttons/simple_edit_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/transaction_card.dart'; + +class AddressDetailsView extends ConsumerStatefulWidget { + const AddressDetailsView({ + Key? key, + required this.addressId, + required this.walletId, + }) : super(key: key); + + static const String routeName = "/addressDetailsView"; + + final Id addressId; + final String walletId; + + @override + ConsumerState createState() => _AddressDetailsViewState(); +} + +class _AddressDetailsViewState extends ConsumerState { + final _qrKey = GlobalKey(); + final isDesktop = Util.isDesktop; + + late Stream stream; + late final Address address; + + AddressLabel? label; + + void _showDesktopAddressQrCode() { + showDialog( + context: context, + builder: (context) => DesktopDialog( + maxWidth: 480, + maxHeight: 400, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Address QR code", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Center( + child: RepaintBoundary( + key: _qrKey, + child: QrImage( + data: AddressUtils.buildUriString( + ref.watch(walletsChangeNotifierProvider.select( + (value) => + value.getManager(widget.walletId).coin)), + address.value, + {}, + ), + size: 220, + backgroundColor: + Theme.of(context).extension()!.popupBG, + foregroundColor: Theme.of(context) + .extension()! + .accentColorDark, + ), + ), + ), + ], + ), + ), + const SizedBox( + height: 32, + ), + ], + ), + ), + ); + } + + @override + void initState() { + address = MainDB.instance.isar.addresses + .where() + .idEqualTo(widget.addressId) + .findFirstSync()!; + + label = MainDB.instance.getAddressLabelSync(widget.walletId, address.value); + Id? id = label?.id; + if (id == null) { + label = AddressLabel( + walletId: widget.walletId, + addressString: address.value, + value: "", + tags: address.subType == AddressSubType.receiving + ? ["receiving"] + : address.subType == AddressSubType.change + ? ["change"] + : null, + ); + id = MainDB.instance.putAddressLabelSync(label!); + } + stream = MainDB.instance.watchAddressLabel(id: id); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return ConditionalParent( + condition: !isDesktop, + builder: (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension()!.backgroundAppBar, + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + titleSpacing: 0, + title: Text( + "Address details", + style: STextStyles.navBarTitle(context), + ), + ), + body: SafeArea( + child: LayoutBuilder( + builder: (builderContext, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: child, + ), + ), + ); + }, + ), + ), + ), + ), + child: StreamBuilder( + stream: stream, + builder: (context, snapshot) { + if (snapshot.hasData) { + label = snapshot.data!; + } + + return ConditionalParent( + condition: isDesktop, + builder: (child) { + return Column( + children: [ + const SizedBox( + height: 20, + ), + RoundedWhiteContainer( + padding: const EdgeInsets.all(24), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Address details", + style: STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + CustomTextButton( + text: "View QR code", + onTap: _showDesktopAddressQrCode, + ), + ], + ), + const SizedBox( + height: 4, + ), + RoundedWhiteContainer( + padding: EdgeInsets.zero, + borderColor: Theme.of(context) + .extension()! + .backgroundAppBar, + child: child, + ), + const SizedBox( + height: 16, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Transaction history", + style: STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ], + ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + padding: EdgeInsets.zero, + borderColor: Theme.of(context) + .extension()! + .backgroundAppBar, + child: _AddressDetailsTxList( + walletId: widget.walletId, + address: address, + ), + ), + ], + ), + ), + ], + ); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (!isDesktop) + Center( + child: RepaintBoundary( + key: _qrKey, + child: QrImage( + data: AddressUtils.buildUriString( + ref.watch(walletsChangeNotifierProvider.select( + (value) => + value.getManager(widget.walletId).coin)), + address.value, + {}, + ), + size: 220, + backgroundColor: Theme.of(context) + .extension()! + .background, + foregroundColor: Theme.of(context) + .extension()! + .accentColorDark, + ), + ), + ), + if (!isDesktop) + const SizedBox( + height: 16, + ), + _Item( + title: "Address", + data: address.value, + button: isDesktop + ? IconCopyButton( + data: address.value, + ) + : SimpleCopyButton( + data: address.value, + ), + ), + const _Div( + height: 12, + ), + _Item( + title: "Label", + data: label!.value, + button: SimpleEditButton( + editValue: label!.value, + editLabel: 'label', + onValueChanged: (value) { + MainDB.instance.putAddressLabel( + label!.copyWith( + label: value, + ), + ); + }, + ), + ), + const _Div( + height: 12, + ), + _Tags( + tags: label!.tags, + ), + if (address.derivationPath != null) + const _Div( + height: 12, + ), + if (address.derivationPath != null) + _Item( + title: "Derivation path", + data: address.derivationPath!.value, + button: Container(), + ), + const _Div( + height: 12, + ), + _Item( + title: "Type", + data: address.type.readableName, + button: Container(), + ), + const _Div( + height: 12, + ), + _Item( + title: "Sub type", + data: address.subType.prettyName, + button: Container(), + ), + if (!isDesktop) + const SizedBox( + height: 20, + ), + if (!isDesktop) + Text( + "Transactions", + textAlign: TextAlign.left, + style: STextStyles.itemSubtitle(context).copyWith( + color: + Theme.of(context).extension()!.textDark3, + ), + ), + if (!isDesktop) + const SizedBox( + height: 12, + ), + if (!isDesktop) + _AddressDetailsTxList( + walletId: widget.walletId, + address: address, + ), + ], + ), + ); + }, + ), + ); + } +} + +class _AddressDetailsTxList extends StatelessWidget { + const _AddressDetailsTxList({ + Key? key, + required this.walletId, + required this.address, + }) : super(key: key); + + final String walletId; + final Address address; + + @override + Widget build(BuildContext context) { + final query = MainDB.instance + .getTransactions(walletId) + .filter() + .address((q) => q.valueEqualTo(address.value)); + + final count = query.countSync(); + + if (count > 0) { + if (Util.isDesktop) { + final txns = query.findAllSync(); + return ListView.separated( + shrinkWrap: true, + primary: false, + itemBuilder: (_, index) => TransactionCard( + transaction: txns[index], + walletId: walletId, + ), + separatorBuilder: (_, __) => const _Div(height: 1), + itemCount: count, + ); + } else { + return RoundedWhiteContainer( + padding: EdgeInsets.zero, + child: Column( + mainAxisSize: MainAxisSize.min, + children: query + .findAllSync() + .map( + (e) => TransactionCard( + transaction: e, + walletId: walletId, + ), + ) + .toList(), + ), + ); + } + } else { + return const NoTransActionsFound(); + } + } +} + +class _Div extends StatelessWidget { + const _Div({ + Key? key, + required this.height, + }) : super(key: key); + + final double height; + + @override + Widget build(BuildContext context) { + if (Util.isDesktop) { + return Container( + color: Theme.of(context).extension()!.backgroundAppBar, + height: 1, + width: double.infinity, + ); + } else { + return SizedBox( + height: height, + ); + } + } +} + +class _Tags extends StatelessWidget { + const _Tags({ + Key? key, + required this.tags, + }) : super(key: key); + + final List? tags; + + @override + Widget build(BuildContext context) { + return RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Tags", + style: STextStyles.itemSubtitle(context), + ), + Container(), + // SimpleEditButton( + // onPressedOverride: () { + // // TODO edit tags + // }, + // ), + ], + ), + const SizedBox( + height: 8, + ), + tags != null && tags!.isNotEmpty + ? Wrap( + spacing: 10, + runSpacing: 10, + children: tags! + .map( + (e) => AddressTag( + tag: e, + ), + ) + .toList(), + ) + : Text( + "Tags will appear here", + style: STextStyles.w500_14(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle3, + ), + ), + ], + ), + ); + } +} + +class _Item extends StatelessWidget { + const _Item({ + Key? key, + required this.title, + required this.data, + required this.button, + }) : super(key: key); + + final String title; + final String data; + final Widget button; + + @override + Widget build(BuildContext context) { + return ConditionalParent( + condition: !Util.isDesktop, + builder: (child) => RoundedWhiteContainer( + child: child, + ), + child: ConditionalParent( + condition: Util.isDesktop, + builder: (child) => Padding( + padding: const EdgeInsets.all(16), + child: child, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: STextStyles.itemSubtitle(context), + ), + button, + ], + ), + const SizedBox( + height: 5, + ), + data.isNotEmpty + ? SelectableText( + data, + style: STextStyles.w500_14(context), + ) + : Text( + "$title will appear here", + style: STextStyles.w500_14(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle3, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/receive_view/addresses/address_qr_popup.dart b/lib/pages/receive_view/addresses/address_qr_popup.dart new file mode 100644 index 000000000..65f0e7977 --- /dev/null +++ b/lib/pages/receive_view/addresses/address_qr_popup.dart @@ -0,0 +1,192 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:ui' as ui; + +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:share_plus/share_plus.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/address_utils.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/clipboard_interface.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; + +class AddressQrPopup extends StatefulWidget { + const AddressQrPopup({ + Key? key, + required this.addressString, + required this.coin, + this.clipboard = const ClipboardWrapper(), + }) : super(key: key); + + final String addressString; + final Coin coin; + final ClipboardInterface clipboard; + + @override + State createState() => _AddressQrPopupState(); +} + +class _AddressQrPopupState extends State { + final _qrKey = GlobalKey(); + final isDesktop = Util.isDesktop; + + Future _capturePng(bool shouldSaveInsteadOfShare) async { + try { + RenderRepaintBoundary boundary = + _qrKey.currentContext?.findRenderObject() as RenderRepaintBoundary; + ui.Image image = await boundary.toImage(); + ByteData? byteData = + await image.toByteData(format: ui.ImageByteFormat.png); + Uint8List pngBytes = byteData!.buffer.asUint8List(); + + if (shouldSaveInsteadOfShare) { + if (isDesktop) { + final dir = Directory("${Platform.environment['HOME']}"); + if (!dir.existsSync()) { + throw Exception( + "Home dir not found while trying to open filepicker on QR image save"); + } + final path = await FilePicker.platform.saveFile( + fileName: "qrcode.png", + initialDirectory: dir.path, + ); + + if (path != null) { + final file = File(path); + if (file.existsSync()) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "$path already exists!", + context: context, + ), + ); + } else { + await file.writeAsBytes(pngBytes); + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "$path saved!", + context: context, + ), + ); + } + } + } else { + // await DocumentFileSavePlus.saveFile( + // pngBytes, + // "receive_qr_code_${DateTime.now().toLocal().toIso8601String()}.png", + // "image/png"); + } + } else { + final tempDir = await getTemporaryDirectory(); + final file = await File("${tempDir.path}/qrcode.png").create(); + await file.writeAsBytes(pngBytes); + + await Share.shareFiles(["${tempDir.path}/qrcode.png"], + text: "Receive URI QR Code"); + } + } catch (e) { + //todo: comeback to this + debugPrint(e.toString()); + } + } + + @override + Widget build(BuildContext context) { + return StackDialogBase( + child: Column( + children: [ + Text( + "todo: custom label", + style: STextStyles.pageTitleH2(context), + ), + const SizedBox( + height: 8, + ), + Text( + widget.addressString, + style: STextStyles.itemSubtitle(context), + ), + const SizedBox( + height: 16, + ), + Center( + child: RepaintBoundary( + key: _qrKey, + child: QrImage( + data: AddressUtils.buildUriString( + widget.coin, + widget.addressString, + {}, + ), + size: 220, + backgroundColor: + Theme.of(context).extension()!.popupBG, + foregroundColor: + Theme.of(context).extension()!.accentColorDark, + ), + ), + ), + const SizedBox( + height: 16, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + width: 170, + buttonHeight: isDesktop ? ButtonHeight.l : null, + onPressed: () async { + await _capturePng(false); + }, + label: "Share", + icon: SvgPicture.asset( + Assets.svg.share, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .buttonTextSecondary, + ), + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + width: 170, + onPressed: () async { + await _capturePng(true); + }, + label: "Save", + icon: SvgPicture.asset( + Assets.svg.arrowDown, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .buttonTextPrimary, + ), + ), + ), + ], + ) + ], + ), + ); + } +} diff --git a/lib/pages/receive_view/addresses/address_tag.dart b/lib/pages/receive_view/addresses/address_tag.dart new file mode 100644 index 000000000..0c78a2f2e --- /dev/null +++ b/lib/pages/receive_view/addresses/address_tag.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_native_splash/cli_commands.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; + +class AddressTag extends StatelessWidget { + const AddressTag({Key? key, required this.tag}) : super(key: key); + + final String tag; + + @override + Widget build(BuildContext context) { + return RoundedContainer( + radiusMultiplier: 0.5, + padding: const EdgeInsets.symmetric( + vertical: 5, + horizontal: 7, + ), + color: Theme.of(context).extension()!.buttonBackPrimary, + child: Text( + tag.capitalize(), + style: STextStyles.w500_14(context).copyWith( + color: Theme.of(context).extension()!.buttonTextPrimary, + ), + ), + ); + } +} diff --git a/lib/pages/receive_view/addresses/edit_address_label_view.dart b/lib/pages/receive_view/addresses/edit_address_label_view.dart new file mode 100644 index 000000000..53ddada96 --- /dev/null +++ b/lib/pages/receive_view/addresses/edit_address_label_view.dart @@ -0,0 +1,241 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/isar/models/address_label.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; + +class EditAddressLabelView extends ConsumerStatefulWidget { + const EditAddressLabelView({ + Key? key, + required this.addressLabelId, + }) : super(key: key); + + static const String routeName = "/editAddressLabel"; + + final int addressLabelId; + + @override + ConsumerState createState() => + _EditAddressLabelViewState(); +} + +class _EditAddressLabelViewState extends ConsumerState { + late final TextEditingController _labelFieldController; + final labelFieldFocusNode = FocusNode(); + + late final bool isDesktop; + + late AddressLabel addressLabel; + + @override + void initState() { + isDesktop = Util.isDesktop; + _labelFieldController = TextEditingController(); + addressLabel = MainDB.instance.isar.addressLabels + .where() + .idEqualTo(widget.addressLabelId) + .findFirstSync()!; + _labelFieldController.text = addressLabel.value; + super.initState(); + } + + @override + void dispose() { + _labelFieldController.dispose(); + labelFieldFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ConditionalParent( + condition: !isDesktop, + builder: (child) => Background( + child: child, + ), + child: Scaffold( + backgroundColor: isDesktop + ? Colors.transparent + : Theme.of(context).extension()!.background, + appBar: isDesktop + ? null + : AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Edit label", + style: STextStyles.navBarTitle(context), + ), + ), + body: ConditionalParent( + condition: !isDesktop, + builder: (child) => Padding( + padding: const EdgeInsets.all(12), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.all(4), + child: child, + ), + ), + ), + ); + }, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (isDesktop) + Padding( + padding: const EdgeInsets.only( + left: 32, + bottom: 12, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Edit label", + style: STextStyles.desktopH3(context), + ), + const DesktopDialogCloseButton(), + ], + ), + ), + Padding( + padding: isDesktop + ? const EdgeInsets.symmetric( + horizontal: 32, + ) + : const EdgeInsets.all(0), + child: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: _labelFieldController, + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + focusNode: labelFieldFocusNode, + decoration: standardInputDecoration( + "Address label", + labelFieldFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + contentPadding: isDesktop + ? const EdgeInsets.only( + left: 16, + top: 11, + bottom: 12, + right: 5, + ) + : null, + suffixIcon: _labelFieldController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _labelFieldController.text = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), + ), + // if (!isDesktop) + const Spacer(), + if (isDesktop) + Padding( + padding: const EdgeInsets.all(32), + child: PrimaryButton( + label: "Save", + onPressed: () async { + await MainDB.instance.updateAddressLabel( + addressLabel.copyWith( + label: _labelFieldController.text, + ), + ); + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + ), + if (!isDesktop) + TextButton( + onPressed: () async { + await MainDB.instance.updateAddressLabel( + addressLabel.copyWith( + label: _labelFieldController.text, + ), + ); + if (mounted) { + Navigator.of(context).pop(); + } + }, + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonStyle(context), + child: Text( + "Save", + style: STextStyles.button(context), + ), + ) + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/receive_view/addresses/wallet_addresses_view.dart b/lib/pages/receive_view/addresses/wallet_addresses_view.dart new file mode 100644 index 000000000..f90221bcf --- /dev/null +++ b/lib/pages/receive_view/addresses/wallet_addresses_view.dart @@ -0,0 +1,268 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/pages/receive_view/addresses/address_card.dart'; +import 'package:stackwallet/pages/receive_view/addresses/address_details_view.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/loading_indicator.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:tuple/tuple.dart'; + +import '../../../utilities/assets.dart'; +import '../../../widgets/icon_widgets/x_icon.dart'; +import '../../../widgets/textfield_icon_button.dart'; + +class WalletAddressesView extends ConsumerStatefulWidget { + const WalletAddressesView({ + Key? key, + required this.walletId, + }) : super(key: key); + + static const String routeName = "/walletAddressesView"; + + final String walletId; + + @override + ConsumerState createState() => + _WalletAddressesViewState(); +} + +class _WalletAddressesViewState extends ConsumerState { + final bool isDesktop = Util.isDesktop; + + String _searchString = ""; + + late final TextEditingController _searchController; + final searchFieldFocusNode = FocusNode(); + + Future> _search(String term) async { + if (term.isEmpty) { + return MainDB.instance + .getAddresses(widget.walletId) + .filter() + .group((q) => q + .subTypeEqualTo(AddressSubType.change) + .or() + .subTypeEqualTo(AddressSubType.receiving) + .or() + .subTypeEqualTo(AddressSubType.paynymReceive) + .or() + .subTypeEqualTo(AddressSubType.paynymNotification)) + .and() + .not() + .typeEqualTo(AddressType.nonWallet) + .sortByDerivationIndex() + .idProperty() + .findAll(); + } + + final labels = await MainDB.instance + .getAddressLabels(widget.walletId) + .filter() + .group( + (q) => q + .valueContains(term, caseSensitive: false) + .or() + .addressStringContains(term, caseSensitive: false) + .or() + .group( + (q) => q + .tagsIsNotNull() + .and() + .tagsElementContains(term, caseSensitive: false), + ), + ) + .findAll(); + + if (labels.isEmpty) { + return []; + } + + return MainDB.instance + .getAddresses(widget.walletId) + .filter() + .anyOf( + labels, (q, e) => q.valueEqualTo(e.addressString)) + .group((q) => q + .subTypeEqualTo(AddressSubType.change) + .or() + .subTypeEqualTo(AddressSubType.receiving) + .or() + .subTypeEqualTo(AddressSubType.paynymReceive) + .or() + .subTypeEqualTo(AddressSubType.paynymNotification)) + .and() + .not() + .typeEqualTo(AddressType.nonWallet) + .sortByDerivationIndex() + .idProperty() + .findAll(); + } + + @override + void initState() { + _searchController = TextEditingController(); + + super.initState(); + } + + @override + void dispose() { + _searchController.dispose(); + searchFieldFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final coin = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(widget.walletId).coin)); + return ConditionalParent( + condition: !isDesktop, + builder: (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension()!.backgroundAppBar, + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + titleSpacing: 0, + title: Text( + "Wallet addresses", + style: STextStyles.navBarTitle(context), + ), + ), + body: Padding( + padding: const EdgeInsets.all(16), + child: child, + ), + ), + ), + child: Column( + children: [ + SizedBox( + width: isDesktop ? 490 : null, + child: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: !isDesktop, + enableSuggestions: !isDesktop, + controller: _searchController, + focusNode: searchFieldFocusNode, + onChanged: (value) { + setState(() { + _searchString = value; + }); + }, + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + decoration: standardInputDecoration( + "Search...", + searchFieldFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + prefixIcon: Padding( + padding: EdgeInsets.symmetric( + horizontal: isDesktop ? 12 : 10, + vertical: isDesktop ? 18 : 16, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: isDesktop ? 20 : 16, + height: isDesktop ? 20 : 16, + ), + ), + suffixIcon: _searchController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchController.text = ""; + _searchString = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), + ), + SizedBox( + height: isDesktop ? 20 : 16, + ), + Expanded( + child: FutureBuilder( + future: _search(_searchString), + builder: (context, AsyncSnapshot> snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.data != null) { + // listview + return ListView.separated( + itemCount: snapshot.data!.length, + separatorBuilder: (_, __) => Container( + height: 10, + ), + itemBuilder: (_, index) => AddressCard( + walletId: widget.walletId, + addressId: snapshot.data![index], + coin: coin, + onPressed: () { + Navigator.of(context).pushNamed( + AddressDetailsView.routeName, + arguments: Tuple2( + snapshot.data![index], + widget.walletId, + ), + ); + }, + ), + ); + } else { + return const Center( + child: LoadingIndicator( + height: 200, + width: 200, + ), + ); + } + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart index 27bc7b151..bc1281156 100644 --- a/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart +++ b/lib/pages/receive_view/generate_receiving_uri_qr_code_view.dart @@ -13,15 +13,14 @@ import 'package:path_provider/path_provider.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:share_plus/share_plus.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/address_utils.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; @@ -218,46 +217,19 @@ class _GenerateUriQrCodeViewState extends State { Center( child: SizedBox( width: width, - child: TextButton( - onPressed: () async { - // TODO: add save button as well - await _capturePng(true); - }, - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Center( - child: SvgPicture.asset( - Assets.svg.share, - width: 14, - height: 14, - ), - ), - const SizedBox( - width: 4, - ), - Column( - children: [ - Text( - "Share", - textAlign: TextAlign.center, - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .buttonTextSecondary, - ), - ), - const SizedBox( - height: 2, - ), - ], - ), - ], + child: SecondaryButton( + label: "Share", + icon: SvgPicture.asset( + Assets.svg.share, + width: 14, + height: 14, + color: Theme.of(context) + .extension()! + .buttonTextSecondary, ), + onPressed: () async { + await _capturePng(false); + }, ), ), ), @@ -409,8 +381,9 @@ class _GenerateUriQrCodeViewState extends State { height: 1.8, ) : STextStyles.field(context), - keyboardType: - const TextInputType.numberWithOptions(decimal: true), + keyboardType: Util.isDesktop + ? null + : const TextInputType.numberWithOptions(decimal: true), onChanged: (_) => setState(() {}), decoration: standardInputDecoration( "Amount", diff --git a/lib/pages/receive_view/receive_view.dart b/lib/pages/receive_view/receive_view.dart index 1ba00f8ee..bd2f6b70e 100644 --- a/lib/pages/receive_view/receive_view.dart +++ b/lib/pages/receive_view/receive_view.dart @@ -5,15 +5,18 @@ import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:qr_flutter/qr_flutter.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages/receive_view/addresses/wallet_addresses_view.dart'; import 'package:stackwallet/pages/receive_view/generate_receiving_uri_qr_code_view.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/route_generator.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; +import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; @@ -23,15 +26,15 @@ import 'package:stackwallet/widgets/rounded_white_container.dart'; class ReceiveView extends ConsumerStatefulWidget { const ReceiveView({ Key? key, - required this.coin, required this.walletId, + this.tokenContract, this.clipboard = const ClipboardWrapper(), }) : super(key: key); static const String routeName = "/receiveView"; - final Coin coin; final String walletId; + final EthContract? tokenContract; final ClipboardInterface clipboard; @override @@ -84,7 +87,7 @@ class _ReceiveViewState extends ConsumerState { @override void initState() { walletId = widget.walletId; - coin = widget.coin; + coin = ref.read(walletsChangeNotifierProvider).getManager(walletId).coin; clipboard = widget.clipboard; WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { @@ -115,6 +118,8 @@ class _ReceiveViewState extends ConsumerState { } }); + final ticker = widget.tokenContract?.symbol ?? coin.ticker; + return Background( child: Scaffold( backgroundColor: Theme.of(context).extension()!.background, @@ -125,9 +130,99 @@ class _ReceiveViewState extends ConsumerState { }, ), title: Text( - "Receive ${coin.ticker}", + "Receive $ticker", style: STextStyles.navBarTitle(context), ), + actions: [ + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + semanticsLabel: + "Address List Pop-up Button. Opens A Pop-up For Address List Button.", + key: const Key("walletNetworkSettingsAddNewNodeViewButton"), + size: 36, + shadows: const [], + color: Theme.of(context).extension()!.background, + icon: SvgPicture.asset( + Assets.svg.verticalEllipsis, + color: Theme.of(context) + .extension()! + .accentColorDark, + width: 20, + height: 20, + ), + onPressed: () { + showDialog( + barrierColor: Colors.transparent, + barrierDismissible: true, + context: context, + builder: (_) { + return Stack( + children: [ + Positioned( + top: 9, + right: 10, + child: Container( + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .popupBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + // boxShadow: [CFColors.standardBoxShadow], + boxShadow: const [], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GestureDetector( + onTap: () { + Navigator.of(context).pop(); + Navigator.of(context).pushNamed( + WalletAddressesView.routeName, + arguments: walletId, + ); + }, + child: RoundedWhiteContainer( + boxShadow: [ + Theme.of(context) + .extension()! + .standardBoxShadow, + ], + child: Material( + color: Colors.transparent, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + ), + child: Text( + "Address list", + style: STextStyles.field(context), + ), + ), + ), + ), + ), + ], + ), + ), + ), + ], + ); + }, + ); + }, + ), + ), + ), + ], ), body: Padding( padding: const EdgeInsets.all(12), @@ -139,6 +234,7 @@ class _ReceiveViewState extends ConsumerState { children: [ GestureDetector( onTap: () { + HapticFeedback.lightImpact(); clipboard.setData( ClipboardData(text: receivingAddress), ); @@ -155,7 +251,7 @@ class _ReceiveViewState extends ConsumerState { Row( children: [ Text( - "Your ${coin.ticker} address", + "Your $ticker address", style: STextStyles.itemSubtitle(context), ), const Spacer(), @@ -197,16 +293,16 @@ class _ReceiveViewState extends ConsumerState { ), ), ), - if (coin != Coin.epicCash) + if (coin != Coin.epicCash && coin != Coin.ethereum) const SizedBox( height: 12, ), - if (coin != Coin.epicCash) + if (coin != Coin.epicCash && coin != Coin.ethereum) TextButton( onPressed: generateNewAddress, style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), child: Text( "Generate new address", style: STextStyles.button(context).copyWith( @@ -233,7 +329,7 @@ class _ReceiveViewState extends ConsumerState { const SizedBox( height: 20, ), - BlueTextButton( + CustomTextButton( text: "Create new QR code", onTap: () async { unawaited(Navigator.of(context).push( diff --git a/lib/pages/send_view/confirm_transaction_view.dart b/lib/pages/send_view/confirm_transaction_view.dart index b7e2181a1..e07064d39 100644 --- a/lib/pages/send_view/confirm_transaction_view.dart +++ b/lib/pages/send_view/confirm_transaction_view.dart @@ -1,25 +1,30 @@ import 'dart:async'; +import 'dart:io'; import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/paynym/paynym_account_lite.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/sending_transaction_dialog.dart'; +import 'package:stackwallet/pages/token_view/token_view.dart'; import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; +import 'package:stackwallet/pages_desktop_specific/coin_control/desktop_coin_control_use_dialog.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; -import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; @@ -41,6 +46,10 @@ class ConfirmTransactionView extends ConsumerStatefulWidget { required this.walletId, this.routeOnSuccessName = WalletView.routeName, this.isTradeTransaction = false, + this.isPaynymTransaction = false, + this.isPaynymNotificationTransaction = false, + this.isTokenTx = false, + this.onSuccessInsteadOfRouteOnSuccess, }) : super(key: key); static const String routeName = "/confirmTransactionView"; @@ -49,6 +58,10 @@ class ConfirmTransactionView extends ConsumerStatefulWidget { final String walletId; final String routeOnSuccessName; final bool isTradeTransaction; + final bool isPaynymTransaction; + final bool isPaynymNotificationTransaction; + final bool isTokenTx; + final VoidCallback? onSuccessInsteadOfRouteOnSuccess; @override ConsumerState createState() => @@ -66,43 +79,88 @@ class _ConfirmTransactionViewState late final TextEditingController noteController; Future _attemptSend(BuildContext context) async { + final manager = + ref.read(walletsChangeNotifierProvider).getManager(walletId); + + final sendProgressController = ProgressAndSuccessController(); + unawaited( showDialog( context: context, useSafeArea: false, barrierDismissible: false, builder: (context) { - return const SendingTransactionDialog(); + return SendingTransactionDialog( + coin: manager.coin, + controller: sendProgressController, + ); }, ), ); + final time = Future.delayed( + const Duration( + milliseconds: 2500, + ), + ); + + late String txid; + Future txidFuture; + final note = noteController.text; - final manager = - ref.read(walletsChangeNotifierProvider).getManager(walletId); try { - String txid; - final coin = manager.coin; - if ((coin == Coin.firo || coin == Coin.firoTestNet) && - ref.read(publicPrivateBalanceStateProvider.state).state != - "Private") { - txid = await (manager.wallet as FiroWallet) - .confirmSendPublic(txData: transactionInfo); + if (widget.isTokenTx) { + txidFuture = ref + .read(tokenServiceProvider)! + .confirmSend(txData: transactionInfo); + } else if (widget.isPaynymNotificationTransaction) { + txidFuture = (manager.wallet as PaynymWalletInterface) + .broadcastNotificationTx(preparedTx: transactionInfo); + } else if (widget.isPaynymTransaction) { + txidFuture = manager.confirmSend(txData: transactionInfo); } else { - txid = await manager.confirmSend(txData: transactionInfo); + final coin = manager.coin; + if ((coin == Coin.firo || coin == Coin.firoTestNet) && + ref.read(publicPrivateBalanceStateProvider.state).state != + "Private") { + txidFuture = (manager.wallet as FiroWallet) + .confirmSendPublic(txData: transactionInfo); + } else { + txidFuture = manager.confirmSend(txData: transactionInfo); + } } + final results = await Future.wait([ + txidFuture, + time, + ]); + + sendProgressController.triggerSuccess?.call(); + await Future.delayed(const Duration(seconds: 5)); + + txid = results.first as String; + ref.refresh(desktopUseUTXOs); + // save note await ref .read(notesServiceChangeNotifierProvider(walletId)) .editOrAddNote(txid: txid, note: note); - unawaited(manager.refresh()); + if (widget.isTokenTx) { + unawaited(ref.read(tokenServiceProvider)!.refresh()); + } else { + unawaited(manager.refresh()); + } // pop back to wallet if (mounted) { - Navigator.of(context).popUntil(ModalRoute.withName(routeOnSuccessName)); + if (widget.onSuccessInsteadOfRouteOnSuccess == null) { + Navigator.of(context) + .popUntil(ModalRoute.withName(routeOnSuccessName)); + } else { + widget.onSuccessInsteadOfRouteOnSuccess!.call(); + } } } on BadEpicHttpAddressException catch (_) { if (mounted) { @@ -175,7 +233,7 @@ class _ConfirmTransactionViewState rightButton: TextButton( style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), child: Text( "Ok", style: STextStyles.button(context).copyWith( @@ -219,6 +277,15 @@ class _ConfirmTransactionViewState final managerProvider = ref.watch(walletsChangeNotifierProvider .select((value) => value.getManagerProvider(walletId))); + final String unit; + if (widget.isTokenTx) { + unit = ref.watch( + tokenServiceProvider.select((value) => value!.tokenContract.symbol)); + } else { + unit = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(walletId).coin.ticker)); + } + return ConditionalParent( condition: !isDesktop, builder: (child) => Background( @@ -285,7 +352,7 @@ class _ConfirmTransactionViewState ).pop(), ), Text( - "Confirm ${ref.watch(managerProvider.select((value) => value.coin.ticker.toUpperCase()))} transaction", + "Confirm $unit transaction", style: STextStyles.desktopH3(context), ), ], @@ -302,7 +369,7 @@ class _ConfirmTransactionViewState crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( - "Send ${ref.watch(managerProvider.select((value) => value.coin)).ticker}", + "Send $unit", style: STextStyles.pageTitleH1(context), ), const SizedBox( @@ -313,14 +380,20 @@ class _ConfirmTransactionViewState crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( - "Recipient", + widget.isPaynymTransaction + ? "PayNym recipient" + : "Recipient", style: STextStyles.smallMed12(context), ), const SizedBox( height: 4, ), Text( - "${transactionInfo["address"] ?? "ERROR"}", + widget.isPaynymTransaction + ? (transactionInfo["paynymAccountLite"] + as PaynymAccountLite) + .nymName + : "${transactionInfo["address"] ?? "ERROR"}", style: STextStyles.itemSubtitle12(context), ), ], @@ -338,14 +411,12 @@ class _ConfirmTransactionViewState style: STextStyles.smallMed12(context), ), Text( - "${Format.satoshiAmountToPrettyString(transactionInfo["recipientAmt"] as int, ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), - ), ref.watch( - managerProvider.select((value) => value.coin), - ))} ${ref.watch( - managerProvider.select((value) => value.coin), - ).ticker}", + "${(transactionInfo["recipientAmt"] as Amount).localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider + .select((value) => value.locale), + ), + )} $unit", style: STextStyles.itemSubtitle12(context), textAlign: TextAlign.right, ), @@ -364,12 +435,18 @@ class _ConfirmTransactionViewState style: STextStyles.smallMed12(context), ), Text( - "${Format.satoshiAmountToPrettyString(transactionInfo["fee"] as int, ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale), - ), ref.watch( - managerProvider.select((value) => value.coin), - ))} ${ref.watch( + "${(transactionInfo["fee"] is Amount ? transactionInfo["fee"] as Amount : (transactionInfo["fee"] as int).toAmountAsRaw( + fractionDigits: ref.watch( + managerProvider.select( + (value) => value.coin.decimals, + ), + ), + )).localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider + .select((value) => value.locale), + ), + )} ${ref.watch( managerProvider.select((value) => value.coin), ).ticker}", style: STextStyles.itemSubtitle12(context), @@ -438,8 +515,14 @@ class _ConfirmTransactionViewState ), child: Row( children: [ - SvgPicture.asset( - Assets.svg.send(context), + SvgPicture.file( + File( + ref.watch( + themeProvider.select( + (value) => value.assets.send, + ), + ), + ), width: 32, height: 32, ), @@ -447,10 +530,7 @@ class _ConfirmTransactionViewState width: 16, ), Text( - "Send ${ref.watch( - managerProvider - .select((value) => value.coin), - ).ticker}", + "Send $unit", style: STextStyles.desktopTextMedium(context), ), ], @@ -473,48 +553,56 @@ class _ConfirmTransactionViewState ), Builder( builder: (context) { - final amount = - transactionInfo["recipientAmt"] as int; final coin = ref.watch( managerProvider.select( (value) => value.coin, ), ); + final amount = + transactionInfo["recipientAmt"] as Amount; final externalCalls = ref.watch( prefsChangeNotifierProvider.select( (value) => value.externalCalls)); String fiatAmount = "N/A"; if (externalCalls) { - final price = ref - .read(priceAnd24hChangeNotifierProvider) - .getPrice(coin) - .item1; - if (price > Decimal.zero) { - fiatAmount = Format.localizedStringAsFixed( - value: Format.satoshisToAmount(amount, - coin: coin) * - price, - locale: ref + final price = widget.isTokenTx + ? ref .read( - localeServiceChangeNotifierProvider) - .locale, - decimalPlaces: 2, - ); + priceAnd24hChangeNotifierProvider) + .getTokenPrice( + ref + .read(tokenServiceProvider)! + .tokenContract + .address, + ) + .item1 + : ref + .read( + priceAnd24hChangeNotifierProvider) + .getPrice(coin) + .item1; + if (price > Decimal.zero) { + fiatAmount = (amount.decimal * price) + .toAmount(fractionDigits: 2) + .localizedStringAsFixed( + locale: ref + .read( + localeServiceChangeNotifierProvider) + .locale, + ); } } return Row( children: [ Text( - "${Format.satoshiAmountToPrettyString( - amount, - ref.watch( + "${amount.localizedStringAsFixed( + locale: ref.watch( localeServiceChangeNotifierProvider .select((value) => value.locale), ), - coin, - )} ${coin.ticker}", + )} $unit", style: STextStyles .desktopTextExtraExtraSmall( context) @@ -560,7 +648,9 @@ class _ConfirmTransactionViewState crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Send to", + widget.isPaynymTransaction + ? "PayNym recipient" + : "Send to", style: STextStyles.desktopTextExtraExtraSmall( context), ), @@ -568,7 +658,11 @@ class _ConfirmTransactionViewState height: 2, ), Text( - "${transactionInfo["address"] ?? "ERROR"}", + widget.isPaynymTransaction + ? (transactionInfo["paynymAccountLite"] + as PaynymAccountLite) + .nymName + : "${transactionInfo["address"] ?? "ERROR"}", style: STextStyles.desktopTextExtraExtraSmall( context) .copyWith( @@ -580,6 +674,66 @@ class _ConfirmTransactionViewState ], ), ), + if (widget.isPaynymTransaction) + Container( + height: 1, + color: Theme.of(context) + .extension()! + .background, + ), + if (widget.isPaynymTransaction) + Padding( + padding: const EdgeInsets.all(12), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Transaction fee", + style: STextStyles.desktopTextExtraExtraSmall( + context), + ), + const SizedBox( + height: 2, + ), + Builder( + builder: (context) { + final coin = ref + .watch(walletsChangeNotifierProvider + .select((value) => + value.getManager(walletId))) + .coin; + + final fee = transactionInfo["fee"] is Amount + ? transactionInfo["fee"] as Amount + : (transactionInfo["fee"] as int) + .toAmountAsRaw( + fractionDigits: coin.decimals, + ); + + return Text( + "${fee.localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider + .select( + (value) => value.locale, + ), + ), + )} ${coin.ticker}", + style: + STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ); + }, + ), + ], + ), + ), // Container( // height: 1, // color: Theme.of(context) @@ -699,7 +853,7 @@ class _ConfirmTransactionViewState ], ), ), - if (isDesktop) + if (isDesktop && !widget.isPaynymTransaction) Padding( padding: const EdgeInsets.only( left: 32, @@ -709,169 +863,126 @@ class _ConfirmTransactionViewState style: STextStyles.desktopTextExtraExtraSmall(context), ), ), - if (isDesktop) + if (isDesktop && !widget.isPaynymTransaction) Padding( - padding: const EdgeInsets.only( - top: 10, - left: 32, - right: 32, + padding: const EdgeInsets.only( + top: 10, + left: 32, + right: 32, + ), + child: RoundedContainer( + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 18, ), - child: RoundedContainer( - padding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 18, - ), - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, - child: Builder(builder: (context) { + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + child: Builder( + builder: (context) { final coin = ref .watch(walletsChangeNotifierProvider .select((value) => value.getManager(walletId))) .coin; - final fee = Format.satoshisToAmount( - transactionInfo["fee"] as int, - coin: coin, - ); + final fee = transactionInfo["fee"] is Amount + ? transactionInfo["fee"] as Amount + : (transactionInfo["fee"] as int).toAmountAsRaw( + fractionDigits: coin.decimals, + ); return Text( - "${Format.localizedStringAsFixed( - value: fee, - locale: ref.watch(localeServiceChangeNotifierProvider - .select((value) => value.locale)), - decimalPlaces: Constants.decimalPlacesForCoin(coin), + "${fee.localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), + ), )} ${coin.ticker}", style: STextStyles.itemSubtitle(context), ); - }), - ) - // DropdownButtonHideUnderline( - // child: DropdownButton2( - // offset: const Offset(0, -10), - // isExpanded: true, - // - // dropdownElevation: 0, - // value: _fee, - // items: [ - // ..._dropDownItems.map( - // (e) { - // String message = _fee.toString(); - // - // return DropdownMenuItem( - // value: e, - // child: Text(message), - // ); - // }, - // ), - // ], - // onChanged: (value) { - // if (value is int) { - // setState(() { - // _fee = value; - // }); - // } - // }, - // icon: SvgPicture.asset( - // Assets.svg.chevronDown, - // width: 12, - // height: 6, - // color: - // Theme.of(context).extension()!.textDark3, - // ), - // buttonPadding: const EdgeInsets.symmetric( - // horizontal: 16, - // vertical: 8, - // ), - // buttonDecoration: BoxDecoration( - // color: Theme.of(context) - // .extension()! - // .textFieldDefaultBG, - // borderRadius: BorderRadius.circular( - // Constants.size.circularBorderRadius, - // ), - // ), - // dropdownDecoration: BoxDecoration( - // color: Theme.of(context) - // .extension()! - // .textFieldDefaultBG, - // borderRadius: BorderRadius.circular( - // Constants.size.circularBorderRadius, - // ), - // ), - // ), - // ), + }, ), + ), + ), if (!isDesktop) const Spacer(), SizedBox( height: isDesktop ? 23 : 12, ), - Padding( - padding: isDesktop - ? const EdgeInsets.symmetric( - horizontal: 32, - ) - : const EdgeInsets.all(0), - child: RoundedContainer( + if (!widget.isTokenTx) + Padding( padding: isDesktop ? const EdgeInsets.symmetric( - horizontal: 16, - vertical: 18, + horizontal: 32, ) - : const EdgeInsets.all(12), - color: Theme.of(context) - .extension()! - .snackBarBackSuccess, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - isDesktop ? "Total amount to send" : "Total amount", - style: isDesktop - ? STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textConfirmTotalAmount, - ) - : STextStyles.titleBold12(context).copyWith( - color: Theme.of(context) - .extension()! - .textConfirmTotalAmount, - ), - ), - Text( - "${Format.satoshiAmountToPrettyString( - (transactionInfo["fee"] as int) + - (transactionInfo["recipientAmt"] as int), - ref.watch( + : const EdgeInsets.all(0), + child: RoundedContainer( + padding: isDesktop + ? const EdgeInsets.symmetric( + horizontal: 16, + vertical: 18, + ) + : const EdgeInsets.all(12), + color: Theme.of(context) + .extension()! + .snackBarBackSuccess, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + isDesktop ? "Total amount to send" : "Total amount", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textConfirmTotalAmount, + ) + : STextStyles.titleBold12(context).copyWith( + color: Theme.of(context) + .extension()! + .textConfirmTotalAmount, + ), + ), + Builder(builder: (context) { + final coin = ref.watch( + walletsChangeNotifierProvider.select( + (value) => value.getManager(walletId).coin)); + final fee = transactionInfo["fee"] is Amount + ? transactionInfo["fee"] as Amount + : (transactionInfo["fee"] as int) + .toAmountAsRaw(fractionDigits: coin.decimals); + final locale = ref.watch( localeServiceChangeNotifierProvider .select((value) => value.locale), - ), - ref.watch( - managerProvider.select((value) => value.coin), - ), - )} ${ref.watch( - managerProvider.select((value) => value.coin), - ).ticker}", - style: isDesktop - ? STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textConfirmTotalAmount, - ) - : STextStyles.itemSubtitle12(context).copyWith( - color: Theme.of(context) - .extension()! - .textConfirmTotalAmount, - ), - textAlign: TextAlign.right, - ), - ], + ); + final amount = + transactionInfo["recipientAmt"] as Amount; + return Text( + "${(amount + fee).localizedStringAsFixed( + locale: locale, + )} ${ref.watch( + managerProvider.select((value) => value.coin), + ).ticker}", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textConfirmTotalAmount, + ) + : STextStyles.itemSubtitle12(context).copyWith( + color: Theme.of(context) + .extension()! + .textConfirmTotalAmount, + ), + textAlign: TextAlign.right, + ); + }), + ], + ), ), ), - ), SizedBox( height: isDesktop ? 28 : 16, ), diff --git a/lib/pages/send_view/send_view.dart b/lib/pages/send_view/send_view.dart index b129c4467..3d98309be 100644 --- a/lib/pages/send_view/send_view.dart +++ b/lib/pages/send_view/send_view.dart @@ -1,13 +1,18 @@ import 'dart:async'; +import 'dart:io'; +import 'package:bip47/bip47.dart'; import 'package:cw_core/monero_transaction_priority.dart'; import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/models/paynym/paynym_account_lite.dart'; import 'package:stackwallet/models/send_view_auto_fill_data.dart'; import 'package:stackwallet/pages/address_book_views/address_book_view.dart'; +import 'package:stackwallet/pages/coin_control/coin_control_view.dart'; import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dialog.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/firo_balance_selection_sheet.dart'; @@ -19,18 +24,20 @@ import 'package:stackwallet/providers/wallet/public_private_balance_state_provid import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; import 'package:stackwallet/services/coins/manager.dart'; +import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/address_utils.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/barcode_scanner_interface.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; -import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/animated_text.dart'; import 'package:stackwallet/widgets/background.dart'; @@ -40,9 +47,11 @@ import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; +import 'package:tuple/tuple.dart'; class SendView extends ConsumerStatefulWidget { const SendView({ @@ -52,6 +61,7 @@ class SendView extends ConsumerStatefulWidget { this.autoFillData, this.clipboard = const ClipboardWrapper(), this.barcodeScanner = const BarcodeScannerWrapper(), + this.accountLite, }) : super(key: key); static const String routeName = "/sendView"; @@ -61,6 +71,7 @@ class SendView extends ConsumerStatefulWidget { final SendViewAutoFillData? autoFillData; final ClipboardInterface clipboard; final BarcodeScannerInterface barcodeScanner; + final PaynymAccountLite? accountLite; @override ConsumerState createState() => _SendViewState(); @@ -85,8 +96,8 @@ class _SendViewState extends ConsumerState { final _cryptoFocus = FocusNode(); final _baseFocus = FocusNode(); - Decimal? _amountToSend; - Decimal? _cachedAmountToSend; + Amount? _amountToSend; + Amount? _cachedAmountToSend; String? _address; String? _privateBalanceString; @@ -97,7 +108,9 @@ class _SendViewState extends ConsumerState { bool _cryptoAmountChangeLock = false; late VoidCallback onCryptoAmountChanged; - Decimal? _cachedBalance; + Amount? _cachedBalance; + + Set selectedUTXOs = {}; void _cryptoAmountChanged() async { if (!_cryptoAmountChangeLock) { @@ -107,7 +120,9 @@ class _SendViewState extends ConsumerState { cryptoAmount != ",") { _amountToSend = cryptoAmount.contains(",") ? Decimal.parse(cryptoAmount.replaceFirst(",", ".")) - : Decimal.parse(cryptoAmount); + .toAmount(fractionDigits: coin.decimals) + : Decimal.parse(cryptoAmount) + .toAmount(fractionDigits: coin.decimals); if (_cachedAmountToSend != null && _cachedAmountToSend == _amountToSend) { return; @@ -120,13 +135,13 @@ class _SendViewState extends ConsumerState { ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin).item1; if (price > Decimal.zero) { - final String fiatAmountString = Format.localizedStringAsFixed( - value: _amountToSend! * price, - locale: ref.read(localeServiceChangeNotifierProvider).locale, - decimalPlaces: 2, - ); - - baseAmountController.text = fiatAmountString; + baseAmountController.text = (_amountToSend!.decimal * price) + .toAmount( + fractionDigits: 2, + ) + .localizedStringAsFixed( + locale: ref.read(localeServiceChangeNotifierProvider).locale, + ); } } else { _amountToSend = null; @@ -135,16 +150,53 @@ class _SendViewState extends ConsumerState { _updatePreviewButtonState(_address, _amountToSend); - // if (_amountToSend == null) { - // setState(() { - // _calculateFeesFuture = calculateFees(0); - // }); - // } else { - // setState(() { - // _calculateFeesFuture = - // calculateFees(Format.decimalAmountToSatoshis(_amountToSend!)); - // }); - // } + _cryptoAmountChangedFeeUpdateTimer?.cancel(); + _cryptoAmountChangedFeeUpdateTimer = Timer(updateFeesTimerDuration, () { + if (coin != Coin.epicCash && !_baseFocus.hasFocus) { + setState(() { + _calculateFeesFuture = calculateFees( + _amountToSend == null + ? 0.toAmountAsRaw(fractionDigits: coin.decimals) + : _amountToSend!, + ); + }); + } + }); + } + } + + final updateFeesTimerDuration = const Duration(milliseconds: 500); + + Timer? _cryptoAmountChangedFeeUpdateTimer; + Timer? _baseAmountChangedFeeUpdateTimer; + + void _baseAmountChanged() { + _baseAmountChangedFeeUpdateTimer?.cancel(); + _baseAmountChangedFeeUpdateTimer = Timer(updateFeesTimerDuration, () { + if (coin != Coin.epicCash && !_cryptoFocus.hasFocus) { + setState(() { + _calculateFeesFuture = calculateFees( + _amountToSend == null + ? 0.toAmountAsRaw(fractionDigits: coin.decimals) + : _amountToSend!, + ); + }); + } + }); + } + + late Amount _currentFee; + + void _setCurrentFee(String fee, bool shouldSetState) { + final value = fee.contains(",") + ? Decimal.parse(fee.replaceFirst(",", ".")) + .toAmount(fractionDigits: coin.decimals) + : Decimal.parse(fee).toAmount(fractionDigits: coin.decimals); + + if (shouldSetState) { + setState(() => _currentFee = value); + } else { + _currentFee = value; } } @@ -158,23 +210,28 @@ class _SendViewState extends ConsumerState { return null; } - void _updatePreviewButtonState(String? address, Decimal? amount) { - final isValidAddress = ref - .read(walletsChangeNotifierProvider) - .getManager(walletId) - .validateAddress(address ?? ""); - ref.read(previewTxButtonStateProvider.state).state = - (isValidAddress && amount != null && amount > Decimal.zero); + void _updatePreviewButtonState(String? address, Amount? amount) { + if (isPaynymSend) { + ref.read(previewTxButtonStateProvider.state).state = + (amount != null && amount > Amount.zero); + } else { + final isValidAddress = ref + .read(walletsChangeNotifierProvider) + .getManager(walletId) + .validateAddress(address ?? ""); + ref.read(previewTxButtonStateProvider.state).state = + (isValidAddress && amount != null && amount > Amount.zero); + } } late Future _calculateFeesFuture; - Map cachedFees = {}; - Map cachedFiroPrivateFees = {}; - Map cachedFiroPublicFees = {}; + Map cachedFees = {}; + Map cachedFiroPrivateFees = {}; + Map cachedFiroPublicFees = {}; - Future calculateFees(int amount) async { - if (amount <= 0) { + Future calculateFees(Amount amount) async { + if (amount <= Amount.zero) { return "0"; } @@ -211,7 +268,8 @@ class _SendViewState extends ConsumerState { break; } - int fee; + final String locale = ref.read(localeServiceChangeNotifierProvider).locale; + Amount fee; if (coin == Coin.monero) { MoneroTransactionPriority specialMoneroId; switch (ref.read(feeRateTypeStateProvider.state).state) { @@ -227,8 +285,7 @@ class _SendViewState extends ConsumerState { } fee = await manager.estimateFeeFor(amount, specialMoneroId.raw!); - cachedFees[amount] = Format.satoshisToAmount(fee, coin: coin) - .toStringAsFixed(Constants.decimalPlacesForCoin(coin)); + cachedFees[amount] = fee.localizedStringAsFixed(locale: locale); return cachedFees[amount]!; } else if (coin == Coin.firo || coin == Coin.firoTestNet) { @@ -236,23 +293,22 @@ class _SendViewState extends ConsumerState { "Private") { fee = await manager.estimateFeeFor(amount, feeRate); - cachedFiroPrivateFees[amount] = Format.satoshisToAmount(fee, coin: coin) - .toStringAsFixed(Constants.decimalPlacesForCoin(coin)); + cachedFiroPrivateFees[amount] = + fee.localizedStringAsFixed(locale: locale); return cachedFiroPrivateFees[amount]!; } else { fee = await (manager.wallet as FiroWallet) .estimateFeeForPublic(amount, feeRate); - cachedFiroPublicFees[amount] = Format.satoshisToAmount(fee, coin: coin) - .toStringAsFixed(Constants.decimalPlacesForCoin(coin)); + cachedFiroPublicFees[amount] = + fee.localizedStringAsFixed(locale: locale); return cachedFiroPublicFees[amount]!; } } else { fee = await manager.estimateFeeFor(amount, feeRate); - cachedFees[amount] = Format.satoshisToAmount(fee, coin: coin) - .toStringAsFixed(Constants.decimalPlacesForCoin(coin)); + cachedFees[amount] = fee.localizedStringAsFixed(locale: locale); return cachedFees[amount]!; } @@ -263,29 +319,259 @@ class _SendViewState extends ConsumerState { final wallet = ref.read(provider).wallet as FiroWallet?; if (wallet != null) { - Decimal? balance; + Amount? balance; if (ref.read(publicPrivateBalanceStateProvider.state).state == "Private") { - balance = await wallet.availablePrivateBalance(); + balance = wallet.availablePrivateBalance(); } else { - balance = await wallet.availablePublicBalance(); + balance = wallet.availablePublicBalance(); } - return Format.localizedStringAsFixed( - value: balance, locale: locale, decimalPlaces: 8); + return balance.localizedStringAsFixed( + locale: ref.read(localeServiceChangeNotifierProvider).locale, + ); } return null; } + Future _previewTransaction() async { + // wait for keyboard to disappear + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 100), + ); + final manager = + ref.read(walletsChangeNotifierProvider).getManager(walletId); + + final Amount amount = _amountToSend!; + final Amount availableBalance; + if ((coin == Coin.firo || coin == Coin.firoTestNet)) { + if (ref.read(publicPrivateBalanceStateProvider.state).state == + "Private") { + availableBalance = + (manager.wallet as FiroWallet).availablePrivateBalance(); + } else { + availableBalance = + (manager.wallet as FiroWallet).availablePublicBalance(); + } + } else { + availableBalance = manager.balance.spendable; + } + + final coinControlEnabled = + ref.read(prefsChangeNotifierProvider).enableCoinControl; + + if (coin != Coin.ethereum && + !(manager.hasCoinControlSupport && coinControlEnabled) || + (manager.hasCoinControlSupport && + coinControlEnabled && + selectedUTXOs.isEmpty)) { + // confirm send all + if (amount == availableBalance) { + bool? shouldSendAll; + if (mounted) { + shouldSendAll = await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return StackDialog( + title: "Confirm send all", + message: + "You are about to send your entire balance. Would you like to continue?", + leftButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Cancel", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + onPressed: () { + Navigator.of(context).pop(false); + }, + ), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonStyle(context), + child: Text( + "Yes", + style: STextStyles.button(context), + ), + onPressed: () { + Navigator.of(context).pop(true); + }, + ), + ); + }, + ); + } + + if (shouldSendAll == null || shouldSendAll == false) { + // cancel preview + return; + } + } + } + + try { + bool wasCancelled = false; + + if (mounted) { + unawaited( + showDialog( + context: context, + useSafeArea: false, + barrierDismissible: false, + builder: (context) { + return BuildingTransactionDialog( + coin: manager.coin, + onCancel: () { + wasCancelled = true; + + Navigator.of(context).pop(); + }, + ); + }, + ), + ); + } + + final time = Future.delayed( + const Duration( + milliseconds: 2500, + ), + ); + + Map txData; + Future> txDataFuture; + + if (isPaynymSend) { + final wallet = manager.wallet as PaynymWalletInterface; + final paymentCode = PaymentCode.fromPaymentCode( + widget.accountLite!.code, + networkType: wallet.networkType, + ); + final feeRate = ref.read(feeRateTypeStateProvider); + txDataFuture = wallet.preparePaymentCodeSend( + paymentCode: paymentCode, + isSegwit: widget.accountLite!.segwit, + amount: amount, + args: { + "feeRate": feeRate, + "UTXOs": (manager.hasCoinControlSupport && + coinControlEnabled && + selectedUTXOs.isNotEmpty) + ? selectedUTXOs + : null, + }, + ); + } else if ((coin == Coin.firo || coin == Coin.firoTestNet) && + ref.read(publicPrivateBalanceStateProvider.state).state != + "Private") { + txDataFuture = (manager.wallet as FiroWallet).prepareSendPublic( + address: _address!, + amount: amount, + args: {"feeRate": ref.read(feeRateTypeStateProvider)}, + ); + } else { + txDataFuture = manager.prepareSend( + address: _address!, + amount: amount, + args: { + "feeRate": ref.read(feeRateTypeStateProvider), + "UTXOs": (manager.hasCoinControlSupport && + coinControlEnabled && + selectedUTXOs.isNotEmpty) + ? selectedUTXOs + : null, + }, + ); + } + + final results = await Future.wait([ + txDataFuture, + time, + ]); + + txData = results.first as Map; + + if (!wasCancelled && mounted) { + // pop building dialog + Navigator.of(context).pop(); + txData["note"] = noteController.text; + if (isPaynymSend) { + txData["paynymAccountLite"] = widget.accountLite!; + } else { + txData["address"] = _address; + } + + unawaited(Navigator.of(context).push( + RouteGenerator.getRoute( + shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute, + builder: (_) => ConfirmTransactionView( + transactionInfo: txData, + walletId: walletId, + isPaynymTransaction: isPaynymSend, + ), + settings: const RouteSettings( + name: ConfirmTransactionView.routeName, + ), + ), + )); + } + } catch (e) { + if (mounted) { + // pop building dialog + Navigator.of(context).pop(); + + unawaited(showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return StackDialog( + title: "Transaction failed", + message: e.toString(), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Ok", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ); + }, + )); + } + } + } + + bool get isPaynymSend => widget.accountLite != null; + @override void initState() { + coin = widget.coin; ref.refresh(feeSheetSessionCacheProvider); + _currentFee = 0.toAmountAsRaw(fractionDigits: coin.decimals); - _calculateFeesFuture = calculateFees(0); + _calculateFeesFuture = + calculateFees(0.toAmountAsRaw(fractionDigits: coin.decimals)); _data = widget.autoFillData; walletId = widget.walletId; - coin = widget.coin; clipboard = widget.clipboard; scanner = widget.barcodeScanner; @@ -297,53 +583,63 @@ class _SendViewState extends ConsumerState { onCryptoAmountChanged = _cryptoAmountChanged; cryptoAmountController.addListener(onCryptoAmountChanged); + baseAmountController.addListener(_baseAmountChanged); if (_data != null) { if (_data!.amount != null) { cryptoAmountController.text = _data!.amount!.toString(); } sendToController.text = _data!.contactLabel; - _address = _data!.address; + _address = _data!.address.trim(); _addressToggleFlag = true; } - if (coin != Coin.epicCash) { - _cryptoFocus.addListener(() { - if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) { - if (_amountToSend == null) { - setState(() { - _calculateFeesFuture = calculateFees(0); - }); - } else { - setState(() { - _calculateFeesFuture = calculateFees( - Format.decimalAmountToSatoshis(_amountToSend!, coin)); - }); - } - } - }); - - _baseFocus.addListener(() { - if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) { - if (_amountToSend == null) { - setState(() { - _calculateFeesFuture = calculateFees(0); - }); - } else { - setState(() { - _calculateFeesFuture = calculateFees( - Format.decimalAmountToSatoshis(_amountToSend!, coin)); - }); - } - } - }); + if (isPaynymSend) { + sendToController.text = widget.accountLite!.nymName; + noteController.text = "PayNym send"; } + + // if (coin != Coin.epicCash) { + // _cryptoFocus.addListener(() { + // if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) { + // if (_amountToSend == null) { + // setState(() { + // _calculateFeesFuture = calculateFees(0); + // }); + // } else { + // setState(() { + // _calculateFeesFuture = calculateFees( + // Format.decimalAmountToSatoshis(_amountToSend!, coin)); + // }); + // } + // } + // }); + + // _baseFocus.addListener(() { + // if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) { + // if (_amountToSend == null) { + // setState(() { + // _calculateFeesFuture = calculateFees(0); + // }); + // } else { + // setState(() { + // _calculateFeesFuture = calculateFees( + // Format.decimalAmountToSatoshis(_amountToSend!, coin)); + // }); + // } + // } + // }); + // } super.initState(); } @override void dispose() { + _cryptoAmountChangedFeeUpdateTimer?.cancel(); + _baseAmountChangedFeeUpdateTimer?.cancel(); + cryptoAmountController.removeListener(onCryptoAmountChanged); + baseAmountController.removeListener(_baseAmountChanged); sendToController.dispose(); cryptoAmountController.dispose(); @@ -366,21 +662,50 @@ class _SendViewState extends ConsumerState { final String locale = ref.watch( localeServiceChangeNotifierProvider.select((value) => value.locale)); + final showCoinControl = ref.watch( + walletsChangeNotifierProvider.select( + (value) => value.getManager(walletId).hasCoinControlSupport, + ), + ) && + ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.enableCoinControl, + ), + ); + if (coin == Coin.firo || coin == Coin.firoTestNet) { ref.listen(publicPrivateBalanceStateProvider, (previous, next) { if (_amountToSend == null) { setState(() { - _calculateFeesFuture = calculateFees(0); + _calculateFeesFuture = + calculateFees(0.toAmountAsRaw(fractionDigits: coin.decimals)); }); } else { setState(() { _calculateFeesFuture = calculateFees( - Format.decimalAmountToSatoshis(_amountToSend!, coin)); + _amountToSend!, + ); }); } }); } + // add listener for epic cash to strip http:// and https:// prefixes if the address also ocntains an @ symbol (indicating an epicbox address) + if (coin == Coin.epicCash) { + sendToController.addListener(() { + _address = sendToController.text.trim(); + + if (_address != null && _address!.isNotEmpty) { + _address = _address!.trim(); + if (_address!.contains("\n")) { + _address = _address!.substring(0, _address!.indexOf("\n")); + } + + sendToController.text = formatAddress(_address!); + } + }); + } + return Background( child: Scaffold( backgroundColor: Theme.of(context).extension()!.background, @@ -434,75 +759,69 @@ class _SendViewState extends ConsumerState { padding: const EdgeInsets.all(12.0), child: Row( children: [ - SvgPicture.asset( - Assets.svg.iconFor(coin: coin), + SvgPicture.file( + File( + ref.watch( + coinIconProvider(coin), + ), + ), width: 22, height: 22, ), const SizedBox( width: 6, ), - if (coin != Coin.firo && - coin != Coin.firoTestNet) - Expanded( - child: Text( + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( ref.watch(provider.select( (value) => value.walletName)), - style: STextStyles.titleBold12(context), + style: STextStyles.titleBold12(context) + .copyWith(fontSize: 14), overflow: TextOverflow.ellipsis, maxLines: 1, ), - ), - if (coin == Coin.firo || - coin == Coin.firoTestNet) - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - ref.watch(provider.select( - (value) => value.walletName)), - style: - STextStyles.titleBold12(context) - .copyWith(fontSize: 14), - ), - // const SizedBox( - // height: 2, - // ), + // const SizedBox( + // height: 2, + // ), + if (coin == Coin.firo || + coin == Coin.firoTestNet) Text( "${ref.watch(publicPrivateBalanceStateProvider.state).state} balance", style: STextStyles.label(context) .copyWith(fontSize: 10), ), - ], - ), - if (coin != Coin.firo && - coin != Coin.firoTestNet) - const SizedBox( - width: 10, - ), - if (coin == Coin.firo || - coin == Coin.firoTestNet) - const Spacer(), + if (coin != Coin.firo && + coin != Coin.firoTestNet) + Text( + "Available balance", + style: STextStyles.label(context) + .copyWith(fontSize: 10), + ), + ], + ), + const Spacer(), FutureBuilder( + // TODO redo this widget now that its not actually a future future: (coin != Coin.firo && coin != Coin.firoTestNet) - ? ref.watch(provider.select( - (value) => value.availableBalance)) - : ref - .watch( - publicPrivateBalanceStateProvider - .state) - .state == + ? Future(() => ref.watch( + provider.select((value) => + value.balance.spendable))) + : ref.watch(publicPrivateBalanceStateProvider.state).state == "Private" - ? (ref.watch(provider).wallet - as FiroWallet) - .availablePrivateBalance() - : (ref.watch(provider).wallet - as FiroWallet) - .availablePublicBalance(), + ? Future(() => (ref + .watch(provider) + .wallet as FiroWallet) + .availablePrivateBalance()) + : Future(() => (ref + .watch(provider) + .wallet as FiroWallet) + .availablePublicBalance()), builder: - (_, AsyncSnapshot snapshot) { + (_, AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) { @@ -513,10 +832,9 @@ class _SendViewState extends ConsumerState { return GestureDetector( onTap: () { cryptoAmountController.text = - _cachedBalance!.toStringAsFixed( - Constants - .decimalPlacesForCoin( - coin)); + _cachedBalance! + .localizedStringAsFixed( + locale: locale); }, child: Container( color: Colors.transparent, @@ -525,10 +843,8 @@ class _SendViewState extends ConsumerState { CrossAxisAlignment.end, children: [ Text( - "${Format.localizedStringAsFixed( - value: _cachedBalance!, + "${_cachedBalance!.localizedStringAsFixed( locale: locale, - decimalPlaces: 8, )} ${coin.ticker}", style: STextStyles.titleBold12( @@ -539,20 +855,13 @@ class _SendViewState extends ConsumerState { textAlign: TextAlign.right, ), Text( - "${Format.localizedStringAsFixed( - value: _cachedBalance! * - ref.watch(priceAnd24hChangeNotifierProvider - .select((value) => - value - .getPrice( - coin) - .item1)), - locale: locale, - decimalPlaces: 2, - )} ${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", - style: STextStyles - .titleBold12_400( - context) + "${(_cachedBalance!.decimal * ref.watch(priceAnd24hChangeNotifierProvider.select((value) => value.getPrice(coin).item1))).toAmount( + fractionDigits: 2, + ).localizedStringAsFixed( + locale: locale, + )} ${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", + style: STextStyles.subtitle( + context) .copyWith( fontSize: 8, ), @@ -609,102 +918,265 @@ class _SendViewState extends ConsumerState { height: 16, ), Text( - "Send to", + isPaynymSend ? "Send to PayNym address" : "Send to", style: STextStyles.smallMed12(context), textAlign: TextAlign.left, ), const SizedBox( height: 8, ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: const Key("sendViewAddressFieldKey"), + if (isPaynymSend) + TextField( + key: const Key("sendViewPaynymAddressFieldKey"), controller: sendToController, - readOnly: false, - autocorrect: false, - enableSuggestions: false, - // inputFormatters: [ - // FilteringTextInputFormatter.allow( - // RegExp("[a-zA-Z0-9]{34}")), - // ], - toolbarOptions: const ToolbarOptions( - copy: false, - cut: false, - paste: true, - selectAll: false, + enabled: false, + readOnly: true, + style: STextStyles.fieldLabel(context), + ), + if (!isPaynymSend) + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), - onChanged: (newValue) { - _address = newValue; - _updatePreviewButtonState( - _address, _amountToSend); - - setState(() { - _addressToggleFlag = newValue.isNotEmpty; - }); - }, - focusNode: _addressFocusNode, - style: STextStyles.field(context), - decoration: standardInputDecoration( - "Enter ${coin.ticker} address", - _addressFocusNode, - context, - ).copyWith( - contentPadding: const EdgeInsets.only( - left: 16, - top: 6, - bottom: 8, - right: 5, + child: TextField( + key: const Key("sendViewAddressFieldKey"), + controller: sendToController, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + // inputFormatters: [ + // FilteringTextInputFormatter.allow( + // RegExp("[a-zA-Z0-9]{34}")), + // ], + toolbarOptions: const ToolbarOptions( + copy: false, + cut: false, + paste: true, + selectAll: false, ), - suffixIcon: Padding( - padding: sendToController.text.isEmpty - ? const EdgeInsets.only(right: 8) - : const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceAround, - children: [ - _addressToggleFlag - ? TextFieldIconButton( - key: const Key( - "sendViewClearAddressFieldButtonKey"), - onTap: () { - sendToController.text = ""; - _address = ""; - _updatePreviewButtonState( - _address, _amountToSend); - setState(() { - _addressToggleFlag = false; - }); - }, - child: const XIcon(), - ) - : TextFieldIconButton( - key: const Key( - "sendViewPasteAddressFieldButtonKey"), - onTap: () async { - final ClipboardData? data = - await clipboard.getData( - Clipboard.kTextPlain); - if (data?.text != null && - data!.text!.isNotEmpty) { - String content = - data.text!.trim(); - if (content - .contains("\n")) { - content = - content.substring( - 0, - content.indexOf( - "\n")); + onChanged: (newValue) { + _address = newValue.trim(); + _updatePreviewButtonState( + _address, _amountToSend); + + setState(() { + _addressToggleFlag = newValue.isNotEmpty; + }); + }, + focusNode: _addressFocusNode, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Enter ${coin.ticker} address", + _addressFocusNode, + context, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 16, + top: 6, + bottom: 8, + right: 5, + ), + suffixIcon: Padding( + padding: sendToController.text.isEmpty + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceAround, + children: [ + _addressToggleFlag + ? TextFieldIconButton( + semanticsLabel: + "Clear Button. Clears The Address Field Input.", + key: const Key( + "sendViewClearAddressFieldButtonKey"), + onTap: () { + sendToController.text = ""; + _address = ""; + _updatePreviewButtonState( + _address, + _amountToSend); + setState(() { + _addressToggleFlag = + false; + }); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + semanticsLabel: + "Paste Button. Pastes From Clipboard To Address Field Input.", + key: const Key( + "sendViewPasteAddressFieldButtonKey"), + onTap: () async { + final ClipboardData? data = + await clipboard.getData( + Clipboard + .kTextPlain); + if (data?.text != null && + data! + .text!.isNotEmpty) { + String content = + data.text!.trim(); + if (content + .contains("\n")) { + content = + content.substring( + 0, + content.indexOf( + "\n")); + } + + if (coin == + Coin.epicCash) { + // strip http:// and https:// if content contains @ + content = formatAddress( + content); + } + sendToController.text = + content.trim(); + _address = content.trim(); + + _updatePreviewButtonState( + _address, + _amountToSend); + setState(() { + _addressToggleFlag = + sendToController + .text + .isNotEmpty; + }); + } + }, + child: sendToController + .text.isEmpty + ? const ClipboardIcon() + : const XIcon(), + ), + if (sendToController.text.isEmpty) + TextFieldIconButton( + semanticsLabel: + "Address Book Button. Opens Address Book For Address Field.", + key: const Key( + "sendViewAddressBookButtonKey"), + onTap: () { + Navigator.of(context).pushNamed( + AddressBookView.routeName, + arguments: widget.coin, + ); + }, + child: const AddressBookIcon(), + ), + if (sendToController.text.isEmpty) + TextFieldIconButton( + semanticsLabel: + "Scan QR Button. Opens Camera For Scanning QR Code.", + key: const Key( + "sendViewScanQrButtonKey"), + onTap: () async { + try { + // ref + // .read( + // shouldShowLockscreenOnResumeStateProvider + // .state) + // .state = false; + if (FocusScope.of(context) + .hasFocus) { + FocusScope.of(context) + .unfocus(); + await Future.delayed( + const Duration( + milliseconds: 75)); + } + + final qrResult = + await scanner.scan(); + + // Future.delayed( + // const Duration(seconds: 2), + // () => ref + // .read( + // shouldShowLockscreenOnResumeStateProvider + // .state) + // .state = true, + // ); + + Logging.instance.log( + "qrResult content: ${qrResult.rawContent}", + level: LogLevel.Info); + + final results = + AddressUtils.parseUri( + qrResult.rawContent); + + Logging.instance.log( + "qrResult parsed: $results", + level: LogLevel.Info); + + if (results.isNotEmpty && + results["scheme"] == + coin.uriScheme) { + // auto fill address + _address = + (results["address"] ?? + "") + .trim(); + sendToController.text = + _address!; + + // autofill notes field + if (results["message"] != + null) { + noteController.text = + results["message"]!; + } else if (results[ + "label"] != + null) { + noteController.text = + results["label"]!; } + // autofill amount field + if (results["amount"] != + null) { + final Amount amount = + Decimal.parse(results[ + "amount"]!) + .toAmount( + fractionDigits: + coin.decimals, + ); + cryptoAmountController + .text = + amount + .localizedStringAsFixed( + locale: locale, + ); + _amountToSend = amount; + } + + _updatePreviewButtonState( + _address, + _amountToSend); + setState(() { + _addressToggleFlag = + sendToController + .text.isNotEmpty; + }); + + // now check for non standard encoded basic address + } else if (ref + .read( + walletsChangeNotifierProvider) + .getManager(walletId) + .validateAddress(qrResult + .rawContent)) { + _address = qrResult + .rawContent + .trim(); sendToController.text = - content; - _address = content; + _address ?? ""; _updatePreviewButtonState( _address, @@ -715,161 +1187,28 @@ class _SendViewState extends ConsumerState { .text.isNotEmpty; }); } - }, - child: sendToController - .text.isEmpty - ? const ClipboardIcon() - : const XIcon(), - ), - if (sendToController.text.isEmpty) - TextFieldIconButton( - key: const Key( - "sendViewAddressBookButtonKey"), - onTap: () { - Navigator.of(context).pushNamed( - AddressBookView.routeName, - arguments: widget.coin, - ); - }, - child: const AddressBookIcon(), - ), - if (sendToController.text.isEmpty) - TextFieldIconButton( - key: const Key( - "sendViewScanQrButtonKey"), - onTap: () async { - try { - // ref - // .read( - // shouldShowLockscreenOnResumeStateProvider - // .state) - // .state = false; - if (FocusScope.of(context) - .hasFocus) { - FocusScope.of(context) - .unfocus(); - await Future.delayed( - const Duration( - milliseconds: 75)); + } on PlatformException catch (e, s) { + // ref + // .read( + // shouldShowLockscreenOnResumeStateProvider + // .state) + // .state = true; + // here we ignore the exception caused by not giving permission + // to use the camera to scan a qr code + Logging.instance.log( + "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s", + level: LogLevel.Warning); } - - final qrResult = - await scanner.scan(); - - // Future.delayed( - // const Duration(seconds: 2), - // () => ref - // .read( - // shouldShowLockscreenOnResumeStateProvider - // .state) - // .state = true, - // ); - - Logging.instance.log( - "qrResult content: ${qrResult.rawContent}", - level: LogLevel.Info); - - final results = - AddressUtils.parseUri( - qrResult.rawContent); - - Logging.instance.log( - "qrResult parsed: $results", - level: LogLevel.Info); - - if (results.isNotEmpty && - results["scheme"] == - coin.uriScheme) { - // auto fill address - _address = - results["address"] ?? ""; - sendToController.text = - _address!; - - // autofill notes field - if (results["message"] != - null) { - noteController.text = - results["message"]!; - } else if (results["label"] != - null) { - noteController.text = - results["label"]!; - } - - // autofill amount field - if (results["amount"] != - null) { - final amount = - Decimal.parse( - results["amount"]!); - cryptoAmountController - .text = - Format - .localizedStringAsFixed( - value: amount, - locale: ref - .read( - localeServiceChangeNotifierProvider) - .locale, - decimalPlaces: Constants - .decimalPlacesForCoin( - coin), - ); - amount.toString(); - _amountToSend = amount; - } - - _updatePreviewButtonState( - _address, _amountToSend); - setState(() { - _addressToggleFlag = - sendToController - .text.isNotEmpty; - }); - - // now check for non standard encoded basic address - } else if (ref - .read( - walletsChangeNotifierProvider) - .getManager(walletId) - .validateAddress( - qrResult.rawContent)) { - _address = - qrResult.rawContent; - sendToController.text = - _address ?? ""; - - _updatePreviewButtonState( - _address, _amountToSend); - setState(() { - _addressToggleFlag = - sendToController - .text.isNotEmpty; - }); - } - } on PlatformException catch (e, s) { - // ref - // .read( - // shouldShowLockscreenOnResumeStateProvider - // .state) - // .state = true; - // here we ignore the exception caused by not giving permission - // to use the camera to scan a qr code - Logging.instance.log( - "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s", - level: LogLevel.Warning); - } - }, - child: const QrCodeIcon(), - ) - ], + }, + child: const QrCodeIcon(), + ) + ], + ), ), ), ), ), ), - ), Builder( builder: (_) { final error = _updateInvalidAddressText( @@ -1059,41 +1398,42 @@ class _SendViewState extends ConsumerState { style: STextStyles.smallMed12(context), textAlign: TextAlign.left, ), - BlueTextButton( - text: "Send all ${coin.ticker}", - onTap: () async { - if (coin == Coin.firo || - coin == Coin.firoTestNet) { - final firoWallet = - ref.read(provider).wallet as FiroWallet; - if (ref - .read( - publicPrivateBalanceStateProvider - .state) - .state == - "Private") { - cryptoAmountController.text = - (await firoWallet - .availablePrivateBalance()) - .toStringAsFixed(Constants - .decimalPlacesForCoin(coin)); + if (coin != Coin.ethereum) + CustomTextButton( + text: "Send all ${coin.ticker}", + onTap: () async { + if (coin == Coin.firo || + coin == Coin.firoTestNet) { + final firoWallet = ref + .read(provider) + .wallet as FiroWallet; + if (ref + .read( + publicPrivateBalanceStateProvider + .state) + .state == + "Private") { + cryptoAmountController.text = firoWallet + .availablePrivateBalance() + .localizedStringAsFixed( + locale: locale); + } else { + cryptoAmountController.text = firoWallet + .availablePublicBalance() + .localizedStringAsFixed( + locale: locale); + } } else { - cryptoAmountController.text = - (await firoWallet - .availablePublicBalance()) - .toStringAsFixed(Constants - .decimalPlacesForCoin(coin)); + cryptoAmountController.text = ref + .read(provider) + .balance + .spendable + .localizedStringAsFixed( + locale: locale); } - } else { - cryptoAmountController.text = (await ref - .read(provider) - .availableBalance) - .toStringAsFixed( - Constants.decimalPlacesForCoin( - coin)); - } - }, - ), + _cryptoAmountChanged(); + }, + ), ], ), const SizedBox( @@ -1111,10 +1451,12 @@ class _SendViewState extends ConsumerState { const Key("amountInputFieldCryptoTextFieldKey"), controller: cryptoAmountController, focusNode: _cryptoFocus, - keyboardType: const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), + keyboardType: Util.isDesktop + ? null + : const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), textAlign: TextAlign.right, inputFormatters: [ // regex to validate a crypto amount with 8 decimal places @@ -1168,11 +1510,12 @@ class _SendViewState extends ConsumerState { const Key("amountInputFieldFiatTextFieldKey"), controller: baseAmountController, focusNode: _baseFocus, - keyboardType: - const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), + keyboardType: Util.isDesktop + ? null + : const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), textAlign: TextAlign.right, inputFormatters: [ // regex to validate a fiat amount with 2 decimal places @@ -1187,26 +1530,33 @@ class _SendViewState extends ConsumerState { if (baseAmountString.isNotEmpty && baseAmountString != "." && baseAmountString != ",") { - final baseAmount = + final Amount baseAmount = baseAmountString.contains(",") ? Decimal.parse(baseAmountString - .replaceFirst(",", ".")) - : Decimal.parse(baseAmountString); + .replaceFirst(",", ".")) + .toAmount(fractionDigits: 2) + : Decimal.parse(baseAmountString) + .toAmount(fractionDigits: 2); - var _price = ref + final Decimal _price = ref .read(priceAnd24hChangeNotifierProvider) .getPrice(coin) .item1; if (_price == Decimal.zero) { - _amountToSend = Decimal.zero; + _amountToSend = 0.toAmountAsRaw( + fractionDigits: coin.decimals); } else { - _amountToSend = baseAmount <= Decimal.zero - ? Decimal.zero - : (baseAmount / _price).toDecimal( - scaleOnInfinitePrecision: - Constants.decimalPlacesForCoin( - coin)); + _amountToSend = baseAmount <= Amount.zero + ? 0.toAmountAsRaw( + fractionDigits: coin.decimals) + : (baseAmount.decimal / _price) + .toDecimal( + scaleOnInfinitePrecision: + coin.decimals, + ) + .toAmount( + fractionDigits: coin.decimals); } if (_cachedAmountToSend != null && _cachedAmountToSend == _amountToSend) { @@ -1218,21 +1568,19 @@ class _SendViewState extends ConsumerState { level: LogLevel.Info); final amountString = - Format.localizedStringAsFixed( - value: _amountToSend!, + _amountToSend!.localizedStringAsFixed( locale: ref .read( localeServiceChangeNotifierProvider) .locale, - decimalPlaces: - Constants.decimalPlacesForCoin(coin), ); _cryptoAmountChangeLock = true; cryptoAmountController.text = amountString; _cryptoAmountChangeLock = false; } else { - _amountToSend = Decimal.zero; + _amountToSend = 0.toAmountAsRaw( + fractionDigits: coin.decimals); _cryptoAmountChangeLock = true; cryptoAmountController.text = ""; _cryptoAmountChangeLock = false; @@ -1272,6 +1620,78 @@ class _SendViewState extends ConsumerState { ), ), ), + if (showCoinControl) + const SizedBox( + height: 8, + ), + if (showCoinControl) + RoundedWhiteContainer( + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Coin control", + style: + STextStyles.w500_14(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + CustomTextButton( + text: selectedUTXOs.isEmpty + ? "Select coins" + : "Selected coins (${selectedUTXOs.length})", + onTap: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 100), + ); + } + + if (mounted) { + final spendable = ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId) + .balance + .spendable; + + Amount? amount; + if (_amountToSend != null) { + amount = _amountToSend!; + + if (spendable == amount) { + // this is now a send all + } else { + amount += _currentFee; + } + } + + final result = + await Navigator.of(context) + .pushNamed( + CoinControlView.routeName, + arguments: Tuple4( + walletId, + CoinControlViewType.use, + amount, + selectedUTXOs, + ), + ); + + if (result is Set) { + setState(() { + selectedUTXOs = result; + }); + } + } + }, + ), + ], + ), + ), const SizedBox( height: 12, ), @@ -1383,11 +1803,18 @@ class _SendViewState extends ConsumerState { builder: (_) => TransactionFeeSelectionSheet( walletId: walletId, - amount: Decimal.tryParse( - cryptoAmountController - .text) ?? - Decimal.zero, + amount: (Decimal.tryParse( + cryptoAmountController + .text) ?? + Decimal.zero) + .toAmount( + fractionDigits: coin.decimals, + ), updateChosen: (String fee) { + _setCurrentFee( + fee, + true, + ); setState(() { _calculateFeesFuture = Future(() => fee); @@ -1413,6 +1840,10 @@ class _SendViewState extends ConsumerState { ConnectionState .done && snapshot.hasData) { + _setCurrentFee( + snapshot.data! as String, + false, + ); return Text( "~${snapshot.data! as String} ${coin.ticker}", style: STextStyles @@ -1465,6 +1896,11 @@ class _SendViewState extends ConsumerState { ConnectionState .done && snapshot.hasData) { + _setCurrentFee( + snapshot.data! + as String, + false, + ); return Text( "~${snapshot.data! as String} ${coin.ticker}", style: STextStyles @@ -1510,265 +1946,17 @@ class _SendViewState extends ConsumerState { onPressed: ref .watch(previewTxButtonStateProvider.state) .state - ? () async { - // wait for keyboard to disappear - FocusScope.of(context).unfocus(); - await Future.delayed( - const Duration(milliseconds: 100), - ); - final manager = ref - .read(walletsChangeNotifierProvider) - .getManager(walletId); - - // TODO: remove the need for this!! - final bool isOwnAddress = - await manager.isOwnAddress(_address!); - if (isOwnAddress) { - await showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) { - return StackDialog( - title: "Transaction failed", - message: - "Sending to self is currently disabled", - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor( - context), - child: Text( - "Ok", - style: STextStyles.button( - context) - .copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .accentColorDark), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ); - }, - ); - return; - } - - final amount = - Format.decimalAmountToSatoshis( - _amountToSend!, coin); - int availableBalance; - if ((coin == Coin.firo || - coin == Coin.firoTestNet)) { - if (ref - .read( - publicPrivateBalanceStateProvider - .state) - .state == - "Private") { - availableBalance = - Format.decimalAmountToSatoshis( - await (manager.wallet - as FiroWallet) - .availablePrivateBalance(), - coin); - } else { - availableBalance = - Format.decimalAmountToSatoshis( - await (manager.wallet - as FiroWallet) - .availablePublicBalance(), - coin); - } - } else { - availableBalance = - Format.decimalAmountToSatoshis( - await manager.availableBalance, - coin); - } - - // confirm send all - if (amount == availableBalance) { - final bool? shouldSendAll = - await showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) { - return StackDialog( - title: "Confirm send all", - message: - "You are about to send your entire balance. Would you like to continue?", - leftButton: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor( - context), - child: Text( - "Cancel", - style: STextStyles.button( - context) - .copyWith( - color: Theme.of(context) - .extension< - StackColors>()! - .accentColorDark), - ), - onPressed: () { - Navigator.of(context) - .pop(false); - }, - ), - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor( - context), - child: Text( - "Yes", - style: - STextStyles.button(context), - ), - onPressed: () { - Navigator.of(context).pop(true); - }, - ), - ); - }, - ); - - if (shouldSendAll == null || - shouldSendAll == false) { - // cancel preview - return; - } - } - - try { - bool wasCancelled = false; - - unawaited(showDialog( - context: context, - useSafeArea: false, - barrierDismissible: false, - builder: (context) { - return BuildingTransactionDialog( - onCancel: () { - wasCancelled = true; - - Navigator.of(context).pop(); - }, - ); - }, - )); - - Map txData; - - if ((coin == Coin.firo || - coin == Coin.firoTestNet) && - ref - .read( - publicPrivateBalanceStateProvider - .state) - .state != - "Private") { - txData = - await (manager.wallet as FiroWallet) - .prepareSendPublic( - address: _address!, - satoshiAmount: amount, - args: { - "feeRate": ref - .read(feeRateTypeStateProvider) - }, - ); - } else { - txData = await manager.prepareSend( - address: _address!, - satoshiAmount: amount, - args: { - "feeRate": ref - .read(feeRateTypeStateProvider) - }, - ); - } - - if (!wasCancelled && mounted) { - // pop building dialog - Navigator.of(context).pop(); - txData["note"] = noteController.text; - txData["address"] = _address; - - unawaited(Navigator.of(context).push( - RouteGenerator.getRoute( - shouldUseMaterialRoute: - RouteGenerator - .useMaterialPageRoute, - builder: (_) => - ConfirmTransactionView( - transactionInfo: txData, - walletId: walletId, - ), - settings: const RouteSettings( - name: ConfirmTransactionView - .routeName, - ), - ), - )); - } - } catch (e) { - if (mounted) { - // pop building dialog - Navigator.of(context).pop(); - - unawaited(showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) { - return StackDialog( - title: "Transaction failed", - message: e.toString(), - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor( - context), - child: Text( - "Ok", - style: STextStyles.button( - context) - .copyWith( - color: Theme.of( - context) - .extension< - StackColors>()! - .accentColorDark), - ), - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ); - }, - )); - } - } - } + ? _previewTransaction : null, style: ref .watch(previewTxButtonStateProvider.state) .state ? Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context) + .getPrimaryEnabledButtonStyle(context) : Theme.of(context) .extension()! - .getPrimaryDisabledButtonColor(context), + .getPrimaryDisabledButtonStyle(context), child: Text( "Preview", style: STextStyles.button(context), @@ -1790,3 +1978,22 @@ class _SendViewState extends ConsumerState { ); } } + +String formatAddress(String epicAddress) { + // strip http:// or https:// prefixes if the address contains an @ symbol (and is thus an epicbox address) + if ((epicAddress.startsWith("http://") || + epicAddress.startsWith("https://")) && + epicAddress.contains("@")) { + epicAddress = epicAddress.replaceAll("http://", ""); + epicAddress = epicAddress.replaceAll("https://", ""); + } + // strip mailto: prefix + if (epicAddress.startsWith("mailto:")) { + epicAddress = epicAddress.replaceAll("mailto:", ""); + } + // strip / suffix if the address contains an @ symbol (and is thus an epicbox address) + if (epicAddress.endsWith("/") && epicAddress.contains("@")) { + epicAddress = epicAddress.substring(0, epicAddress.length - 1); + } + return epicAddress; +} diff --git a/lib/pages/send_view/sub_widgets/building_transaction_dialog.dart b/lib/pages/send_view/sub_widgets/building_transaction_dialog.dart index 1f6c95df6..d930cd3ae 100644 --- a/lib/pages/send_view/sub_widgets/building_transaction_dialog.dart +++ b/lib/pages/send_view/sub_widgets/building_transaction_dialog.dart @@ -1,57 +1,49 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/utilities/assets.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/coin_image_provider.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/animated_widgets/rotating_arrows.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; -class BuildingTransactionDialog extends StatefulWidget { +class BuildingTransactionDialog extends ConsumerStatefulWidget { const BuildingTransactionDialog({ Key? key, required this.onCancel, + required this.coin, }) : super(key: key); final VoidCallback onCancel; + final Coin coin; @override - State createState() => _RestoringDialogState(); + ConsumerState createState() => + _RestoringDialogState(); } -class _RestoringDialogState extends State - with TickerProviderStateMixin { - late AnimationController? _spinController; - late Animation _spinAnimation; - +class _RestoringDialogState extends ConsumerState { late final VoidCallback onCancel; + @override void initState() { onCancel = widget.onCancel; - _spinController = AnimationController( - duration: const Duration(seconds: 2), - vsync: this, - )..repeat(); - - _spinAnimation = CurvedAnimation( - parent: _spinController!, - curve: Curves.linear, - ); - super.initState(); } - @override - void dispose() { - _spinController?.dispose(); - _spinController = null; - - super.dispose(); - } - @override Widget build(BuildContext context) { + final assetPath = ref.watch( + coinImageSecondaryProvider( + widget.coin, + ), + ); + if (Util.isDesktop) { return Column( mainAxisSize: MainAxisSize.min, @@ -63,16 +55,14 @@ class _RestoringDialogState extends State const SizedBox( height: 40, ), - RotationTransition( - turns: _spinAnimation, - child: SvgPicture.asset( - Assets.svg.arrowRotate, - color: - Theme.of(context).extension()!.accentColorDark, - width: 24, - height: 24, - ), - ), + assetPath.endsWith(".gif") + ? Image.file(File( + assetPath, + )) + : const RotatingArrows( + width: 40, + height: 40, + ), const SizedBox( height: 40, ), @@ -90,34 +80,66 @@ class _RestoringDialogState extends State onWillPop: () async { return false; }, - child: StackDialog( - title: "Generating transaction", - // // TODO get message from design team - // message: "", - icon: RotationTransition( - turns: _spinAnimation, - child: SvgPicture.asset( - Assets.svg.arrowRotate, - color: - Theme.of(context).extension()!.accentColorDark, - width: 24, - height: 24, - ), - ), - rightButton: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - child: Text( - "Cancel", - style: STextStyles.itemSubtitle12(context), - ), - onPressed: () { - Navigator.of(context).pop(); - onCancel.call(); - }, - ), - ), + child: assetPath.endsWith(".gif") + ? StackDialogBase( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + Image.file(File( + assetPath, + )), + Text( + "Generating transaction", + textAlign: TextAlign.center, + style: STextStyles.pageTitleH2(context), + ), + const SizedBox( + height: 32, + ), + Row( + children: [ + const Spacer(), + Expanded( + child: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Cancel", + style: STextStyles.itemSubtitle12(context), + ), + onPressed: () { + Navigator.of(context).pop(); + onCancel.call(); + }, + ), + ) + ], + ), + ], + ), + ) + : StackDialog( + title: "Generating transaction", + icon: const RotatingArrows( + width: 24, + height: 24, + ), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Cancel", + style: STextStyles.itemSubtitle12(context), + ), + onPressed: () { + Navigator.of(context).pop(); + onCancel.call(); + }, + ), + ), ); } } diff --git a/lib/pages/send_view/sub_widgets/firo_balance_selection_sheet.dart b/lib/pages/send_view/sub_widgets/firo_balance_selection_sheet.dart index d6de3c6ee..175e1442a 100644 --- a/lib/pages/send_view/sub_widgets/firo_balance_selection_sheet.dart +++ b/lib/pages/send_view/sub_widgets/firo_balance_selection_sheet.dart @@ -1,14 +1,12 @@ -import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; -import 'package:stackwallet/widgets/animated_text.dart'; class FiroBalanceSelectionSheet extends ConsumerStatefulWidget { const FiroBalanceSelectionSheet({ @@ -153,27 +151,18 @@ class _FiroBalanceSelectionSheetState const SizedBox( width: 2, ), - FutureBuilder( - future: firoWallet.availablePrivateBalance(), - builder: - (context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - return Text( - "${snapshot.data!.toStringAsFixed(8)} ${manager.coin.ticker}", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.left, - ); - } else { - return AnimatedText( - stringsToLoopThrough: - stringsToLoopThrough, - style: STextStyles.itemSubtitle(context), - ); - } - }, - ) + Text( + "${firoWallet.availablePrivateBalance().localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider + .select( + (value) => value.locale, + ), + ), + )} ${manager.coin.ticker}", + style: STextStyles.itemSubtitle(context), + textAlign: TextAlign.left, + ), ], ), // ], @@ -243,29 +232,18 @@ class _FiroBalanceSelectionSheetState const SizedBox( width: 2, ), - FutureBuilder( - future: firoWallet.availablePublicBalance(), - builder: - (context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - return Text( - "${snapshot.data!.toStringAsFixed(8)} ${manager.coin.ticker}", - style: STextStyles.itemSubtitle(context), - textAlign: TextAlign.left, - ); - } else { - return AnimatedText( - stringsToLoopThrough: - stringsToLoopThrough, - style: STextStyles.itemSubtitle(context), - ); - } - }, - ) - // ], - // ), + Text( + "${firoWallet.availablePublicBalance().localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider + .select( + (value) => value.locale, + ), + ), + )} ${manager.coin.ticker}", + style: STextStyles.itemSubtitle(context), + textAlign: TextAlign.left, + ), ], ), ), diff --git a/lib/pages/send_view/sub_widgets/sending_transaction_dialog.dart b/lib/pages/send_view/sub_widgets/sending_transaction_dialog.dart index e5c86fe2e..2442a7324 100644 --- a/lib/pages/send_view/sub_widgets/sending_transaction_dialog.dart +++ b/lib/pages/send_view/sub_widgets/sending_transaction_dialog.dart @@ -1,51 +1,57 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:lottie/lottie.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/coin_image_provider.dart'; import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; -class SendingTransactionDialog extends StatefulWidget { +class SendingTransactionDialog extends ConsumerStatefulWidget { const SendingTransactionDialog({ Key? key, + required this.coin, + required this.controller, }) : super(key: key); + final Coin coin; + final ProgressAndSuccessController controller; + @override - State createState() => _RestoringDialogState(); + ConsumerState createState() => + _RestoringDialogState(); } -class _RestoringDialogState extends State - with TickerProviderStateMixin { - late AnimationController? _spinController; - late Animation _spinAnimation; +class _RestoringDialogState extends ConsumerState { + late ProgressAndSuccessController? _progressAndSuccessController; @override void initState() { - _spinController = AnimationController( - duration: const Duration(seconds: 2), - vsync: this, - )..repeat(); - - _spinAnimation = CurvedAnimation( - parent: _spinController!, - curve: Curves.linear, - ); + _progressAndSuccessController = widget.controller; super.initState(); } @override void dispose() { - _spinController?.dispose(); - _spinController = null; + _progressAndSuccessController = null; super.dispose(); } @override Widget build(BuildContext context) { + final assetPath = ref.watch( + coinImageSecondaryProvider( + widget.coin, + ), + ); + if (Util.isDesktop) { return DesktopDialog( child: Padding( @@ -60,17 +66,13 @@ class _RestoringDialogState extends State const SizedBox( height: 40, ), - RotationTransition( - turns: _spinAnimation, - child: SvgPicture.asset( - Assets.svg.arrowRotate, - color: Theme.of(context) - .extension()! - .accentColorDark, - width: 24, - height: 24, - ), - ), + assetPath.endsWith(".gif") + ? Image.file( + File(assetPath), + ) + : ProgressAndSuccess( + controller: _progressAndSuccessController!, + ), ], ), ), @@ -80,22 +82,150 @@ class _RestoringDialogState extends State onWillPop: () async { return false; }, - child: StackDialog( - title: "Sending transaction", - // // TODO get message from design team - // message: "", - icon: RotationTransition( - turns: _spinAnimation, - child: SvgPicture.asset( - Assets.svg.arrowRotate, - color: - Theme.of(context).extension()!.accentColorDark, - width: 24, - height: 24, - ), - ), - ), + child: assetPath.endsWith(".gif") + ? StackDialogBase( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + Image.file( + File(assetPath), + ), + Text( + "Sending transaction", + textAlign: TextAlign.center, + style: STextStyles.pageTitleH2(context), + ), + const SizedBox( + height: 32, + ), + ], + ), + ) + : StackDialog( + title: "Sending transaction", + icon: ProgressAndSuccess( + controller: _progressAndSuccessController!, + ), + ), ); } } } + +class ProgressAndSuccessController { + VoidCallback? triggerSuccess; +} + +class ProgressAndSuccess extends StatefulWidget { + const ProgressAndSuccess({ + Key? key, + this.height = 24, + this.width = 24, + required this.controller, + }) : super(key: key); + + final double height; + final double width; + final ProgressAndSuccessController controller; + + @override + State createState() => _ProgressAndSuccessState(); +} + +class _ProgressAndSuccessState extends State + with TickerProviderStateMixin { + late final AnimationController controller1; + late final AnimationController controller2; + + CrossFadeState _crossFadeState = CrossFadeState.showFirst; + + bool _triggered = false; + + @override + void initState() { + controller1 = AnimationController(vsync: this); + controller2 = AnimationController(vsync: this); + + controller1.addListener(() => setState(() {})); + controller2.addListener(() => setState(() {})); + + controller1.addStatusListener((status) { + if (status == AnimationStatus.completed && _triggered) { + controller2.forward(); + setState(() { + _crossFadeState = CrossFadeState.showSecond; + }); + } + }); + + widget.controller.triggerSuccess = () { + controller1.forward(); + _triggered = true; + }; + + super.initState(); + } + + @override + void dispose() { + controller1.dispose(); + controller2.dispose(); + + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedCrossFade( + crossFadeState: _crossFadeState, + firstChild: Lottie.asset( + Assets.lottie.iconSend, + controller: controller1, + width: widget.width, + delegates: LottieDelegates( + values: [ + ValueDelegate.color( + const ["**"], + value: + Theme.of(context).extension()!.accentColorDark, + ), + ValueDelegate.strokeColor( + const ["**"], + value: + Theme.of(context).extension()!.accentColorDark, + ), + ], + ), + height: widget.height, + onLoaded: (composition) { + final start = composition.markers[0].start; + final end = composition.markers[1].start; + + setState(() { + controller1.duration = composition.duration; + }); + controller1.repeat( + min: start, + max: end, + period: composition.duration * (end - start), + ); + }, + ), + secondChild: Lottie.asset( + Assets.lottie.loaderAndCheckmark, + controller: controller2, + width: widget.width, + height: widget.height, + onLoaded: (composition) { + setState(() { + controller2.duration = composition.duration * + (composition.markers.last.end - composition.markers[1].start); + controller2.value = composition.markers[1].start; + }); + }, + ), + duration: const Duration(microseconds: 1), + ); + } +} diff --git a/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart b/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart index 8ad6d0ca5..f2458349e 100644 --- a/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart +++ b/lib/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart @@ -3,16 +3,18 @@ import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/models/paymint/fee_object_model.dart'; +import 'package:stackwallet/pages/token_view/token_view.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart'; import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; -import 'package:stackwallet/utilities/format.dart'; +import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/animated_text.dart'; final feeSheetSessionCacheProvider = @@ -21,9 +23,9 @@ final feeSheetSessionCacheProvider = }); class FeeSheetSessionCache extends ChangeNotifier { - final Map fast = {}; - final Map average = {}; - final Map slow = {}; + final Map fast = {}; + final Map average = {}; + final Map slow = {}; void notify() => notifyListeners(); } @@ -34,11 +36,13 @@ class TransactionFeeSelectionSheet extends ConsumerStatefulWidget { required this.walletId, required this.amount, required this.updateChosen, + this.isToken = false, }) : super(key: key); final String walletId; - final Decimal amount; + final Amount amount; final Function updateChosen; + final bool isToken; @override ConsumerState createState() => @@ -48,7 +52,7 @@ class TransactionFeeSelectionSheet extends ConsumerStatefulWidget { class _TransactionFeeSelectionSheetState extends ConsumerState { late final String walletId; - late final Decimal amount; + late final Amount amount; FeeObject? feeObject; @@ -59,8 +63,8 @@ class _TransactionFeeSelectionSheetState "Calculating...", ]; - Future feeFor({ - required int amount, + Future feeFor({ + required Amount amount, required FeeRateType feeRateType, required int feeRate, required Coin coin, @@ -68,88 +72,82 @@ class _TransactionFeeSelectionSheetState switch (feeRateType) { case FeeRateType.fast: if (ref.read(feeSheetSessionCacheProvider).fast[amount] == null) { - final manager = - ref.read(walletsChangeNotifierProvider).getManager(walletId); + if (widget.isToken == false) { + final manager = + ref.read(walletsChangeNotifierProvider).getManager(walletId); - if (coin == Coin.monero || coin == Coin.wownero) { - final fee = await manager.estimateFeeFor( - amount, MoneroTransactionPriority.fast.raw!); - ref.read(feeSheetSessionCacheProvider).fast[amount] = - Format.satoshisToAmount( - fee, - coin: coin, - ); - } else if ((coin == Coin.firo || coin == Coin.firoTestNet) && - ref.read(publicPrivateBalanceStateProvider.state).state != - "Private") { - ref.read(feeSheetSessionCacheProvider).fast[amount] = - Format.satoshisToAmount( - await (manager.wallet as FiroWallet) - .estimateFeeForPublic(amount, feeRate), - coin: coin); + if (coin == Coin.monero || coin == Coin.wownero) { + final fee = await manager.estimateFeeFor( + amount, MoneroTransactionPriority.fast.raw!); + ref.read(feeSheetSessionCacheProvider).fast[amount] = fee; + } else if ((coin == Coin.firo || coin == Coin.firoTestNet) && + ref.read(publicPrivateBalanceStateProvider.state).state != + "Private") { + ref.read(feeSheetSessionCacheProvider).fast[amount] = + await (manager.wallet as FiroWallet) + .estimateFeeForPublic(amount, feeRate); + } else { + ref.read(feeSheetSessionCacheProvider).fast[amount] = + await manager.estimateFeeFor(amount, feeRate); + } } else { - ref.read(feeSheetSessionCacheProvider).fast[amount] = - Format.satoshisToAmount( - await manager.estimateFeeFor(amount, feeRate), - coin: coin); + final tokenWallet = ref.read(tokenServiceProvider)!; + final fee = tokenWallet.estimateFeeFor(feeRate); + ref.read(feeSheetSessionCacheProvider).fast[amount] = fee; } } return ref.read(feeSheetSessionCacheProvider).fast[amount]!; case FeeRateType.average: if (ref.read(feeSheetSessionCacheProvider).average[amount] == null) { - final manager = - ref.read(walletsChangeNotifierProvider).getManager(walletId); - if (coin == Coin.monero || coin == Coin.wownero) { - final fee = await manager.estimateFeeFor( - amount, MoneroTransactionPriority.regular.raw!); - ref.read(feeSheetSessionCacheProvider).average[amount] = - Format.satoshisToAmount( - fee, - coin: coin, - ); - } else if ((coin == Coin.firo || coin == Coin.firoTestNet) && - ref.read(publicPrivateBalanceStateProvider.state).state != - "Private") { - ref.read(feeSheetSessionCacheProvider).average[amount] = - Format.satoshisToAmount( - await (manager.wallet as FiroWallet) - .estimateFeeForPublic(amount, feeRate), - coin: coin); + if (widget.isToken == false) { + final manager = + ref.read(walletsChangeNotifierProvider).getManager(walletId); + if (coin == Coin.monero || coin == Coin.wownero) { + final fee = await manager.estimateFeeFor( + amount, MoneroTransactionPriority.regular.raw!); + ref.read(feeSheetSessionCacheProvider).average[amount] = fee; + } else if ((coin == Coin.firo || coin == Coin.firoTestNet) && + ref.read(publicPrivateBalanceStateProvider.state).state != + "Private") { + ref.read(feeSheetSessionCacheProvider).average[amount] = + await (manager.wallet as FiroWallet) + .estimateFeeForPublic(amount, feeRate); + } else { + ref.read(feeSheetSessionCacheProvider).average[amount] = + await manager.estimateFeeFor(amount, feeRate); + } } else { - ref.read(feeSheetSessionCacheProvider).average[amount] = - Format.satoshisToAmount( - await manager.estimateFeeFor(amount, feeRate), - coin: coin); + final tokenWallet = ref.read(tokenServiceProvider)!; + final fee = tokenWallet.estimateFeeFor(feeRate); + ref.read(feeSheetSessionCacheProvider).average[amount] = fee; } } return ref.read(feeSheetSessionCacheProvider).average[amount]!; case FeeRateType.slow: if (ref.read(feeSheetSessionCacheProvider).slow[amount] == null) { - final manager = - ref.read(walletsChangeNotifierProvider).getManager(walletId); - if (coin == Coin.monero || coin == Coin.wownero) { - final fee = await manager.estimateFeeFor( - amount, MoneroTransactionPriority.slow.raw!); - ref.read(feeSheetSessionCacheProvider).slow[amount] = - Format.satoshisToAmount( - fee, - coin: coin, - ); - } else if ((coin == Coin.firo || coin == Coin.firoTestNet) && - ref.read(publicPrivateBalanceStateProvider.state).state != - "Private") { - ref.read(feeSheetSessionCacheProvider).slow[amount] = - Format.satoshisToAmount( - await (manager.wallet as FiroWallet) - .estimateFeeForPublic(amount, feeRate), - coin: coin); + if (widget.isToken == false) { + final manager = + ref.read(walletsChangeNotifierProvider).getManager(walletId); + if (coin == Coin.monero || coin == Coin.wownero) { + final fee = await manager.estimateFeeFor( + amount, MoneroTransactionPriority.slow.raw!); + ref.read(feeSheetSessionCacheProvider).slow[amount] = fee; + } else if ((coin == Coin.firo || coin == Coin.firoTestNet) && + ref.read(publicPrivateBalanceStateProvider.state).state != + "Private") { + ref.read(feeSheetSessionCacheProvider).slow[amount] = + await (manager.wallet as FiroWallet) + .estimateFeeForPublic(amount, feeRate); + } else { + ref.read(feeSheetSessionCacheProvider).slow[amount] = + await manager.estimateFeeFor(amount, feeRate); + } } else { - ref.read(feeSheetSessionCacheProvider).slow[amount] = - Format.satoshisToAmount( - await manager.estimateFeeFor(amount, feeRate), - coin: coin); + final tokenWallet = ref.read(tokenServiceProvider)!; + final fee = tokenWallet.estimateFeeFor(feeRate); + ref.read(feeSheetSessionCacheProvider).slow[amount] = fee; } } return ref.read(feeSheetSessionCacheProvider).slow[amount]!; @@ -231,7 +229,9 @@ class _TransactionFeeSelectionSheetState height: 36, ), FutureBuilder( - future: manager.fees, + future: widget.isToken + ? ref.read(tokenServiceProvider)!.fees + : manager.fees, builder: (context, AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) { @@ -320,23 +320,25 @@ class _TransactionFeeSelectionSheetState if (feeObject != null) FutureBuilder( future: feeFor( - coin: manager.coin, - feeRateType: FeeRateType.fast, - feeRate: feeObject!.fast, - amount: Format - .decimalAmountToSatoshis( - amount, manager.coin)), + coin: manager.coin, + feeRateType: FeeRateType.fast, + feeRate: feeObject!.fast, + amount: amount, + ), // future: manager.estimateFeeFor( // Format.decimalAmountToSatoshis( // amount), // feeObject!.fast), builder: (_, - AsyncSnapshot snapshot) { + AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) { return Text( - "(~${snapshot.data!} ${manager.coin.ticker})", + "(~${snapshot.data!.decimal.toStringAsFixed( + manager.coin.decimals, + )}" + " ${manager.coin.ticker})", style: STextStyles.itemSubtitle( context), textAlign: TextAlign.left, @@ -356,13 +358,15 @@ class _TransactionFeeSelectionSheetState const SizedBox( height: 2, ), - if (feeObject == null) + if (feeObject == null && + manager.coin != Coin.ethereum) AnimatedText( stringsToLoopThrough: stringsToLoopThrough, style: STextStyles.itemSubtitle(context), ), - if (feeObject != null) + if (feeObject != null && + manager.coin != Coin.ethereum) Text( estimatedTimeToBeIncludedInNextBlock( Constants.targetBlockTimeInSeconds( @@ -452,23 +456,23 @@ class _TransactionFeeSelectionSheetState if (feeObject != null) FutureBuilder( future: feeFor( - coin: manager.coin, - feeRateType: FeeRateType.average, - feeRate: feeObject!.medium, - amount: Format - .decimalAmountToSatoshis( - amount, manager.coin)), + coin: manager.coin, + feeRateType: FeeRateType.average, + feeRate: feeObject!.medium, + amount: amount, + ), // future: manager.estimateFeeFor( // Format.decimalAmountToSatoshis( // amount), // feeObject!.fast), builder: (_, - AsyncSnapshot snapshot) { + AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) { return Text( - "(~${snapshot.data!} ${manager.coin.ticker})", + "(~${snapshot.data!.decimal.toStringAsFixed(manager.coin.decimals)}" + " ${manager.coin.ticker})", style: STextStyles.itemSubtitle( context), textAlign: TextAlign.left, @@ -488,13 +492,15 @@ class _TransactionFeeSelectionSheetState const SizedBox( height: 2, ), - if (feeObject == null) + if (feeObject == null && + manager.coin != Coin.ethereum) AnimatedText( stringsToLoopThrough: stringsToLoopThrough, style: STextStyles.itemSubtitle(context), ), - if (feeObject != null) + if (feeObject != null && + manager.coin != Coin.ethereum) Text( estimatedTimeToBeIncludedInNextBlock( Constants.targetBlockTimeInSeconds( @@ -523,7 +529,6 @@ class _TransactionFeeSelectionSheetState FeeRateType.slow; } String? fee = getAmount(FeeRateType.slow, manager.coin); - print("fee $fee"); if (fee != null) { widget.updateChosen(fee); } @@ -586,23 +591,22 @@ class _TransactionFeeSelectionSheetState if (feeObject != null) FutureBuilder( future: feeFor( - coin: manager.coin, - feeRateType: FeeRateType.slow, - feeRate: feeObject!.slow, - amount: Format - .decimalAmountToSatoshis( - amount, manager.coin)), + coin: manager.coin, + feeRateType: FeeRateType.slow, + feeRate: feeObject!.slow, + amount: amount, + ), // future: manager.estimateFeeFor( // Format.decimalAmountToSatoshis( // amount), // feeObject!.fast), builder: (_, - AsyncSnapshot snapshot) { + AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) { return Text( - "(~${snapshot.data!} ${manager.coin.ticker})", + "(~${snapshot.data!.decimal.toStringAsFixed(manager.coin.decimals)} ${manager.coin.ticker})", style: STextStyles.itemSubtitle( context), textAlign: TextAlign.left, @@ -622,13 +626,15 @@ class _TransactionFeeSelectionSheetState const SizedBox( height: 2, ), - if (feeObject == null) + if (feeObject == null && + manager.coin != Coin.ethereum) AnimatedText( stringsToLoopThrough: stringsToLoopThrough, style: STextStyles.itemSubtitle(context), ), - if (feeObject != null) + if (feeObject != null && + manager.coin != Coin.ethereum) Text( estimatedTimeToBeIncludedInNextBlock( Constants.targetBlockTimeInSeconds( @@ -660,18 +666,12 @@ class _TransactionFeeSelectionSheetState String? getAmount(FeeRateType feeRateType, Coin coin) { try { - print(feeRateType); - var amount = Format.decimalAmountToSatoshis(this.amount, coin); - print(amount); - print(ref.read(feeSheetSessionCacheProvider).fast); - print(ref.read(feeSheetSessionCacheProvider).average); - print(ref.read(feeSheetSessionCacheProvider).slow); switch (feeRateType) { case FeeRateType.fast: if (ref.read(feeSheetSessionCacheProvider).fast[amount] != null) { return (ref.read(feeSheetSessionCacheProvider).fast[amount] as Decimal) - .toString(); + .toStringAsFixed(coin.decimals); } return null; @@ -679,22 +679,20 @@ class _TransactionFeeSelectionSheetState if (ref.read(feeSheetSessionCacheProvider).average[amount] != null) { return (ref.read(feeSheetSessionCacheProvider).average[amount] as Decimal) - .toString(); + .toStringAsFixed(coin.decimals); } return null; case FeeRateType.slow: - print(ref.read(feeSheetSessionCacheProvider).slow); - print(ref.read(feeSheetSessionCacheProvider).slow[amount]); if (ref.read(feeSheetSessionCacheProvider).slow[amount] != null) { return (ref.read(feeSheetSessionCacheProvider).slow[amount] as Decimal) - .toString(); + .toStringAsFixed(coin.decimals); } return null; } } catch (e, s) { - print("$e $s"); + Logging.instance.log("$e $s", level: LogLevel.Warning); return null; } } diff --git a/lib/pages/send_view/token_send_view.dart b/lib/pages/send_view/token_send_view.dart new file mode 100644 index 000000000..d0eb505a2 --- /dev/null +++ b/lib/pages/send_view/token_send_view.dart @@ -0,0 +1,1237 @@ +import 'dart:async'; + +import 'package:decimal/decimal.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/models/send_view_auto_fill_data.dart'; +import 'package:stackwallet/pages/address_book_views/address_book_view.dart'; +import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart'; +import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dialog.dart'; +import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart'; +import 'package:stackwallet/pages/token_view/token_view.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart'; +import 'package:stackwallet/providers/ui/preview_tx_button_state_provider.dart'; +import 'package:stackwallet/route_generator.dart'; +import 'package:stackwallet/services/coins/manager.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/address_utils.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/barcode_scanner_interface.dart'; +import 'package:stackwallet/utilities/clipboard_interface.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/prefs.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/animated_text.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/eth_token_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/qrcode_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; + +class TokenSendView extends ConsumerStatefulWidget { + const TokenSendView({ + Key? key, + required this.walletId, + required this.coin, + required this.tokenContract, + this.autoFillData, + this.clipboard = const ClipboardWrapper(), + this.barcodeScanner = const BarcodeScannerWrapper(), + }) : super(key: key); + + static const String routeName = "/tokenSendView"; + + final String walletId; + final Coin coin; + final EthContract tokenContract; + final SendViewAutoFillData? autoFillData; + final ClipboardInterface clipboard; + final BarcodeScannerInterface barcodeScanner; + + @override + ConsumerState createState() => _TokenSendViewState(); +} + +class _TokenSendViewState extends ConsumerState { + late final String walletId; + late final Coin coin; + late final EthContract tokenContract; + late final ClipboardInterface clipboard; + late final BarcodeScannerInterface scanner; + + late TextEditingController sendToController; + late TextEditingController cryptoAmountController; + late TextEditingController baseAmountController; + late TextEditingController noteController; + late TextEditingController feeController; + + late final SendViewAutoFillData? _data; + + final _addressFocusNode = FocusNode(); + final _noteFocusNode = FocusNode(); + final _cryptoFocus = FocusNode(); + final _baseFocus = FocusNode(); + + Amount? _amountToSend; + Amount? _cachedAmountToSend; + String? _address; + + bool _addressToggleFlag = false; + + bool _cryptoAmountChangeLock = false; + late VoidCallback onCryptoAmountChanged; + + final updateFeesTimerDuration = const Duration(milliseconds: 500); + + Timer? _cryptoAmountChangedFeeUpdateTimer; + Timer? _baseAmountChangedFeeUpdateTimer; + late Future _calculateFeesFuture; + String cachedFees = ""; + + void _onTokenSendViewPasteAddressFieldButtonPressed() async { + final ClipboardData? data = await clipboard.getData(Clipboard.kTextPlain); + if (data?.text != null && data!.text!.isNotEmpty) { + String content = data.text!.trim(); + if (content.contains("\n")) { + content = content.substring(0, content.indexOf("\n")); + } + sendToController.text = content.trim(); + _address = content.trim(); + + _updatePreviewButtonState(_address, _amountToSend); + setState(() { + _addressToggleFlag = sendToController.text.isNotEmpty; + }); + } + } + + void _onTokenSendViewScanQrButtonPressed() async { + try { + // ref + // .read( + // shouldShowLockscreenOnResumeStateProvider + // .state) + // .state = false; + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + + final qrResult = await scanner.scan(); + + // Future.delayed( + // const Duration(seconds: 2), + // () => ref + // .read( + // shouldShowLockscreenOnResumeStateProvider + // .state) + // .state = true, + // ); + + Logging.instance.log("qrResult content: ${qrResult.rawContent}", + level: LogLevel.Info); + + final results = AddressUtils.parseUri(qrResult.rawContent); + + Logging.instance.log("qrResult parsed: $results", level: LogLevel.Info); + + if (results.isNotEmpty && results["scheme"] == coin.uriScheme) { + // auto fill address + _address = (results["address"] ?? "").trim(); + sendToController.text = _address!; + + // autofill notes field + if (results["message"] != null) { + noteController.text = results["message"]!; + } else if (results["label"] != null) { + noteController.text = results["label"]!; + } + + // autofill amount field + if (results["amount"] != null) { + final Amount amount = Decimal.parse(results["amount"]!).toAmount( + fractionDigits: tokenContract.decimals, + ); + cryptoAmountController.text = amount.localizedStringAsFixed( + locale: ref.read(localeServiceChangeNotifierProvider).locale, + ); + _amountToSend = amount; + } + + _updatePreviewButtonState(_address, _amountToSend); + setState(() { + _addressToggleFlag = sendToController.text.isNotEmpty; + }); + + // now check for non standard encoded basic address + } else if (ref + .read(walletsChangeNotifierProvider) + .getManager(walletId) + .validateAddress(qrResult.rawContent)) { + _address = qrResult.rawContent.trim(); + sendToController.text = _address ?? ""; + + _updatePreviewButtonState(_address, _amountToSend); + setState(() { + _addressToggleFlag = sendToController.text.isNotEmpty; + }); + } + } on PlatformException catch (e, s) { + // ref + // .read( + // shouldShowLockscreenOnResumeStateProvider + // .state) + // .state = true; + // here we ignore the exception caused by not giving permission + // to use the camera to scan a qr code + Logging.instance.log( + "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s", + level: LogLevel.Warning); + } + } + + void _onFiatAmountFieldChanged(String baseAmountString) { + if (baseAmountString.isNotEmpty && + baseAmountString != "." && + baseAmountString != ",") { + final baseAmount = Amount.fromDecimal( + baseAmountString.contains(",") + ? Decimal.parse(baseAmountString.replaceFirst(",", ".")) + : Decimal.parse(baseAmountString), + fractionDigits: tokenContract.decimals, + ); + + final _price = ref + .read(priceAnd24hChangeNotifierProvider) + .getTokenPrice(tokenContract.address) + .item1; + + if (_price == Decimal.zero) { + _amountToSend = Amount.zero; + } else { + _amountToSend = baseAmount <= Amount.zero + ? Amount.zero + : Amount.fromDecimal( + (baseAmount.decimal / _price).toDecimal( + scaleOnInfinitePrecision: tokenContract.decimals), + fractionDigits: tokenContract.decimals); + } + if (_cachedAmountToSend != null && _cachedAmountToSend == _amountToSend) { + return; + } + _cachedAmountToSend = _amountToSend; + Logging.instance.log("it changed $_amountToSend $_cachedAmountToSend", + level: LogLevel.Info); + + _cryptoAmountChangeLock = true; + cryptoAmountController.text = _amountToSend!.localizedStringAsFixed( + locale: ref.read(localeServiceChangeNotifierProvider).locale, + ); + _cryptoAmountChangeLock = false; + } else { + _amountToSend = Amount.zero; + _cryptoAmountChangeLock = true; + cryptoAmountController.text = ""; + _cryptoAmountChangeLock = false; + } + // setState(() { + // _calculateFeesFuture = calculateFees( + // Format.decimalAmountToSatoshis( + // _amountToSend!)); + // }); + _updatePreviewButtonState(_address, _amountToSend); + } + + void _cryptoAmountChanged() async { + if (!_cryptoAmountChangeLock) { + final String cryptoAmount = cryptoAmountController.text; + if (cryptoAmount.isNotEmpty && + cryptoAmount != "." && + cryptoAmount != ",") { + _amountToSend = Amount.fromDecimal( + cryptoAmount.contains(",") + ? Decimal.parse(cryptoAmount.replaceFirst(",", ".")) + : Decimal.parse(cryptoAmount), + fractionDigits: tokenContract.decimals); + if (_cachedAmountToSend != null && + _cachedAmountToSend == _amountToSend) { + return; + } + _cachedAmountToSend = _amountToSend; + Logging.instance.log("it changed $_amountToSend $_cachedAmountToSend", + level: LogLevel.Info); + + final price = ref + .read(priceAnd24hChangeNotifierProvider) + .getTokenPrice(tokenContract.address) + .item1; + + if (price > Decimal.zero) { + baseAmountController.text = (_amountToSend!.decimal * price) + .toAmount( + fractionDigits: 2, + ) + .localizedStringAsFixed( + locale: ref.read(localeServiceChangeNotifierProvider).locale, + ); + } + } else { + _amountToSend = null; + baseAmountController.text = ""; + } + + _updatePreviewButtonState(_address, _amountToSend); + + _cryptoAmountChangedFeeUpdateTimer?.cancel(); + _cryptoAmountChangedFeeUpdateTimer = Timer(updateFeesTimerDuration, () { + if (coin != Coin.epicCash && !_baseFocus.hasFocus) { + setState(() { + _calculateFeesFuture = calculateFees(); + }); + } + }); + } + } + + void _baseAmountChanged() { + _baseAmountChangedFeeUpdateTimer?.cancel(); + _baseAmountChangedFeeUpdateTimer = Timer(updateFeesTimerDuration, () { + if (coin != Coin.epicCash && !_cryptoFocus.hasFocus) { + setState(() { + _calculateFeesFuture = calculateFees(); + }); + } + }); + } + + String? _updateInvalidAddressText(String address, Manager manager) { + if (_data != null && _data!.contactLabel == address) { + return null; + } + if (address.isNotEmpty && !manager.validateAddress(address)) { + return "Invalid address"; + } + return null; + } + + void _updatePreviewButtonState(String? address, Amount? amount) { + final isValidAddress = ref + .read(walletsChangeNotifierProvider) + .getManager(walletId) + .validateAddress(address ?? ""); + ref.read(previewTxButtonStateProvider.state).state = + (isValidAddress && amount != null && amount > Amount.zero); + } + + Future calculateFees() async { + final wallet = ref.read(tokenServiceProvider)!; + final feeObject = await wallet.fees; + + late final int feeRate; + + switch (ref.read(feeRateTypeStateProvider.state).state) { + case FeeRateType.fast: + feeRate = feeObject.fast; + break; + case FeeRateType.average: + feeRate = feeObject.medium; + break; + case FeeRateType.slow: + feeRate = feeObject.slow; + break; + } + + final Amount fee = wallet.estimateFeeFor(feeRate); + cachedFees = fee.localizedStringAsFixed( + locale: ref.read(localeServiceChangeNotifierProvider).locale, + ); + + return cachedFees; + } + + Future _previewTransaction() async { + // wait for keyboard to disappear + FocusScope.of(context).unfocus(); + await Future.delayed( + const Duration(milliseconds: 100), + ); + final manager = + ref.read(walletsChangeNotifierProvider).getManager(walletId); + final tokenWallet = ref.read(tokenServiceProvider)!; + + final Amount amount = _amountToSend!; + + // // confirm send all + // if (amount == availableBalance) { + // bool? shouldSendAll; + // if (mounted) { + // shouldSendAll = await showDialog( + // context: context, + // useSafeArea: false, + // barrierDismissible: true, + // builder: (context) { + // return StackDialog( + // title: "Confirm send all", + // message: + // "You are about to send your entire balance. Would you like to continue?", + // leftButton: TextButton( + // style: Theme.of(context) + // .extension()! + // .getSecondaryEnabledButtonStyle(context), + // child: Text( + // "Cancel", + // style: STextStyles.button(context).copyWith( + // color: Theme.of(context) + // .extension()! + // .accentColorDark), + // ), + // onPressed: () { + // Navigator.of(context).pop(false); + // }, + // ), + // rightButton: TextButton( + // style: Theme.of(context) + // .extension()! + // .getPrimaryEnabledButtonStyle(context), + // child: Text( + // "Yes", + // style: STextStyles.button(context), + // ), + // onPressed: () { + // Navigator.of(context).pop(true); + // }, + // ), + // ); + // }, + // ); + // } + // + // if (shouldSendAll == null || shouldSendAll == false) { + // // cancel preview + // return; + // } + // } + + try { + bool wasCancelled = false; + + if (mounted) { + unawaited( + showDialog( + context: context, + useSafeArea: false, + barrierDismissible: false, + builder: (context) { + return BuildingTransactionDialog( + coin: manager.coin, + onCancel: () { + wasCancelled = true; + + Navigator.of(context).pop(); + }, + ); + }, + ), + ); + } + + final time = Future.delayed( + const Duration( + milliseconds: 2500, + ), + ); + + Map txData; + Future> txDataFuture; + + txDataFuture = tokenWallet.prepareSend( + address: _address!, + amount: amount, + args: { + "feeRate": ref.read(feeRateTypeStateProvider), + }, + ); + + final results = await Future.wait([ + txDataFuture, + time, + ]); + + txData = results.first as Map; + + if (!wasCancelled && mounted) { + // pop building dialog + Navigator.of(context).pop(); + txData["note"] = noteController.text; + + txData["address"] = _address; + + unawaited(Navigator.of(context).push( + RouteGenerator.getRoute( + shouldUseMaterialRoute: RouteGenerator.useMaterialPageRoute, + builder: (_) => ConfirmTransactionView( + transactionInfo: txData, + walletId: walletId, + isTokenTx: true, + ), + settings: const RouteSettings( + name: ConfirmTransactionView.routeName, + ), + ), + )); + } + } catch (e) { + if (mounted) { + // pop building dialog + Navigator.of(context).pop(); + + unawaited(showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return StackDialog( + title: "Transaction failed", + message: e.toString(), + rightButton: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context), + child: Text( + "Ok", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ); + }, + )); + } + } + } + + @override + void initState() { + ref.refresh(feeSheetSessionCacheProvider); + + _calculateFeesFuture = calculateFees(); + _data = widget.autoFillData; + walletId = widget.walletId; + coin = widget.coin; + tokenContract = widget.tokenContract; + clipboard = widget.clipboard; + scanner = widget.barcodeScanner; + + sendToController = TextEditingController(); + cryptoAmountController = TextEditingController(); + baseAmountController = TextEditingController(); + noteController = TextEditingController(); + feeController = TextEditingController(); + + onCryptoAmountChanged = _cryptoAmountChanged; + cryptoAmountController.addListener(onCryptoAmountChanged); + baseAmountController.addListener(_baseAmountChanged); + + if (_data != null) { + if (_data!.amount != null) { + cryptoAmountController.text = _data!.amount!.toString(); + } + sendToController.text = _data!.contactLabel; + _address = _data!.address.trim(); + _addressToggleFlag = true; + } + + super.initState(); + } + + @override + void dispose() { + _cryptoAmountChangedFeeUpdateTimer?.cancel(); + _baseAmountChangedFeeUpdateTimer?.cancel(); + + cryptoAmountController.removeListener(onCryptoAmountChanged); + baseAmountController.removeListener(_baseAmountChanged); + + sendToController.dispose(); + cryptoAmountController.dispose(); + baseAmountController.dispose(); + noteController.dispose(); + feeController.dispose(); + + _noteFocusNode.dispose(); + _addressFocusNode.dispose(); + _cryptoFocus.dispose(); + _baseFocus.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + final provider = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManagerProvider(walletId))); + final String locale = ref.watch( + localeServiceChangeNotifierProvider.select((value) => value.locale)); + + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 50)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "Send ${tokenContract.symbol}", + style: STextStyles.navBarTitle(context), + ), + ), + body: LayoutBuilder( + builder: (builderContext, constraints) { + return Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, + ), + child: SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + // subtract top and bottom padding set in parent + minHeight: constraints.maxHeight - 24, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container( + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .popupBG, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Row( + children: [ + EthTokenIcon( + contractAddress: tokenContract.address, + ), + const SizedBox( + width: 6, + ), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + ref.watch(provider.select( + (value) => value.walletName)), + style: STextStyles.titleBold12(context) + .copyWith(fontSize: 14), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + Text( + "Available balance", + style: STextStyles.label(context) + .copyWith(fontSize: 10), + ), + ], + ), + const Spacer(), + GestureDetector( + onTap: () { + cryptoAmountController.text = ref + .read(tokenServiceProvider)! + .balance + .spendable + .localizedStringAsFixed( + locale: ref + .read( + localeServiceChangeNotifierProvider) + .locale, + ); + }, + child: Container( + color: Colors.transparent, + child: Column( + crossAxisAlignment: + CrossAxisAlignment.end, + children: [ + Text( + "${ref.watch( + tokenServiceProvider.select( + (value) => value! + .balance.spendable + .localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider + .select( + (value) => value.locale, + ), + ), + ), + ), + )} ${tokenContract.symbol}", + style: + STextStyles.titleBold12(context) + .copyWith( + fontSize: 10, + ), + textAlign: TextAlign.right, + ), + Text( + "${(ref.watch(tokenServiceProvider.select((value) => value!.balance.spendable.decimal)) * ref.watch(priceAnd24hChangeNotifierProvider.select((value) => value.getTokenPrice(tokenContract.address).item1))).toAmount( + fractionDigits: 2, + ).localizedStringAsFixed( + locale: locale, + )} ${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", + style: STextStyles.subtitle(context) + .copyWith( + fontSize: 8, + ), + textAlign: TextAlign.right, + ) + ], + ), + ), + ), + ], + ), + ), + ), + const SizedBox( + height: 16, + ), + Text( + "Send to", + style: STextStyles.smallMed12(context), + textAlign: TextAlign.left, + ), + const SizedBox( + height: 8, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + key: const Key("tokenSendViewAddressFieldKey"), + controller: sendToController, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + toolbarOptions: const ToolbarOptions( + copy: false, + cut: false, + paste: true, + selectAll: false, + ), + onChanged: (newValue) { + _address = newValue.trim(); + _updatePreviewButtonState( + _address, _amountToSend); + + setState(() { + _addressToggleFlag = newValue.isNotEmpty; + }); + }, + focusNode: _addressFocusNode, + style: STextStyles.field(context), + decoration: standardInputDecoration( + "Enter ${tokenContract.symbol} address", + _addressFocusNode, + context, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 16, + top: 6, + bottom: 8, + right: 5, + ), + suffixIcon: Padding( + padding: sendToController.text.isEmpty + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceAround, + children: [ + _addressToggleFlag + ? TextFieldIconButton( + key: const Key( + "tokenSendViewClearAddressFieldButtonKey"), + onTap: () { + sendToController.text = ""; + _address = ""; + _updatePreviewButtonState( + _address, _amountToSend); + setState(() { + _addressToggleFlag = false; + }); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + key: const Key( + "tokenSendViewPasteAddressFieldButtonKey"), + onTap: + _onTokenSendViewPasteAddressFieldButtonPressed, + child: sendToController + .text.isEmpty + ? const ClipboardIcon() + : const XIcon(), + ), + if (sendToController.text.isEmpty) + TextFieldIconButton( + key: const Key( + "sendViewAddressBookButtonKey"), + onTap: () { + Navigator.of(context).pushNamed( + AddressBookView.routeName, + arguments: widget.coin, + ); + }, + child: const AddressBookIcon(), + ), + if (sendToController.text.isEmpty) + TextFieldIconButton( + key: const Key( + "sendViewScanQrButtonKey"), + onTap: + _onTokenSendViewScanQrButtonPressed, + child: const QrCodeIcon(), + ) + ], + ), + ), + ), + ), + ), + ), + Builder( + builder: (_) { + final error = _updateInvalidAddressText( + _address ?? "", + ref + .read(walletsChangeNotifierProvider) + .getManager(walletId), + ); + + if (error == null || error.isEmpty) { + return Container(); + } else { + return Align( + alignment: Alignment.topLeft, + child: Padding( + padding: const EdgeInsets.only( + left: 12.0, + top: 4.0, + ), + child: Text( + error, + textAlign: TextAlign.left, + style: + STextStyles.label(context).copyWith( + color: Theme.of(context) + .extension()! + .textError, + ), + ), + ), + ); + } + }, + ), + const SizedBox( + height: 12, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Amount", + style: STextStyles.smallMed12(context), + textAlign: TextAlign.left, + ), + // CustomTextButton( + // text: "Send all ${tokenContract.symbol}", + // onTap: () async { + // cryptoAmountController.text = ref + // .read(tokenServiceProvider)! + // .balance + // .getSpendable() + // .toStringAsFixed(tokenContract.decimals); + // + // _cryptoAmountChanged(); + // }, + // ), + ], + ), + const SizedBox( + height: 8, + ), + TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + style: STextStyles.smallMed14(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + key: + const Key("amountInputFieldCryptoTextFieldKey"), + controller: cryptoAmountController, + focusNode: _cryptoFocus, + keyboardType: Util.isDesktop + ? null + : const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), + textAlign: TextAlign.right, + inputFormatters: [ + // regex to validate a crypto amount with 8 decimal places + TextInputFormatter.withFunction((oldValue, + newValue) => + RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$') + .hasMatch(newValue.text) + ? newValue + : oldValue), + ], + decoration: InputDecoration( + contentPadding: const EdgeInsets.only( + top: 12, + right: 12, + ), + hintText: "0", + hintStyle: + STextStyles.fieldLabel(context).copyWith( + fontSize: 14, + ), + prefixIcon: FittedBox( + fit: BoxFit.scaleDown, + child: Padding( + padding: const EdgeInsets.all(12), + child: Text( + tokenContract.symbol, + style: STextStyles.smallMed14(context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + ), + ), + ), + ), + if (Prefs.instance.externalCalls) + const SizedBox( + height: 8, + ), + if (Prefs.instance.externalCalls) + TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + style: STextStyles.smallMed14(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + key: + const Key("amountInputFieldFiatTextFieldKey"), + controller: baseAmountController, + focusNode: _baseFocus, + keyboardType: Util.isDesktop + ? null + : const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), + textAlign: TextAlign.right, + inputFormatters: [ + // regex to validate a fiat amount with 2 decimal places + TextInputFormatter.withFunction((oldValue, + newValue) => + RegExp(r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$') + .hasMatch(newValue.text) + ? newValue + : oldValue), + ], + onChanged: _onFiatAmountFieldChanged, + decoration: InputDecoration( + contentPadding: const EdgeInsets.only( + top: 12, + right: 12, + ), + hintText: "0", + hintStyle: + STextStyles.fieldLabel(context).copyWith( + fontSize: 14, + ), + prefixIcon: FittedBox( + fit: BoxFit.scaleDown, + child: Padding( + padding: const EdgeInsets.all(12), + child: Text( + ref.watch(prefsChangeNotifierProvider + .select((value) => value.currency)), + style: STextStyles.smallMed14(context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + ), + ), + ), + ), + const SizedBox( + height: 12, + ), + Text( + "Note (optional)", + style: STextStyles.smallMed12(context), + textAlign: TextAlign.left, + ), + const SizedBox( + height: 8, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + controller: noteController, + focusNode: _noteFocusNode, + style: STextStyles.field(context), + onChanged: (_) => setState(() {}), + decoration: standardInputDecoration( + "Type something...", + _noteFocusNode, + context, + ).copyWith( + suffixIcon: noteController.text.isNotEmpty + ? Padding( + padding: + const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + noteController.text = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), + const SizedBox( + height: 12, + ), + if (coin != Coin.epicCash) + Text( + "Transaction fee (estimated)", + style: STextStyles.smallMed12(context), + textAlign: TextAlign.left, + ), + const SizedBox( + height: 8, + ), + Stack( + children: [ + TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: + Util.isDesktop ? false : true, + controller: feeController, + readOnly: true, + textInputAction: TextInputAction.none, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + ), + child: RawMaterialButton( + splashColor: Theme.of(context) + .extension()! + .highlight, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + showModalBottomSheet( + backgroundColor: Colors.transparent, + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical( + top: Radius.circular(20), + ), + ), + builder: (_) => + TransactionFeeSelectionSheet( + walletId: walletId, + isToken: true, + amount: (Decimal.tryParse( + cryptoAmountController + .text) ?? + Decimal.zero) + .toAmount( + fractionDigits: + tokenContract.decimals, + ), + updateChosen: (String fee) { + setState(() { + _calculateFeesFuture = + Future(() => fee); + }); + }, + ), + ); + }, + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Text( + ref + .watch(feeRateTypeStateProvider + .state) + .state + .prettyName, + style: STextStyles.itemSubtitle12( + context), + ), + const SizedBox( + width: 10, + ), + FutureBuilder( + future: _calculateFeesFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + return Text( + "~${snapshot.data! as String} ${coin.ticker}", + style: + STextStyles.itemSubtitle( + context), + ); + } else { + return AnimatedText( + stringsToLoopThrough: const [ + "Calculating", + "Calculating.", + "Calculating..", + "Calculating...", + ], + style: + STextStyles.itemSubtitle( + context), + ); + } + }, + ), + ], + ), + SvgPicture.asset( + Assets.svg.chevronDown, + width: 8, + height: 4, + color: Theme.of(context) + .extension()! + .textSubtitle2, + ), + ], + ), + ), + ) + ], + ), + const Spacer(), + const SizedBox( + height: 12, + ), + TextButton( + onPressed: ref + .watch(previewTxButtonStateProvider.state) + .state + ? _previewTransaction + : null, + style: ref + .watch(previewTxButtonStateProvider.state) + .state + ? Theme.of(context) + .extension()! + .getPrimaryEnabledButtonStyle(context) + : Theme.of(context) + .extension()! + .getPrimaryDisabledButtonStyle(context), + child: Text( + "Preview", + style: STextStyles.button(context), + ), + ), + const SizedBox( + height: 4, + ), + ], + ), + ), + ), + ), + ), + ); + }, + ), + ), + ); + } +} diff --git a/lib/pages/settings_views/global_settings_view/about_view.dart b/lib/pages/settings_views/global_settings_view/about_view.dart index ac530a713..60b95c626 100644 --- a/lib/pages/settings_views/global_settings_view/about_view.dart +++ b/lib/pages/settings_views/global_settings_view/about_view.dart @@ -8,9 +8,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:http/http.dart'; // import 'package:lelantus/git_versions.dart' as FIRO_VERSIONS; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; @@ -40,15 +40,17 @@ Future doesCommitExist( ); final response = jsonDecode(commitQuery.body.toString()); - Logging.instance.log("doesCommitExist $project $commit $response", + Logging.instance.log("doesCommitExist $project $commit", // $response", level: LogLevel.Info); bool isThereCommit; try { isThereCommit = response['sha'] == commit; - Logging.instance - .log("isThereCommit $isThereCommit", level: LogLevel.Info); + Logging.instance.log( + "$commit isThereCommit=$isThereCommit", + level: LogLevel.Info, + ); return isThereCommit; - } catch (e, s) { + } catch (_) { return false; } } catch (e, s) { @@ -75,14 +77,19 @@ Future isHeadCommit( ); final response = jsonDecode(commitQuery.body.toString()); - Logging.instance.log("isHeadCommit $project $commit $branch $response", - level: LogLevel.Info); + Logging.instance.log( + "isHeadCommit $project $commit $branch", //$response", + level: LogLevel.Info, + ); bool isHead; try { isHead = response['sha'] == commit; - Logging.instance.log("isHead $isHead", level: LogLevel.Info); + Logging.instance.log( + "$commit isHead=$isHead", + level: LogLevel.Info, + ); return isHead; - } catch (e, s) { + } catch (_) { return false; } } catch (e, s) { @@ -484,7 +491,7 @@ class AboutView extends ConsumerWidget { const SizedBox( height: 4, ), - BlueTextButton( + CustomTextButton( text: "https://stackwallet.com", onTap: () { launchUrl( diff --git a/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart b/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart index 06b798c36..3285d61cf 100644 --- a/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart +++ b/lib/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart @@ -1,15 +1,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/debug_view.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart'; import 'package:stackwallet/pages/stack_privacy_calls.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/choose_coin_view.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:tuple/tuple.dart'; class AdvancedSettingsView extends StatelessWidget { const AdvancedSettingsView({ @@ -121,6 +124,53 @@ class AdvancedSettingsView extends StatelessWidget { const SizedBox( height: 8, ), + RoundedWhiteContainer( + child: Consumer( + builder: (_, ref, __) { + return RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: null, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Enable coin control", + style: STextStyles.titleBold12(context), + textAlign: TextAlign.left, + ), + SizedBox( + height: 20, + width: 40, + child: DraggableSwitchButton( + isOn: ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.enableCoinControl), + ), + onValueChanged: (newValue) { + ref + .read(prefsChangeNotifierProvider) + .enableCoinControl = newValue; + }, + ), + ), + ], + ), + ), + ); + }, + ), + ), + const SizedBox( + height: 8, + ), RoundedWhiteContainer( padding: const EdgeInsets.all(0), child: Consumer( @@ -174,6 +224,43 @@ class AdvancedSettingsView extends StatelessWidget { }, ), ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + Navigator.of(context).pushNamed(ChooseCoinView.routeName, + arguments: const Tuple3( + "Manage block explorers", + "block explorer", + ManageExplorerView.routeName)); + }, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 20, + ), + child: Row( + children: [ + Text( + "Change block explorer", + style: STextStyles.titleBold12(context), + textAlign: TextAlign.left, + ), + ], + ), + ), + ), + ), ], ), ), diff --git a/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart b/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart index e3a8f0eb6..bb7865300 100644 --- a/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart +++ b/lib/pages/settings_views/global_settings_view/advanced_views/debug_view.dart @@ -17,14 +17,13 @@ import 'package:stackwallet/models/isar/models/log.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/swb_file_system.dart'; import 'package:stackwallet/providers/global/debug_service_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/stack_file_system.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; @@ -86,7 +85,6 @@ class _DebugViewState extends ConsumerState { @override void initState() { - ref.read(debugServiceProvider).updateRecentLogs(); super.initState(); } @@ -145,7 +143,7 @@ class _DebugViewState extends ConsumerState { leftButton: TextButton( style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), child: Text( "Cancel", style: STextStyles.itemSubtitle12(context), @@ -157,7 +155,7 @@ class _DebugViewState extends ConsumerState { rightButton: TextButton( style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), child: Text( "Delete logs", style: STextStyles.button(context), @@ -182,10 +180,7 @@ class _DebugViewState extends ConsumerState { await ref .read(debugServiceProvider) - .deleteAllMessages(); - await ref - .read(debugServiceProvider) - .updateRecentLogs(); + .deleteAllLogs(); shouldPop = true; @@ -195,6 +190,8 @@ class _DebugViewState extends ConsumerState { type: FlushBarType.info, context: context, message: 'Logs cleared!')); + + setState(() {}); } }, ), @@ -283,7 +280,7 @@ class _DebugViewState extends ConsumerState { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - BlueTextButton( + CustomTextButton( text: "Save Debug Info to clipboard", onTap: () async { try { @@ -311,7 +308,7 @@ class _DebugViewState extends ConsumerState { _searchTerm) .reversed .toList(growable: false); - List errorLogs = []; + List errorLogs = []; for (var log in logs) { if (log.logLevel == LogLevel.Error || log.logLevel == LogLevel.Fatal) { @@ -348,7 +345,7 @@ class _DebugViewState extends ConsumerState { }, ), const Spacer(), - BlueTextButton( + CustomTextButton( text: "Save logs to file", onTap: () async { final systemfile = SWBFileSystem(); @@ -404,14 +401,14 @@ class _DebugViewState extends ConsumerState { ), )); - bool logssaved = true; - var filename; + bool logsSaved = true; + String? filename; try { filename = await ref .read(debugServiceProvider) .exportToFile(path, eventBus); } catch (e, s) { - logssaved = false; + logsSaved = false; Logging.instance .log("$e $s", level: LogLevel.Error); } @@ -426,7 +423,7 @@ class _DebugViewState extends ConsumerState { showDialog( context: context, builder: (context) => StackOkDialog( - title: logssaved + title: logsSaved ? "Logs saved to" : "Error Saving Logs", message: "${path!}/$filename", @@ -438,7 +435,7 @@ class _DebugViewState extends ConsumerState { showFloatingFlushBar( type: FlushBarType.info, context: context, - message: logssaved + message: logsSaved ? 'Logs file saved' : "Error Saving Logs", ), diff --git a/lib/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart b/lib/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart new file mode 100644 index 000000000..845c29411 --- /dev/null +++ b/lib/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart @@ -0,0 +1,129 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/block_explorers.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class ManageExplorerView extends ConsumerStatefulWidget { + const ManageExplorerView({ + Key? key, + required this.coin, + }) : super(key: key); + + static const String routeName = "/manageExplorer"; + + final Coin coin; + + @override + ConsumerState createState() => _ManageExplorerViewState(); +} + +class _ManageExplorerViewState extends ConsumerState { + late TextEditingController textEditingController; + + @override + void initState() { + super.initState(); + textEditingController = TextEditingController( + text: + getBlockExplorerTransactionUrlFor(coin: widget.coin, txid: "[TXID]") + .toString() + .replaceAll("%5BTXID%5D", "[TXID]")); + } + + @override + void dispose() { + textEditingController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "${widget.coin.prettyName} block explorer", + style: STextStyles.navBarTitle(context), + ), + ), + body: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + Expanded( + child: Column( + children: [ + TextField( + controller: textEditingController, + decoration: const InputDecoration( + border: OutlineInputBorder(), + ), + ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + child: Center( + child: Text( + "Edit your block explorer above. Keep in mind that " + "every block explorer has a slightly different URL " + "scheme.\n\nPaste in your block explorer of choice," + " then edit in [TXID] where the transaction ID " + "should go, and Stack Wallet will auto fill the " + "transaction ID in that place of URL.", + style: STextStyles.itemSubtitle(context), + ), + ), + ), + ], + )), + Align( + alignment: Alignment.bottomCenter, + child: ConstrainedBox( + constraints: const BoxConstraints( + minWidth: 480, + minHeight: 70, + ), + child: TextButton( + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonStyle(context), + onPressed: () async { + textEditingController.text = + textEditingController.text.trim(); + await setBlockExplorerForCoin( + coin: widget.coin, + url: Uri.parse( + textEditingController.text, + ), + ); + + if (mounted) { + Navigator.of(context).pop(); + } + }, + child: Text( + "Save", + style: STextStyles.button(context), + ), + ), + ), + ) + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/settings_views/global_settings_view/appearance_settings/appearance_settings_view.dart b/lib/pages/settings_views/global_settings_view/appearance_settings/appearance_settings_view.dart new file mode 100644 index 000000000..314e0fff5 --- /dev/null +++ b/lib/pages/settings_views/global_settings_view/appearance_settings/appearance_settings_view.dart @@ -0,0 +1,153 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/manage_themes.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/theme_options_widget.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class AppearanceSettingsView extends ConsumerWidget { + const AppearanceSettingsView({Key? key}) : super(key: key); + + static const String routeName = "/appearanceSettings"; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Appearance", + style: STextStyles.navBarTitle(context), + ), + ), + body: Padding( + padding: const EdgeInsets.all(16), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + RoundedWhiteContainer( + child: Consumer( + builder: (_, ref, __) { + return RawMaterialButton( + splashColor: Theme.of(context) + .extension()! + .highlight, + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: null, + child: Padding( + padding: + const EdgeInsets.symmetric(vertical: 8), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Display favorite wallets", + style: STextStyles.titleBold12(context), + textAlign: TextAlign.left, + ), + SizedBox( + height: 20, + width: 40, + child: DraggableSwitchButton( + isOn: ref.watch( + prefsChangeNotifierProvider.select( + (value) => + value.showFavoriteWallets), + ), + onValueChanged: (newValue) { + ref + .read( + prefsChangeNotifierProvider) + .showFavoriteWallets = newValue; + }, + ), + ) + ], + ), + ), + ); + }, + ), + ), + const SizedBox( + height: 10, + ), + RoundedWhiteContainer( + child: Column( + children: [ + Row( + children: [ + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "Choose Theme", + style: STextStyles.titleBold12(context), + textAlign: TextAlign.left, + ), + const SizedBox( + height: 12, + ), + const Padding( + padding: EdgeInsets.all(4), + child: ThemeOptionsWidget(), + ), + ], + ), + ], + ), + const SizedBox( + height: 12, + ), + SecondaryButton( + label: "Add more themes", + onPressed: () { + Navigator.of(context).pushNamed( + ManageThemesView.routeName, + ); + }, + ) + ], + ), + ), + ], + ), + ), + ), + ); + }, + ), + ), + ), + ); + } +} diff --git a/lib/pages/settings_views/global_settings_view/appearance_settings/manage_themes.dart b/lib/pages/settings_views/global_settings_view/appearance_settings/manage_themes.dart new file mode 100644 index 000000000..b363108c7 --- /dev/null +++ b/lib/pages/settings_views/global_settings_view/appearance_settings/manage_themes.dart @@ -0,0 +1,288 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/install_theme_from_file_dialog.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart'; +import 'package:stackwallet/providers/db/main_db_provider.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_service.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/loading_indicator.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:tuple/tuple.dart'; + +class ManageThemesView extends ConsumerStatefulWidget { + const ManageThemesView({Key? key}) : super(key: key); + + static const String routeName = "/manageThemes"; + + @override + ConsumerState createState() => _ManageThemesViewState(); +} + +class _ManageThemesViewState extends ConsumerState { + late bool _showThemes; + + Future> Function() future = () async => []; + + void _onInstallPressed() { + showDialog( + context: context, + builder: (context) => const InstallThemeFromFileDialog(), + ); + } + + @override + void initState() { + _showThemes = ref.read(prefsChangeNotifierProvider).externalCalls; + if (_showThemes) { + future = ref.read(pThemeService).fetchThemes; + } + super.initState(); + } + + @override + Widget build(BuildContext context) { + return ConditionalParent( + condition: !Util.isDesktop, + builder: (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Add more themes", + style: STextStyles.navBarTitle(context), + ), + actions: [ + Padding( + padding: const EdgeInsets.only(right: 2), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + icon: SvgPicture.asset( + Assets.svg.circlePlusFilled, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, + height: 20, + width: 20, + ), + onPressed: _onInstallPressed, + ), + ), + ), + ], + ), + body: _showThemes + ? Column( + children: [ + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, + ), + child: IntrinsicHeight( + child: child, + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.all(16), + child: SecondaryButton( + label: "Install theme file", + onPressed: _onInstallPressed, + ), + ), + ], + ) + : SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: IntrinsicHeight( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RoundedWhiteContainer( + child: Text( + "You are using Incognito Mode. Please press the" + " button below to load available themes from our server" + " or install a theme file manually from your device.", + style: STextStyles.smallMed12(context), + ), + ), + const SizedBox( + height: 12, + ), + PrimaryButton( + label: "Load themes", + onPressed: () { + setState(() { + _showThemes = true; + future = ref.watch(pThemeService).fetchThemes; + }); + }, + ), + const SizedBox( + height: 12, + ), + SecondaryButton( + label: "Install theme file", + onPressed: _onInstallPressed, + ), + const SizedBox( + height: 16, + ), + Expanded( + child: IncognitoInstalledThemes( + cardWidth: + (MediaQuery.of(context).size.width - 48) / 2, + ), + ), + const SizedBox( + height: 16, + ), + ], + ), + ), + ), + ), + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FutureBuilder( + future: future(), + builder: ( + context, + AsyncSnapshot> snapshot, + ) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData) { + return Wrap( + spacing: 16, + runSpacing: 16, + children: snapshot.data! + .map( + (e) => SizedBox( + key: Key("ManageThemesView_card_${e.id}_key"), + width: (MediaQuery.of(context).size.width - 48) / 2, + child: StackThemeCard( + data: e, + ), + ), + ) + .toList(), + ); + } else { + return Center( + child: LoadingIndicator( + width: (MediaQuery.of(context).size.width - 48) / 2, + ), + ); + } + }, + ), + ], + ), + ); + } +} + +class IncognitoInstalledThemes extends ConsumerStatefulWidget { + const IncognitoInstalledThemes({ + Key? key, + required this.cardWidth, + }) : super(key: key); + + final double cardWidth; + + @override + ConsumerState createState() => + _IncognitoInstalledThemesState(); +} + +class _IncognitoInstalledThemesState + extends ConsumerState { + late final StreamSubscription _subscription; + + List> installedThemeIdNames = []; + + void _updateInstalledList() { + installedThemeIdNames = ref + .read(pThemeService) + .installedThemes + .where((e) => e.themeId != "light" && e.themeId != "dark") + .map((e) => Tuple3(e.themeId, e.name, e.version)) + .toList(); + } + + @override + void initState() { + _updateInstalledList(); + + _subscription = + ref.read(mainDBProvider).isar.stackThemes.watchLazy().listen((_) { + if (mounted) { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + _updateInstalledList(); + }); + }); + } + }); + + super.initState(); + } + + @override + void dispose() { + _subscription.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Wrap( + spacing: 16, + runSpacing: 16, + children: installedThemeIdNames + .map( + (e) => SizedBox( + key: Key("IncognitoInstalledThemes_card_${e.item1}_key"), + width: widget.cardWidth, + child: StackThemeCard( + data: StackThemeMetaData( + name: e.item2, + id: e.item1, + version: e.item3 ?? 1, + sha256: "", + size: "", + previewImageUrl: "", + ), + ), + ), + ) + .toList(), + ); + } +} diff --git a/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/install_theme_from_file_dialog.dart b/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/install_theme_from_file_dialog.dart new file mode 100644 index 000000000..c12d634a1 --- /dev/null +++ b/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/install_theme_from_file_dialog.dart @@ -0,0 +1,187 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_service.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/show_loading.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; + +class InstallThemeFromFileDialog extends ConsumerStatefulWidget { + const InstallThemeFromFileDialog({Key? key}) : super(key: key); + + @override + ConsumerState createState() => + _InstallThemeFromFileDialogState(); +} + +class _InstallThemeFromFileDialogState + extends ConsumerState { + late final TextEditingController controller; + + Future _install() async { + try { + final timedFuture = Future.delayed(const Duration(seconds: 2)); + final installFuture = File(controller.text).readAsBytes().then( + (fileBytes) => ref.read(pThemeService).install( + themeArchiveData: fileBytes, + ), + ); + + // wait for at least 2 seconds to prevent annoying screen flashing + await Future.wait([ + installFuture, + timedFuture, + ]); + return true; + } catch (e, s) { + Logging.instance.log( + "Failed to install theme: $e\n$s", + level: LogLevel.Warning, + ); + return false; + } + } + + Future _pickFile() async { + try { + final result = await FilePicker.platform.pickFiles( + dialogTitle: "Choose theme file", + type: FileType.custom, + allowedExtensions: ["zip"], + lockParentWindow: true, // windows only + ); + + if (result != null && mounted) { + setState(() { + controller.text = result.paths.first ?? ""; + }); + } + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Error); + } + } + + @override + void initState() { + controller = TextEditingController(); + super.initState(); + } + + @override + void dispose() { + controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return StackDialogBase( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Install theme file", + style: STextStyles.pageTitleH2(context), + ), + const SizedBox( + height: 12, + ), + TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + onTap: _pickFile, + controller: controller, + style: STextStyles.field(context), + decoration: InputDecoration( + hintText: "Choose file...", + hintStyle: STextStyles.fieldLabel(context), + suffixIcon: UnconstrainedBox( + child: Row( + children: [ + const SizedBox( + width: 16, + ), + SvgPicture.asset( + Assets.svg.folder, + color: + Theme.of(context).extension()!.textDark3, + width: 16, + height: 16, + ), + const SizedBox( + width: 12, + ), + ], + ), + ), + ), + readOnly: true, + ), + const SizedBox( + height: 20, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + onPressed: Navigator.of(context).pop, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Install", + enabled: controller.text.isNotEmpty, + onPressed: () async { + final result = await showLoading( + whileFuture: _install(), + context: context, + message: "Installing ${controller.text}...", + ); + if (mounted) { + Navigator.of(context).pop(); + if (!result) { + unawaited( + showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Failed to install theme:", + message: controller.text, + ), + ), + ); + } else { + unawaited( + showDialog( + context: context, + builder: (_) => const StackOkDialog( + title: "Theme install succeeded!", + ), + ), + ); + } + } + }, + ), + ), + ], + ) + ], + ), + ); + } +} diff --git a/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart b/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart new file mode 100644 index 000000000..7b36c873a --- /dev/null +++ b/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart @@ -0,0 +1,299 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/providers/db/main_db_provider.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_service.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/show_loading.dart'; +import 'package:stackwallet/utilities/stack_file_system.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/animated_text.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; + +class StackThemeCard extends ConsumerStatefulWidget { + const StackThemeCard({ + Key? key, + required this.data, + }) : super(key: key); + + final StackThemeMetaData data; + + @override + ConsumerState createState() => _StackThemeCardState(); +} + +class _StackThemeCardState extends ConsumerState { + final isDesktop = Util.isDesktop; + late final StreamSubscription _subscription; + + late bool _hasTheme; + String? _cachedSize; + + Future _downloadAndInstall() async { + final service = ref.read(pThemeService); + + try { + final data = await service.fetchTheme( + themeMetaData: widget.data, + ); + + await service.install(themeArchiveData: data); + return true; + } catch (e, s) { + Logging.instance.log( + "Failed _downloadAndInstall of ${widget.data.id}: $e\n$s", + level: LogLevel.Warning, + ); + return false; + } + } + + Future _downloadPressed() async { + final result = await showLoading( + whileFuture: _downloadAndInstall(), + context: context, + message: "Downloading and installing theme...", + ); + + if (mounted) { + final message = result + ? "${widget.data.name} theme installed!" + : "Failed to install ${widget.data.name} theme"; + if (isDesktop) { + await showFloatingFlushBar( + type: result ? FlushBarType.success : FlushBarType.warning, + message: message, + context: context, + ); + } else { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: message, + onOkPressed: (_) { + setState(() { + _hasTheme = result; + }); + }, + ), + ); + } + } + } + + Future _uninstallThemePressed() async { + await ref.read(pThemeService).remove(themeId: widget.data.id); + if (mounted) { + await showFloatingFlushBar( + type: FlushBarType.success, + message: "${widget.data.name} uninstalled", + context: context, + ); + } + } + + bool get themeIsInUse { + final prefs = ref.read(prefsChangeNotifierProvider); + final themeId = widget.data.id; + + return prefs.themeId == themeId || + prefs.systemBrightnessDarkThemeId == themeId || + prefs.systemBrightnessLightThemeId == themeId; + } + + Future getThemeDirectorySize() async { + final themesDir = await StackFileSystem.applicationThemesDirectory(); + final themeDir = Directory("${themesDir.path}/${widget.data.id}"); + int bytes = 0; + if (await themeDir.exists()) { + await for (FileSystemEntity entity in themeDir.list(recursive: true)) { + if (entity is File) { + bytes += await entity.length(); + } + } + } else if (widget.data.size.isNotEmpty) { + return widget.data.size; + } + + if (bytes < 1024) { + return '$bytes B'; + } else if (bytes < 1048576) { + double kbSize = bytes / 1024; + return '${kbSize.toStringAsFixed(2)} KB'; + } else if (bytes < 1073741824) { + double mbSize = bytes / 1048576; + return '${mbSize.toStringAsFixed(2)} MB'; + } else { + double gbSize = bytes / 1073741824; + return '${gbSize.toStringAsFixed(2)} GB'; + } + } + + @override + void initState() { + _hasTheme = ref + .read(mainDBProvider) + .isar + .stackThemes + .where() + .themeIdEqualTo(widget.data.id) + .countSync() > + 0; + + _subscription = ref + .read(mainDBProvider) + .isar + .stackThemes + .watchLazy() + .listen((event) async { + final hasTheme = (await ref + .read(mainDBProvider) + .isar + .stackThemes + .where() + .themeIdEqualTo(widget.data.id) + .count()) > + 0; + if (_hasTheme != hasTheme && mounted) { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + setState(() { + _hasTheme = hasTheme; + }); + }); + } + }); + + _subscription.resume(); + super.initState(); + } + + @override + void dispose() { + _subscription.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return RoundedWhiteContainer( + radiusMultiplier: isDesktop ? 2.5 : 1, + borderColor: isDesktop + ? Theme.of(context).extension()!.textSubtitle6 + : null, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 18, + ), + child: widget.data.previewImageUrl.isNotEmpty + ? AspectRatio( + aspectRatio: 1, + child: ClipRRect( + borderRadius: BorderRadius.circular(100), + child: Image.network( + widget.data.previewImageUrl, + ), + ), + ) + : Builder( + builder: (context) { + final themePreview = ref + .watch(pThemeService) + .getTheme(themeId: widget.data.id) + ?.assets + .themePreview ?? + ""; + + return (themePreview.endsWith(".png")) + ? Image.file( + File( + themePreview, + ), + height: 100, + ) + : SvgPicture.file( + File( + themePreview, + ), + height: 100, + ); + }, + ), + ), + const SizedBox( + height: 12, + ), + Text( + widget.data.name, + style: STextStyles.itemSubtitle12(context), + ), + const SizedBox( + height: 6, + ), + FutureBuilder( + future: getThemeDirectorySize(), + builder: ( + context, + AsyncSnapshot snapshot, + ) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData) { + _cachedSize = snapshot.data; + } + if (_cachedSize == null) { + return AnimatedText( + stringsToLoopThrough: const [ + "Calculating size ", + "Calculating size. ", + "Calculating size.. ", + "Calculating size...", + ], + style: STextStyles.label(context), + ); + } else { + return Text( + _cachedSize!, + style: STextStyles.label(context), + ); + } + }, + ), + const SizedBox( + height: 12, + ), + AnimatedCrossFade( + duration: const Duration(milliseconds: 300), + crossFadeState: _hasTheme + ? CrossFadeState.showSecond + : CrossFadeState.showFirst, + firstChild: PrimaryButton( + label: "Download", + buttonHeight: isDesktop ? ButtonHeight.s : ButtonHeight.l, + onPressed: _downloadPressed, + ), + secondChild: SecondaryButton( + label: themeIsInUse ? "Theme is active" : "Remove", + enabled: !themeIsInUse, + buttonHeight: isDesktop ? ButtonHeight.s : ButtonHeight.l, + onPressed: _uninstallThemePressed, + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/theme_option.dart b/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/theme_option.dart new file mode 100644 index 000000000..1b78a231f --- /dev/null +++ b/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/theme_option.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; + +class ThemeOption extends StatelessWidget { + const ThemeOption({ + Key? key, + required this.onPressed, + required this.onChanged, + required this.label, + required this.value, + required this.groupValue, + }) : super(key: key); + + final VoidCallback onPressed; + final void Function(Object?) onChanged; + final String label; + final T value; + final T groupValue; + + @override + Widget build(BuildContext context) { + return MaterialButton( + splashColor: Colors.transparent, + hoverColor: Colors.transparent, + padding: const EdgeInsets.all(0), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: onPressed, + child: SizedBox( + width: 200, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + SizedBox( + width: 20, + height: 20, + child: Radio( + activeColor: Theme.of(context) + .extension()! + .radioButtonIconEnabled, + value: value, + groupValue: groupValue, + onChanged: onChanged, + ), + ), + const SizedBox( + width: 14, + ), + Text( + label, + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: + Theme.of(context).extension()!.textDark2, + ), + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/theme_options_widget.dart b/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/theme_options_widget.dart new file mode 100644 index 000000000..de5c91ee0 --- /dev/null +++ b/lib/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/theme_options_widget.dart @@ -0,0 +1,238 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/theme_option.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/system_brightness_theme_selection_view.dart'; +import 'package:stackwallet/providers/db/main_db_provider.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; +import 'package:stackwallet/themes/theme_service.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; +import 'package:tuple/tuple.dart'; + +class ThemeOptionsWidget extends ConsumerStatefulWidget { + const ThemeOptionsWidget({Key? key}) : super(key: key); + + @override + ConsumerState createState() => _ThemeOptionsWidgetState(); +} + +class _ThemeOptionsWidgetState extends ConsumerState { + late final StreamSubscription _subscription; + late int _current; + + List> installedThemeIdNames = []; + + int get systemDefault => installedThemeIdNames.length; + + void setTheme(int index) { + if (index == _current) { + return; + } + + if (index == systemDefault) { + // update current index + _current = index; + + // enable system brightness setting + ref.read(prefsChangeNotifierProvider).enableSystemBrightness = true; + + // get theme + final String themeId; + switch (MediaQuery.of(context).platformBrightness) { + case Brightness.dark: + themeId = ref + .read(prefsChangeNotifierProvider.notifier) + .systemBrightnessDarkThemeId; + break; + case Brightness.light: + themeId = ref + .read(prefsChangeNotifierProvider.notifier) + .systemBrightnessLightThemeId; + break; + } + + // apply theme + ref.read(themeProvider.notifier).state = + ref.read(pThemeService).getTheme(themeId: themeId)!; + + // Assets.precache(context); + } else { + if (_current == systemDefault) { + // disable system brightness setting + ref.read(prefsChangeNotifierProvider).enableSystemBrightness = false; + } + + // update current index + _current = index; + + // get theme + final themeId = installedThemeIdNames[index].item1; + + // save theme setting + ref.read(prefsChangeNotifierProvider.notifier).themeId = themeId; + + // apply theme + ref.read(themeProvider.notifier).state = + ref.read(pThemeService).getTheme(themeId: themeId)!; + + // Assets.precache(context); + } + } + + void _updateInstalledList() { + installedThemeIdNames = ref + .read(pThemeService) + .installedThemes + .map((e) => Tuple2(e.themeId, e.name)) + .toList(); + + if (ref.read(prefsChangeNotifierProvider).enableSystemBrightness) { + _current = installedThemeIdNames.length; + } else { + final themeId = ref.read(prefsChangeNotifierProvider).themeId; + + for (int i = 0; i < installedThemeIdNames.length; i++) { + if (installedThemeIdNames[i].item1 == themeId) { + _current = i; + break; + } + } + } + } + + @override + void initState() { + _updateInstalledList(); + + _subscription = + ref.read(mainDBProvider).isar.stackThemes.watchLazy().listen((_) { + if (mounted) { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + _updateInstalledList(); + }); + }); + } + }); + + super.initState(); + } + + @override + void dispose() { + _subscription.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + MaterialButton( + splashColor: Colors.transparent, + hoverColor: Colors.transparent, + padding: const EdgeInsets.all(0), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + // setTheme(systemDefault); + }, + child: SizedBox( + width: 200, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 20, + height: 26, + child: Radio( + activeColor: Theme.of(context) + .extension()! + .radioButtonIconEnabled, + value: installedThemeIdNames.length, + groupValue: _current, + onChanged: (newValue) { + if (newValue is int) { + setTheme(newValue); + } + }, + ), + ), + const SizedBox( + width: 14, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "System default", + style: STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark2, + ), + ), + const SizedBox( + height: 2, + ), + CustomTextButton( + text: "Options", + onTap: () { + Navigator.of(context).pushNamed( + SystemBrightnessThemeSelectionView.routeName, + ); + }, + ), + ], + ), + ], + ), + ], + ), + ), + ), + const SizedBox( + height: 10, + ), + for (int i = 0; i < installedThemeIdNames.length; i++) + ConditionalParent( + key: Key("installedTheme_${installedThemeIdNames[i].item1}"), + condition: i > 0, + builder: (child) => Padding( + padding: const EdgeInsets.only(top: 10), + child: child, + ), + child: ThemeOption( + label: installedThemeIdNames[i].item2, + onPressed: () { + setTheme(i); + }, + onChanged: (newValue) { + if (newValue is int) { + setTheme(newValue); + } + }, + value: i, + groupValue: _current, + ), + ), + ], + ); + } +} diff --git a/lib/pages/settings_views/global_settings_view/appearance_settings/system_brightness_theme_selection_view.dart b/lib/pages/settings_views/global_settings_view/appearance_settings/system_brightness_theme_selection_view.dart new file mode 100644 index 000000000..734d7194b --- /dev/null +++ b/lib/pages/settings_views/global_settings_view/appearance_settings/system_brightness_theme_selection_view.dart @@ -0,0 +1,243 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/theme_option.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; +import 'package:stackwallet/themes/theme_service.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:tuple/tuple.dart'; + +class SystemBrightnessThemeSelectionView extends ConsumerStatefulWidget { + const SystemBrightnessThemeSelectionView({Key? key}) : super(key: key); + + static const String routeName = "/chooseSystemTheme"; + + @override + ConsumerState createState() => + _SystemBrightnessThemeSelectionViewState(); +} + +class _SystemBrightnessThemeSelectionViewState + extends ConsumerState { + List> installedThemeIdNames = []; + + void _setTheme({ + required BuildContext context, + required bool isDark, + required String themeId, + required WidgetRef ref, + }) { + final brightness = MediaQuery.of(context).platformBrightness; + if (isDark) { + ref.read(prefsChangeNotifierProvider).systemBrightnessDarkThemeId = + themeId; + if (brightness == Brightness.dark) { + // apply theme + ref.read(themeProvider.notifier).state = + ref.read(pThemeService).getTheme(themeId: themeId)!; + } + } else { + ref.read(prefsChangeNotifierProvider).systemBrightnessLightThemeId = + themeId; + if (brightness == Brightness.light) { + // apply theme + ref.read(themeProvider.notifier).state = + ref.read(pThemeService).getTheme(themeId: themeId)!; + } + } + } + + @override + void initState() { + installedThemeIdNames = ref + .read(pThemeService) + .installedThemes + .map((e) => Tuple2(e.themeId, e.name)) + .toList(); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + Navigator.of(context).pop(); + }, + ), + title: Text( + "System default theme", + style: STextStyles.navBarTitle(context), + ), + ), + body: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: IntrinsicHeight( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox( + height: 16, + ), + RoundedWhiteContainer( + child: Text( + "Select a light and dark theme that will be" + " activated automatically when your phone system" + " switches light and dark mode.", + style: STextStyles.smallMed12(context), + ), + ), + const SizedBox( + height: 10, + ), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Choose light mode theme", + style: STextStyles.titleBold12(context), + ), + const SizedBox( + height: 18, + ), + for (int i = 0; + i < (2 * installedThemeIdNames.length) - 1; + i++) + (i % 2 == 1) + ? const SizedBox( + height: 10, + ) + : ThemeOption( + label: + installedThemeIdNames[i ~/ 2].item2, + onPressed: () { + _setTheme( + context: context, + isDark: false, + themeId: + installedThemeIdNames[i ~/ 2] + .item1, + ref: ref, + ); + }, + onChanged: (newValue) { + final value = + installedThemeIdNames[i ~/ 2] + .item1; + if (newValue == value && + ref + .read( + prefsChangeNotifierProvider) + .systemBrightnessLightThemeId != + value) { + _setTheme( + context: context, + isDark: false, + themeId: value, + ref: ref, + ); + } + }, + value: + installedThemeIdNames[i ~/ 2].item1, + groupValue: ref.watch( + prefsChangeNotifierProvider.select( + (value) => value + .systemBrightnessLightThemeId)), + ), + ], + ), + ), + const SizedBox( + height: 10, + ), + RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Choose dark mode theme", + style: STextStyles.titleBold12(context), + ), + const SizedBox( + height: 18, + ), + for (int i = 0; + i < (2 * installedThemeIdNames.length) - 1; + i++) + (i % 2 == 1) + ? const SizedBox( + height: 10, + ) + : ThemeOption( + label: + installedThemeIdNames[i ~/ 2].item2, + onPressed: () { + _setTheme( + context: context, + isDark: true, + themeId: + installedThemeIdNames[i ~/ 2] + .item1, + ref: ref, + ); + }, + onChanged: (newValue) { + final value = + installedThemeIdNames[i ~/ 2] + .item1; + if (newValue == value && + ref + .read( + prefsChangeNotifierProvider) + .systemBrightnessDarkThemeId != + value) { + _setTheme( + context: context, + isDark: true, + themeId: value, + ref: ref, + ); + } + }, + value: + installedThemeIdNames[i ~/ 2].item1, + groupValue: ref.watch( + prefsChangeNotifierProvider.select( + (value) => value + .systemBrightnessDarkThemeId)), + ), + ], + ), + ), + const SizedBox( + height: 16, + ), + ], + ), + ), + ), + ), + ); + }, + ), + ), + ); + } +} diff --git a/lib/pages/settings_views/global_settings_view/appearance_settings_view.dart b/lib/pages/settings_views/global_settings_view/appearance_settings_view.dart deleted file mode 100644 index 693f39f02..000000000 --- a/lib/pages/settings_views/global_settings_view/appearance_settings_view.dart +++ /dev/null @@ -1,436 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/hive/db.dart'; -import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/providers/ui/color_theme_provider.dart'; -import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/color_theme.dart'; -import 'package:stackwallet/utilities/theme/dark_colors.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/ocean_breeze_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; -import 'package:stackwallet/widgets/background.dart'; -import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; -import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; -import 'package:stackwallet/widgets/rounded_white_container.dart'; - -class AppearanceSettingsView extends ConsumerWidget { - const AppearanceSettingsView({Key? key}) : super(key: key); - - static const String routeName = "/appearanceSettings"; - - String chooseThemeType(ThemeType type) { - switch (type) { - case ThemeType.light: - return "Light theme"; - case ThemeType.oceanBreeze: - return "Ocean theme"; - case ThemeType.dark: - return "Dark theme"; - } - } - - @override - Widget build(BuildContext context, WidgetRef ref) { - return Background( - child: Scaffold( - backgroundColor: Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () async { - Navigator.of(context).pop(); - }, - ), - title: Text( - "Appearance", - style: STextStyles.navBarTitle(context), - ), - ), - body: Padding( - padding: const EdgeInsets.all(16), - child: LayoutBuilder( - builder: (context, constraints) { - return SingleChildScrollView( - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: constraints.maxHeight, - ), - child: IntrinsicHeight( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - RoundedWhiteContainer( - child: Consumer( - builder: (_, ref, __) { - return RawMaterialButton( - splashColor: Theme.of(context) - .extension()! - .highlight, - materialTapTargetSize: - MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: null, - child: Padding( - padding: - const EdgeInsets.symmetric(vertical: 8), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - "Display favorite wallets", - style: STextStyles.titleBold12(context), - textAlign: TextAlign.left, - ), - SizedBox( - height: 20, - width: 40, - child: DraggableSwitchButton( - isOn: ref.watch( - prefsChangeNotifierProvider.select( - (value) => - value.showFavoriteWallets), - ), - onValueChanged: (newValue) { - ref - .read( - prefsChangeNotifierProvider) - .showFavoriteWallets = newValue; - }, - ), - ) - ], - ), - ), - ); - }, - ), - ), - const SizedBox( - height: 10, - ), - RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: RawMaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - padding: const EdgeInsets.all(0), - materialTapTargetSize: - MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: null, - child: Padding( - padding: const EdgeInsets.all(12), - child: Row( - children: [ - Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Text( - "Choose Theme", - style: STextStyles.titleBold12(context), - textAlign: TextAlign.left, - ), - const Padding( - padding: EdgeInsets.all(10), - child: ThemeOptionsView(), - ), - ], - ), - ], - ), - ), - ), - ), - ], - ), - ), - ), - ); - }, - ), - ), - ), - ); - } -} - -class ThemeOptionsView extends ConsumerStatefulWidget { - const ThemeOptionsView({ - Key? key, - }) : super(key: key); - - @override - ConsumerState createState() => _ThemeOptionsView(); -} - -class _ThemeOptionsView extends ConsumerState { - late String _selectedTheme; - - @override - void initState() { - _selectedTheme = - DB.instance.get(boxName: DB.boxNameTheme, key: "colorScheme") - as String? ?? - "light"; - - super.initState(); - } - - @override - Widget build(BuildContext context) { - return Column( - children: [ - MaterialButton( - splashColor: Colors.transparent, - hoverColor: Colors.transparent, - padding: const EdgeInsets.all(0), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: () { - DB.instance.put( - boxName: DB.boxNameTheme, - key: "colorScheme", - value: ThemeType.light.name, - ); - ref.read(colorThemeProvider.state).state = - StackColors.fromStackColorTheme( - LightColors(), - ); - - setState(() { - _selectedTheme = "light"; - }); - }, - child: SizedBox( - width: 200, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - children: [ - SizedBox( - width: 10, - height: 10, - child: Radio( - activeColor: Theme.of(context) - .extension()! - .radioButtonIconEnabled, - value: "light", - groupValue: _selectedTheme, - onChanged: (newValue) { - if (newValue is String && newValue == "light") { - DB.instance.put( - boxName: DB.boxNameTheme, - key: "colorScheme", - value: ThemeType.light.name, - ); - ref.read(colorThemeProvider.state).state = - StackColors.fromStackColorTheme( - LightColors(), - ); - - setState(() { - _selectedTheme = "light"; - }); - } - }, - ), - ), - const SizedBox( - width: 14, - ), - Text( - "Light", - style: - STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark2, - ), - ), - ], - ), - ], - ), - ), - ), - const SizedBox( - height: 10, - ), - MaterialButton( - splashColor: Colors.transparent, - hoverColor: Colors.transparent, - padding: const EdgeInsets.all(0), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: () { - DB.instance.put( - boxName: DB.boxNameTheme, - key: "colorScheme", - value: ThemeType.oceanBreeze.name, - ); - ref.read(colorThemeProvider.state).state = - StackColors.fromStackColorTheme( - OceanBreezeColors(), - ); - - setState(() { - _selectedTheme = "oceanBreeze"; - }); - }, - child: SizedBox( - width: 200, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - children: [ - SizedBox( - width: 10, - height: 10, - child: Radio( - activeColor: Theme.of(context) - .extension()! - .radioButtonIconEnabled, - value: "oceanBreeze", - groupValue: _selectedTheme, - onChanged: (newValue) { - if (newValue is String && newValue == "oceanBreeze") { - DB.instance.put( - boxName: DB.boxNameTheme, - key: "colorScheme", - value: ThemeType.oceanBreeze.name, - ); - ref.read(colorThemeProvider.state).state = - StackColors.fromStackColorTheme( - OceanBreezeColors(), - ); - - setState(() { - _selectedTheme = "oceanBreeze"; - }); - } - }, - ), - ), - const SizedBox( - width: 14, - ), - Text( - "Ocean Breeze", - style: - STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark2, - ), - ), - ], - ), - ], - ), - ), - ), - const SizedBox( - height: 10, - ), - MaterialButton( - splashColor: Colors.transparent, - hoverColor: Colors.transparent, - padding: const EdgeInsets.all(0), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - onPressed: () { - DB.instance.put( - boxName: DB.boxNameTheme, - key: "colorScheme", - value: ThemeType.dark.name, - ); - ref.read(colorThemeProvider.state).state = - StackColors.fromStackColorTheme( - DarkColors(), - ); - - setState(() { - _selectedTheme = "dark"; - }); - }, - child: SizedBox( - width: 200, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - children: [ - SizedBox( - width: 10, - height: 10, - child: Radio( - activeColor: Theme.of(context) - .extension()! - .radioButtonIconEnabled, - value: "dark", - groupValue: _selectedTheme, - onChanged: (newValue) { - if (newValue is String && newValue == "dark") { - DB.instance.put( - boxName: DB.boxNameTheme, - key: "colorScheme", - value: ThemeType.dark.name, - ); - ref.read(colorThemeProvider.state).state = - StackColors.fromStackColorTheme( - DarkColors(), - ); - - setState(() { - _selectedTheme = "dark"; - }); - } - }, - ), - ), - const SizedBox( - width: 14, - ), - Text( - "Dark", - style: - STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark2, - ), - ), - ], - ), - ], - ), - ), - ), - ], - ); - } -} diff --git a/lib/pages/settings_views/global_settings_view/currency_view.dart b/lib/pages/settings_views/global_settings_view/currency_view.dart index 5bcf7fb7f..346c625d2 100644 --- a/lib/pages/settings_views/global_settings_view/currency_view.dart +++ b/lib/pages/settings_views/global_settings_view/currency_view.dart @@ -3,10 +3,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/providers/global/base_currencies_provider.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; diff --git a/lib/pages/settings_views/global_settings_view/delete_account_view.dart b/lib/pages/settings_views/global_settings_view/delete_account_view.dart index e9cc819dc..3dc08c1d2 100644 --- a/lib/pages/settings_views/global_settings_view/delete_account_view.dart +++ b/lib/pages/settings_views/global_settings_view/delete_account_view.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:stackwallet/pages/intro_view.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/delete_everything.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; @@ -7,10 +9,7 @@ import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; - import 'package:stackwallet/widgets/stack_dialog.dart'; -import 'package:stackwallet/utilities/delete_everything.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; class DeleteAccountView extends StatefulWidget { const DeleteAccountView({Key? key}) : super(key: key); @@ -35,7 +34,7 @@ class _DeleteAccountViewState extends State { leftButton: TextButton( style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), onPressed: () { Navigator.pop(context); }, @@ -50,7 +49,7 @@ class _DeleteAccountViewState extends State { rightButton: TextButton( style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), onPressed: () async { await deleteEverything(); diff --git a/lib/pages/settings_views/global_settings_view/global_settings_view.dart b/lib/pages/settings_views/global_settings_view/global_settings_view.dart index 49e7ea36c..582ccfde5 100644 --- a/lib/pages/settings_views/global_settings_view/global_settings_view.dart +++ b/lib/pages/settings_views/global_settings_view/global_settings_view.dart @@ -5,7 +5,7 @@ import 'package:stackwallet/pages/address_book_views/address_book_view.dart'; import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/about_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart'; -import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings_view.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/appearance_settings_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/currency_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/delete_account_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/language_view.dart'; @@ -17,9 +17,9 @@ import 'package:stackwallet/pages/settings_views/global_settings_view/support_vi import 'package:stackwallet/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_preferences_view.dart'; import 'package:stackwallet/pages/settings_views/sub_widgets/settings_list_button.dart'; import 'package:stackwallet/route_generator.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -162,7 +162,7 @@ class GlobalSettingsView extends StatelessWidget { height: 8, ), SettingsListButton( - iconAssetName: Assets.svg.arrowRotate3, + iconAssetName: Assets.svg.arrowRotate, iconSize: 18, title: "Syncing preferences", onPressed: () { @@ -191,7 +191,8 @@ class GlobalSettingsView extends StatelessWidget { title: "Appearance", onPressed: () { Navigator.of(context).pushNamed( - AppearanceSettingsView.routeName); + AppearanceSettingsView.routeName, + ); }, ), if (Platform.isIOS) diff --git a/lib/pages/settings_views/global_settings_view/hidden_settings.dart b/lib/pages/settings_views/global_settings_view/hidden_settings.dart index d92b166d7..b4b849990 100644 --- a/lib/pages/settings_views/global_settings_view/hidden_settings.dart +++ b/lib/pages/settings_views/global_settings_view/hidden_settings.dart @@ -5,9 +5,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/providers/global/debug_service_provider.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -56,11 +56,15 @@ class HiddenSettings extends StatelessWidget { .read(notificationsProvider) .delete(notifs[0], true); - unawaited(showFloatingFlushBar( - type: FlushBarType.success, - message: "Notification history deleted", - context: context, - )); + if (context.mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "Notification history deleted", + context: context, + ), + ); + } }, child: RoundedWhiteContainer( child: Text( @@ -109,13 +113,17 @@ class HiddenSettings extends StatelessWidget { onTap: () async { await ref .read(debugServiceProvider) - .deleteAllMessages(); + .deleteAllLogs(); - unawaited(showFloatingFlushBar( - type: FlushBarType.success, - message: "Debug Logs deleted", - context: context, - )); + if (context.mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "Debug Logs deleted", + context: context, + ), + ); + } }, child: RoundedWhiteContainer( child: Text( @@ -128,6 +136,98 @@ class HiddenSettings extends StatelessWidget { ), ); }), + // const SizedBox( + // height: 12, + // ), + // Consumer(builder: (_, ref, __) { + // return GestureDetector( + // onTap: () async { + // final x = + // await MajesticBankAPI.instance.getRates(); + // print(x); + // }, + // child: RoundedWhiteContainer( + // child: Text( + // "Click me", + // style: STextStyles.button(context).copyWith( + // color: Theme.of(context) + // .extension()! + // .accentColorDark), + // ), + // ), + // ); + // }), + const SizedBox( + height: 12, + ), + Consumer(builder: (_, ref, __) { + return GestureDetector( + onTap: () async { + ref + .read(priceAnd24hChangeNotifierProvider) + .tokenContractAddressesToCheck + .add( + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); + ref + .read(priceAnd24hChangeNotifierProvider) + .tokenContractAddressesToCheck + .add( + "0xdAC17F958D2ee523a2206206994597C13D831ec7"); + await ref + .read(priceAnd24hChangeNotifierProvider) + .updatePrice(); + + final x = ref + .read(priceAnd24hChangeNotifierProvider) + .getTokenPrice( + "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"); + + print( + "PRICE 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48: $x"); + }, + child: RoundedWhiteContainer( + child: Text( + "Click me", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + ), + ); + }), + const SizedBox( + height: 12, + ), + Consumer(builder: (_, ref, __) { + return GestureDetector( + onTap: () async { + // final erc20 = Erc20ContractInfo( + // contractAddress: 'some con', + // name: "loonamsn", + // symbol: "DD", + // decimals: 19, + // ); + // + // final json = erc20.toJson(); + // + // print(json); + // + // final ee = EthContractInfo.fromJson(json); + // + // print(ee); + }, + child: RoundedWhiteContainer( + child: Text( + "Click me", + style: STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + ), + ); + }), const SizedBox( height: 12, ), @@ -174,9 +274,9 @@ class HiddenSettings extends StatelessWidget { // builder: (_) { // return StackDialogBase( // child: SizedBox( - // width: 200, + // width: 300, // child: Lottie.asset( - // Assets.lottie.test2, + // Assets.lottie.plain(Coin.bitcoincash), // ), // ), // ); @@ -187,8 +287,9 @@ class HiddenSettings extends StatelessWidget { // child: Text( // "Lottie test", // style: STextStyles.button(context).copyWith( - // color: Theme.of(context).extension()!.accentColorDark - // ), + // color: Theme.of(context) + // .extension()! + // .accentColorDark), // ), // ), // ), diff --git a/lib/pages/settings_views/global_settings_view/language_view.dart b/lib/pages/settings_views/global_settings_view/language_view.dart index 9ba2d6dd2..0d7b34216 100644 --- a/lib/pages/settings_views/global_settings_view/language_view.dart +++ b/lib/pages/settings_views/global_settings_view/language_view.dart @@ -2,11 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/languages_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index e29b0888f..1ff1a1359 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -3,12 +3,13 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/svg.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; @@ -17,7 +18,6 @@ import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/test_epic_box_connection.dart'; import 'package:stackwallet/utilities/test_monero_node_connection.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; @@ -30,6 +30,7 @@ import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; import 'package:uuid/uuid.dart'; +// import 'package:web3dart/web3dart.dart'; enum AddEditNodeViewType { add, edit } @@ -62,6 +63,44 @@ class _AddEditNodeViewState extends ConsumerState { late bool saveEnabled; late bool testConnectionEnabled; + Future _xmrHelper(String url, int? port) async { + final uri = Uri.parse(url); + + final String path = uri.path.isEmpty ? "/json_rpc" : uri.path; + + final uriString = "${uri.scheme}://${uri.host}:${port ?? 0}$path"; + + ref.read(nodeFormDataProvider).useSSL = true; + + final response = await testMoneroNodeConnection( + Uri.parse(uriString), + false, + ); + + if (response.cert != null) { + if (mounted) { + final shouldAllowBadCert = await showBadX509CertificateDialog( + response.cert!, + response.url!, + response.port!, + context, + ); + + if (shouldAllowBadCert) { + final response = + await testMoneroNodeConnection(Uri.parse(uriString), true); + ref.read(nodeFormDataProvider).host = url; + return response.success; + } + } + } else { + ref.read(nodeFormDataProvider).host = url; + return response.success; + } + + return false; + } + Future _testConnection({bool showFlushBar = true}) async { final formData = ref.read(nodeFormDataProvider); @@ -86,41 +125,19 @@ class _AddEditNodeViewState extends ConsumerState { case Coin.monero: case Coin.wownero: try { - final uri = Uri.parse(formData.host!); - if (uri.scheme.startsWith("http")) { - final String path = uri.path.isEmpty ? "/json_rpc" : uri.path; + final url = formData.host!; + final uri = Uri.tryParse(url); + if (uri != null) { + if (!uri.hasScheme) { + // try https first + testPassed = await _xmrHelper("https://$url", formData.port); - String uriString = - "${uri.scheme}://${uri.host}:${formData.port ?? 0}$path"; - - if (uri.host == "https") { - ref.read(nodeFormDataProvider).useSSL = true; - } else { - ref.read(nodeFormDataProvider).useSSL = false; - } - - final response = await testMoneroNodeConnection( - Uri.parse(uriString), - false, - ); - - if (response.cert != null) { - if (mounted) { - final shouldAllowBadCert = await showBadX509CertificateDialog( - response.cert!, - response.url!, - response.port!, - context, - ); - - if (shouldAllowBadCert) { - final response = await testMoneroNodeConnection( - Uri.parse(uriString), true); - testPassed = response.success; - } + if (testPassed == false) { + // try http + testPassed = await _xmrHelper("http://$url", formData.port); } } else { - testPassed = response.success; + testPassed = await _xmrHelper(url, formData.port); } } } catch (e, s) { @@ -156,9 +173,17 @@ class _AddEditNodeViewState extends ConsumerState { } break; + + case Coin.ethereum: + // final client = Web3Client( + // "https://mainnet.infura.io/v3/22677300bf774e49a458b73313ee56ba", + // Client()); + try { + // await client.getSyncStatus(); + } catch (_) {} } - if (showFlushBar) { + if (showFlushBar && mounted) { if (testPassed) { unawaited(showFloatingFlushBar( type: FlushBarType.success, @@ -182,7 +207,7 @@ class _AddEditNodeViewState extends ConsumerState { bool? shouldSave; - if (!canConnect) { + if (!canConnect && mounted) { await showDialog( context: context, useSafeArea: true, @@ -284,7 +309,7 @@ class _AddEditNodeViewState extends ConsumerState { }, style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), child: Text( "Save", style: STextStyles.button(context), @@ -328,6 +353,7 @@ class _AddEditNodeViewState extends ConsumerState { enabled: true, coinName: coin.name, isFailover: formData.isFailover!, + trusted: formData.trusted!, isDown: false, ); @@ -352,6 +378,7 @@ class _AddEditNodeViewState extends ConsumerState { enabled: true, coinName: coin.name, isFailover: formData.isFailover!, + trusted: formData.trusted!, isDown: false, ); @@ -601,10 +628,10 @@ class _AddEditNodeViewState extends ConsumerState { style: saveEnabled ? Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context) + .getPrimaryEnabledButtonStyle(context) : Theme.of(context) .extension()! - .getPrimaryDisabledButtonColor(context), + .getPrimaryDisabledButtonStyle(context), onPressed: saveEnabled ? attemptSave : null, child: Text( "Save", @@ -621,11 +648,11 @@ class _AddEditNodeViewState extends ConsumerState { class NodeFormData { String? name, host, login, password; int? port; - bool? useSSL, isFailover; + bool? useSSL, isFailover, trusted; @override String toString() { - return "{ name: $name, host: $host, port: $port, useSSL: $useSSL }"; + return "{ name: $name, host: $host, port: $port, useSSL: $useSSL, trusted: $trusted }"; } } @@ -666,6 +693,7 @@ class _NodeFormState extends ConsumerState { bool _useSSL = false; bool _isFailover = false; + bool _trusted = false; int? port; late bool enableSSLCheckbox; @@ -689,8 +717,10 @@ class _NodeFormState extends ConsumerState { case Coin.firoTestNet: case Coin.dogecoinTestNet: case Coin.epicCash: + case Coin.eCash: return false; + case Coin.ethereum: case Coin.monero: case Coin.wownero: return true; @@ -718,6 +748,9 @@ class _NodeFormState extends ConsumerState { return enable; } + bool get shouldBeReadOnly => + widget.readOnly || widget.node?.isDefault == true; + void _updateState() { port = int.tryParse(_portController.text); onChanged?.call(canSave, canTestConnection); @@ -733,6 +766,7 @@ class _NodeFormState extends ConsumerState { ref.read(nodeFormDataProvider).port = port; ref.read(nodeFormDataProvider).useSSL = _useSSL; ref.read(nodeFormDataProvider).isFailover = _isFailover; + ref.read(nodeFormDataProvider).trusted = _trusted; } @override @@ -764,12 +798,12 @@ class _NodeFormState extends ConsumerState { _usernameController.text = node.loginName ?? ""; _useSSL = node.useSSL; _isFailover = node.isFailover; + _trusted = node.trusted ?? false; if (widget.coin == Coin.epicCash) { enableSSLCheckbox = !node.host.startsWith("http"); } else { enableSSLCheckbox = true; } - print("enableSSLCheckbox: $enableSSLCheckbox"); WidgetsBinding.instance.addPostFrameCallback((_) { // update provider state object so test connection works without having to modify a field in the ui first @@ -812,7 +846,7 @@ class _NodeFormState extends ConsumerState { autocorrect: Util.isDesktop ? false : true, enableSuggestions: Util.isDesktop ? false : true, key: const Key("addCustomNodeNodeNameFieldKey"), - readOnly: widget.readOnly, + readOnly: shouldBeReadOnly, enabled: enableField(_nameController), controller: _nameController, focusNode: _nameFocusNode, @@ -822,7 +856,7 @@ class _NodeFormState extends ConsumerState { _nameFocusNode, context, ).copyWith( - suffixIcon: !widget.readOnly && _nameController.text.isNotEmpty + suffixIcon: !shouldBeReadOnly && _nameController.text.isNotEmpty ? Padding( padding: const EdgeInsets.only(right: 0), child: UnconstrainedBox( @@ -858,7 +892,7 @@ class _NodeFormState extends ConsumerState { autocorrect: Util.isDesktop ? false : true, enableSuggestions: Util.isDesktop ? false : true, key: const Key("addCustomNodeNodeAddressFieldKey"), - readOnly: widget.readOnly, + readOnly: shouldBeReadOnly, enabled: enableField(_hostController), controller: _hostController, focusNode: _hostFocusNode, @@ -870,7 +904,7 @@ class _NodeFormState extends ConsumerState { _hostFocusNode, context, ).copyWith( - suffixIcon: !widget.readOnly && _hostController.text.isNotEmpty + suffixIcon: !shouldBeReadOnly && _hostController.text.isNotEmpty ? Padding( padding: const EdgeInsets.only(right: 0), child: UnconstrainedBox( @@ -917,7 +951,7 @@ class _NodeFormState extends ConsumerState { autocorrect: Util.isDesktop ? false : true, enableSuggestions: Util.isDesktop ? false : true, key: const Key("addCustomNodeNodePortFieldKey"), - readOnly: widget.readOnly, + readOnly: shouldBeReadOnly, enabled: enableField(_portController), controller: _portController, focusNode: _portFocusNode, @@ -929,7 +963,7 @@ class _NodeFormState extends ConsumerState { _portFocusNode, context, ).copyWith( - suffixIcon: !widget.readOnly && _portController.text.isNotEmpty + suffixIcon: !shouldBeReadOnly && _portController.text.isNotEmpty ? Padding( padding: const EdgeInsets.only(right: 0), child: UnconstrainedBox( @@ -966,9 +1000,8 @@ class _NodeFormState extends ConsumerState { autocorrect: Util.isDesktop ? false : true, enableSuggestions: Util.isDesktop ? false : true, controller: _usernameController, - readOnly: widget.readOnly, + readOnly: shouldBeReadOnly, enabled: enableField(_usernameController), - keyboardType: TextInputType.number, focusNode: _usernameFocusNode, style: STextStyles.field(context), decoration: standardInputDecoration( @@ -977,7 +1010,7 @@ class _NodeFormState extends ConsumerState { context, ).copyWith( suffixIcon: - !widget.readOnly && _usernameController.text.isNotEmpty + !shouldBeReadOnly && _usernameController.text.isNotEmpty ? Padding( padding: const EdgeInsets.only(right: 0), child: UnconstrainedBox( @@ -1015,9 +1048,9 @@ class _NodeFormState extends ConsumerState { autocorrect: Util.isDesktop ? false : true, enableSuggestions: Util.isDesktop ? false : true, controller: _passwordController, - readOnly: widget.readOnly, + readOnly: shouldBeReadOnly, enabled: enableField(_passwordController), - keyboardType: TextInputType.number, + obscureText: true, focusNode: _passwordFocusNode, style: STextStyles.field(context), decoration: standardInputDecoration( @@ -1026,7 +1059,7 @@ class _NodeFormState extends ConsumerState { context, ).copyWith( suffixIcon: - !widget.readOnly && _passwordController.text.isNotEmpty + !shouldBeReadOnly && _passwordController.text.isNotEmpty ? Padding( padding: const EdgeInsets.only(right: 0), child: UnconstrainedBox( @@ -1059,7 +1092,7 @@ class _NodeFormState extends ConsumerState { Row( children: [ GestureDetector( - onTap: !widget.readOnly && enableSSLCheckbox + onTap: !shouldBeReadOnly && enableSSLCheckbox ? () { setState(() { _useSSL = !_useSSL; @@ -1075,7 +1108,7 @@ class _NodeFormState extends ConsumerState { width: 20, height: 20, child: Checkbox( - fillColor: !widget.readOnly && enableSSLCheckbox + fillColor: !shouldBeReadOnly && enableSSLCheckbox ? null : MaterialStateProperty.all(Theme.of(context) .extension()! @@ -1083,7 +1116,7 @@ class _NodeFormState extends ConsumerState { materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, value: _useSSL, - onChanged: !widget.readOnly && enableSSLCheckbox + onChanged: !shouldBeReadOnly && enableSSLCheckbox ? (newValue) { setState(() { _useSSL = newValue!; @@ -1106,6 +1139,57 @@ class _NodeFormState extends ConsumerState { ), ], ), + if (widget.coin == Coin.monero || widget.coin == Coin.wownero) + Row( + children: [ + GestureDetector( + onTap: !widget.readOnly /*&& trustedCheckbox*/ + ? () { + setState(() { + _trusted = !_trusted; + }); + _updateState(); + } + : null, + child: Container( + color: Colors.transparent, + child: Row( + children: [ + SizedBox( + width: 20, + height: 20, + child: Checkbox( + fillColor: !widget.readOnly + ? null + : MaterialStateProperty.all(Theme.of(context) + .extension()! + .checkboxBGDisabled), + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + value: _trusted, + onChanged: !widget.readOnly + ? (newValue) { + setState(() { + _trusted = newValue!; + }); + _updateState(); + } + : null, + ), + ), + const SizedBox( + width: 12, + ), + Text( + "Trusted", + style: STextStyles.itemSubtitle12(context), + ) + ], + ), + ), + ), + ], + ), if (widget.coin != Coin.monero && widget.coin != Coin.wownero && widget.coin != Coin.epicCash) diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/coin_nodes_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/coin_nodes_view.dart index 91e6871f3..03fc37f4f 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/coin_nodes_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/coin_nodes_view.dart @@ -1,12 +1,15 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart'; import 'package:stackwallet/pages/settings_views/sub_widgets/nodes_list.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; @@ -53,8 +56,12 @@ class _CoinNodesViewState extends ConsumerState { const SizedBox( width: 32, ), - SvgPicture.asset( - Assets.svg.iconFor(coin: widget.coin), + SvgPicture.file( + File( + ref.watch( + coinIconProvider(widget.coin), + ), + ), width: 24, height: 24, ), @@ -92,7 +99,7 @@ class _CoinNodesViewState extends ConsumerState { ), textAlign: TextAlign.left, ), - BlueTextButton( + CustomTextButton( text: "Add new node", onTap: () { Navigator.of(context).pushNamed( diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/manage_nodes_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/manage_nodes_view.dart index 743fc6957..7caf4647f 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/manage_nodes_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/manage_nodes_view.dart @@ -1,13 +1,15 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/coin_nodes_view.dart'; import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -102,8 +104,12 @@ class _ManageNodesViewState extends ConsumerState { padding: const EdgeInsets.all(12), child: Row( children: [ - SvgPicture.asset( - Assets.svg.iconFor(coin: coin), + SvgPicture.file( + File( + ref.watch( + coinIconProvider(coin), + ), + ), width: 24, height: 24, ), diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart index ad8ad7301..4905393ac 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart @@ -8,6 +8,7 @@ import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; @@ -15,7 +16,6 @@ import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/test_epic_box_connection.dart'; import 'package:stackwallet/utilities/test_monero_node_connection.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; @@ -70,15 +70,13 @@ class _NodeDetailsViewState extends ConsumerState { switch (coin) { case Coin.epicCash: try { - - testPassed = await testEpicNodeConnection( - NodeFormData() - ..host = node!.host - ..useSSL = node.useSSL - ..port = node.port, - ) != - null; - + testPassed = await testEpicNodeConnection( + NodeFormData() + ..host = node!.host + ..useSSL = node.useSSL + ..port = node.port, + ) != + null; } catch (e, s) { Logging.instance.log("$e\n$s", level: LogLevel.Warning); testPassed = false; @@ -128,6 +126,7 @@ class _NodeDetailsViewState extends ConsumerState { case Coin.litecoin: case Coin.dogecoin: case Coin.firo: + case Coin.particl: case Coin.bitcoinTestNet: case Coin.firoTestNet: case Coin.dogecoinTestNet: @@ -388,6 +387,7 @@ class _NodeDetailsViewState extends ConsumerState { port: ref.read(nodeFormDataProvider).port, name: ref.read(nodeFormDataProvider).name, useSSL: ref.read(nodeFormDataProvider).useSSL, + trusted: ref.read(nodeFormDataProvider).trusted, loginName: ref.read(nodeFormDataProvider).login, isFailover: ref.read(nodeFormDataProvider).isFailover, diff --git a/lib/pages/settings_views/global_settings_view/security_views/change_pin_view/change_pin_view.dart b/lib/pages/settings_views/global_settings_view/security_views/change_pin_view/change_pin_view.dart index fb5722594..0e848ec12 100644 --- a/lib/pages/settings_views/global_settings_view/security_views/change_pin_view/change_pin_view.dart +++ b/lib/pages/settings_views/global_settings_view/security_views/change_pin_view/change_pin_view.dart @@ -2,13 +2,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/security_views/security_view.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_pin_put/custom_pin_put.dart'; @@ -123,6 +123,8 @@ class _ChangePinViewState extends ConsumerState { .background, counterText: "", ), + isRandom: + ref.read(prefsChangeNotifierProvider).randomizePIN, submittedFieldDecoration: _pinPutDecoration.copyWith( color: Theme.of(context) .extension()! @@ -188,6 +190,8 @@ class _ChangePinViewState extends ConsumerState { .background, counterText: "", ), + isRandom: + ref.read(prefsChangeNotifierProvider).randomizePIN, submittedFieldDecoration: _pinPutDecoration.copyWith( color: Theme.of(context) .extension()! diff --git a/lib/pages/settings_views/global_settings_view/security_views/security_view.dart b/lib/pages/settings_views/global_settings_view/security_views/security_view.dart index c2a64bb50..38be88839 100644 --- a/lib/pages/settings_views/global_settings_view/security_views/security_view.dart +++ b/lib/pages/settings_views/global_settings_view/security_views/security_view.dart @@ -4,9 +4,9 @@ import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/security_views/change_pin_view/change_pin_view.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/route_generator.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; @@ -146,6 +146,53 @@ class SecurityView extends StatelessWidget { }, ), ), + const SizedBox( + height: 8, + ), + RoundedWhiteContainer( + child: Consumer( + builder: (_, ref, __) { + return RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: null, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Randomize PIN Pad", + style: STextStyles.titleBold12(context), + textAlign: TextAlign.left, + ), + SizedBox( + height: 20, + width: 40, + child: DraggableSwitchButton( + isOn: ref.watch( + prefsChangeNotifierProvider + .select((value) => value.randomizePIN), + ), + onValueChanged: (newValue) { + ref + .read(prefsChangeNotifierProvider) + .randomizePIN = newValue; + }, + ), + ), + ], + ), + ), + ); + }, + ), + ), ], ), ), diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/auto_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/auto_backup_view.dart index 15b5a12fa..cad08892c 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/auto_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/auto_backup_view.dart @@ -6,11 +6,11 @@ import 'package:stackwallet/pages/settings_views/global_settings_view/stack_back import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart'; import 'package:stackwallet/providers/global/auto_swb_service_provider.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; @@ -85,7 +85,7 @@ class _AutoBackupViewState extends ConsumerState { leftButton: TextButton( style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), child: Text( "Back", style: STextStyles.button(context).copyWith( @@ -100,7 +100,7 @@ class _AutoBackupViewState extends ConsumerState { rightButton: TextButton( style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), child: Text( "Continue", style: STextStyles.button(context), @@ -142,7 +142,7 @@ class _AutoBackupViewState extends ConsumerState { leftButton: TextButton( style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), child: Text( "Back", style: STextStyles.button(context).copyWith( @@ -157,7 +157,7 @@ class _AutoBackupViewState extends ConsumerState { rightButton: TextButton( style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), child: Text( "Disable", style: STextStyles.button(context), @@ -327,7 +327,7 @@ class _AutoBackupViewState extends ConsumerState { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - BlueTextButton( + CustomTextButton( text: "Back up now", onTap: () { ref.read(autoSWBServiceProvider).doBackup(); @@ -448,7 +448,7 @@ class _AutoBackupViewState extends ConsumerState { height: 20, ), Center( - child: BlueTextButton( + child: CustomTextButton( text: "Edit Auto Backup", onTap: () async { Navigator.of(context) diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart index 8e8731105..71b01f698 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_auto_backup_view.dart @@ -13,14 +13,13 @@ import 'package:stackwallet/pages/settings_views/global_settings_view/stack_back import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/sub_views/backup_frequency_type_select_sheet.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; @@ -483,10 +482,10 @@ class _EnableAutoBackupViewState extends ConsumerState { style: shouldEnableCreate ? Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context) + .getPrimaryEnabledButtonStyle(context) : Theme.of(context) .extension()! - .getPrimaryDisabledButtonColor(context), + .getPrimaryDisabledButtonStyle(context), onPressed: !shouldEnableCreate ? null : () async { @@ -596,7 +595,7 @@ class _EnableAutoBackupViewState extends ConsumerState { fileToSave, adkString, jsonEncode(backup), - adkVersion: adkVersion, + adkVersion, ); // this future should already be complete unless there was an error encrypting diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_information_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_information_view.dart index 012477a5b..a1fe7aaf6 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_information_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_information_view.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -67,7 +67,7 @@ class CreateBackupInfoView extends StatelessWidget { TextButton( style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), onPressed: () { Navigator.of(context) .pushNamed(CreateBackupView.routeName); diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart index 4d69ce4e9..4ecf1470c 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart @@ -9,12 +9,11 @@ import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/swb_file_system.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; @@ -454,10 +453,10 @@ class _RestoreFromFileViewState extends State { style: shouldEnableCreate ? Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context) + .getPrimaryEnabledButtonStyle(context) : Theme.of(context) .extension()! - .getPrimaryDisabledButtonColor(context), + .getPrimaryDisabledButtonStyle(context), onPressed: !shouldEnableCreate ? null : () async { diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/dialogs/cancel_stack_restore_dialog.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/dialogs/cancel_stack_restore_dialog.dart index 905cdea72..49b5728d6 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/dialogs/cancel_stack_restore_dialog.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/dialogs/cancel_stack_restore_dialog.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; @@ -27,7 +27,7 @@ class CancelStackRestoreDialog extends StatelessWidget { leftButton: TextButton( style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), child: Text( "Back", style: STextStyles.itemSubtitle12(context), @@ -39,7 +39,7 @@ class CancelStackRestoreDialog extends StatelessWidget { rightButton: TextButton( style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), child: Text( "Yes, cancel", style: STextStyles.itemSubtitle12(context).copyWith( diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart index 0cc85d77c..04e96667c 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/edit_auto_backup_view.dart @@ -15,6 +15,7 @@ import 'package:stackwallet/pages/settings_views/global_settings_view/stack_back import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/sub_views/backup_frequency_type_select_sheet.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; @@ -22,7 +23,6 @@ import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; @@ -187,7 +187,7 @@ class _EditAutoBackupViewState extends ConsumerState { fileToSave, adkString, jsonEncode(backup), - adkVersion: adkVersion, + adkVersion, ); // this future should already be complete unless there was an error encrypting @@ -781,10 +781,10 @@ class _EditAutoBackupViewState extends ConsumerState { style: shouldEnableCreate ? Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context) + .getPrimaryEnabledButtonStyle(context) : Theme.of(context) .extension()! - .getPrimaryDisabledButtonColor(context), + .getPrimaryDisabledButtonStyle(context), onPressed: !shouldEnableCreate ? null : onSavePressed, child: Text( "Save", diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart index 62a8bf9cb..b89954521 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart @@ -4,16 +4,14 @@ import 'dart:io'; import 'dart:typed_data'; import 'package:stack_wallet_backup/stack_wallet_backup.dart'; -import 'package:stackwallet/hive/db.dart'; -import 'package:stackwallet/models/contact.dart'; -import 'package:stackwallet/models/contact_address_entry.dart'; +import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; +import 'package:stackwallet/models/isar/models/contact_entry.dart'; import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/models/stack_restoring_ui_state.dart'; import 'package:stackwallet/models/trade_wallet_lookup.dart'; import 'package:stackwallet/models/wallet_restore_state.dart'; -import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; import 'package:stackwallet/services/address_book_service.dart'; import 'package:stackwallet/services/coins/coin_service.dart'; import 'package:stackwallet/services/coins/manager.dart'; @@ -133,9 +131,9 @@ abstract class SWB { static Future encryptStackWalletWithADK( String fileToSave, String adk, - String plaintext, { - int? adkVersion, - }) async { + String plaintext, + int adkVersion, + ) async { try { File backupFile = File(fileToSave); if (!backupFile.existsSync()) { @@ -246,7 +244,6 @@ abstract class SWB { final _prefs = Prefs.instance; await _prefs.init(); prefs['currency'] = _prefs.currency; - prefs['exchangeRateType'] = _prefs.exchangeRateType.name; prefs['useBiometrics'] = _prefs.useBiometrics; prefs['hasPin'] = _prefs.hasPin; prefs['language'] = _prefs.language; @@ -268,7 +265,7 @@ abstract class SWB { ); AddressBookService addressBookService = AddressBookService(); - var addresses = await addressBookService.addressBookEntries; + var addresses = addressBookService.contacts; backupJson['addressBookEntries'] = addresses.map((e) => e.toMap()).toList(); @@ -284,6 +281,7 @@ abstract class SWB { backupWallet['id'] = manager.walletId; backupWallet['isFavorite'] = manager.isFavorite; backupWallet['mnemonic'] = await manager.mnemonic; + backupWallet['mnemonicPassphrase'] = await manager.mnemonicPassphrase; backupWallet['coinName'] = manager.coin.name; backupWallet['storedChainHeight'] = DB.instance .get(boxName: manager.walletId, key: 'storedChainHeight'); @@ -357,12 +355,15 @@ abstract class SWB { List mnemonicList = (walletbackup['mnemonic'] as List) .map((e) => e as String) .toList(); - String mnemonic = mnemonicList.join(" ").trim(); + final String mnemonic = mnemonicList.join(" ").trim(); + final String mnemonicPassphrase = + walletbackup['mnemonicPassphrase'] as String? ?? ""; uiState?.update( walletId: manager.walletId, restoringStatus: StackRestoringStatus.restoring, mnemonic: mnemonic, + mnemonicPassphrase: mnemonicPassphrase, ); if (_shouldCancelRestore) { @@ -403,6 +404,7 @@ abstract class SWB { // without using them await manager.recoverFromMnemonic( mnemonic: mnemonic, + mnemonicPassphrase: mnemonicPassphrase, maxUnusedAddressGap: manager.coin == Coin.firo ? 50 : 20, maxNumberOfIndexesToCheck: 1000, height: restoreHeight, @@ -431,6 +433,7 @@ abstract class SWB { address: currentAddress, height: restoreHeight, mnemonic: mnemonic, + mnemonicPassphrase: mnemonicPassphrase, ); } catch (e, s) { Logging.instance.log("$e $s", level: LogLevel.Warning); @@ -439,6 +442,7 @@ abstract class SWB { restoringStatus: StackRestoringStatus.failed, manager: manager, mnemonic: mnemonic, + mnemonicPassphrase: mnemonicPassphrase, ); return false; } @@ -794,7 +798,7 @@ abstract class SWB { // contacts final addressBookService = AddressBookService(); - final allContactIds = addressBookService.contacts.map((e) => e.id); + final allContactIds = addressBookService.contacts.map((e) => e.customId); if (addressBookEntries == null) { // if no contacts were present before attempted restore then delete any that @@ -818,21 +822,20 @@ abstract class SWB { List addresses = []; for (var address in (contact['addresses'] as List)) { addresses.add( - ContactAddressEntry( - coin: Coin.values - .firstWhere((element) => element.name == address['coin']), - address: address['address'] as String, - label: address['label'] as String, - ), + ContactAddressEntry() + ..coinName = address['coin'] as String + ..address = address['address'] as String + ..label = address['label'] as String + ..other = address['other'] as String?, ); } await addressBookService.editContact( - Contact( + ContactEntry( emojiChar: contact['emoji'] as String?, name: contact['name'] as String, addresses: addresses, isFavorite: contact['isFavorite'] as bool, - id: contact['id'] as String, + customId: contact['id'] as String, ), ); } else { @@ -989,9 +992,6 @@ abstract class SWB { final _prefs = Prefs.instance; await _prefs.init(); _prefs.currency = prefs['currency'] as String; - _prefs.exchangeRateType = prefs['exchangeRateType'] == "estimated" - ? ExchangeRateType.estimated - : ExchangeRateType.fixed; // _prefs.useBiometrics = prefs['useBiometrics'] as bool; // _prefs.hasPin = prefs['hasPin'] as bool; _prefs.language = prefs['language'] as String; @@ -1024,21 +1024,20 @@ abstract class SWB { List addresses = []; for (var address in (contact['addresses'] as List)) { addresses.add( - ContactAddressEntry( - coin: Coin.values - .firstWhere((element) => element.name == address['coin']), - address: address['address'] as String, - label: address['label'] as String, - ), + ContactAddressEntry() + ..coinName = address['coin'] as String + ..address = address['address'] as String + ..label = address['label'] as String + ..other = address['other'] as String?, ); } await addressBookService.addContact( - Contact( + ContactEntry( emojiChar: contact['emoji'] as String?, name: contact['name'] as String, addresses: addresses, isFavorite: contact['isFavorite'] as bool, - id: contact['id'] as String, + customId: contact['id'] as String, ), ); } diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_encrypted_string_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_encrypted_string_view.dart index 14a262d99..a81561471 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_encrypted_string_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_encrypted_string_view.dart @@ -7,10 +7,10 @@ import 'package:stackwallet/pages/home_view/home_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart'; import 'package:stackwallet/route_generator.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; @@ -158,10 +158,10 @@ class _RestoreFromEncryptedStringViewState style: passwordController.text.isEmpty ? Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context) + .getPrimaryEnabledButtonStyle(context) : Theme.of(context) .extension()! - .getPrimaryDisabledButtonColor(context), + .getPrimaryDisabledButtonStyle(context), onPressed: passwordController.text.isEmpty ? null : () async { diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart index 4b0d1e044..a63b23e98 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart @@ -10,11 +10,11 @@ import 'package:stackwallet/pages/settings_views/global_settings_view/stack_back import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/swb_file_system.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart'; import 'package:stackwallet/route_generator.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; @@ -280,10 +280,10 @@ class _RestoreFromFileViewState extends ConsumerState { fileLocationController.text.isEmpty ? Theme.of(context) .extension()! - .getPrimaryDisabledButtonColor(context) + .getPrimaryDisabledButtonStyle(context) : Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), onPressed: passwordController.text.isEmpty || fileLocationController.text.isEmpty ? null diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/stack_backup_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/stack_backup_view.dart index fe163cb66..3a0e61aa9 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/stack_backup_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/stack_backup_view.dart @@ -3,10 +3,10 @@ import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/auto_backup_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/create_backup_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/restore_from_file_view.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/backup_frequency_type_select_sheet.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/backup_frequency_type_select_sheet.dart index b64defcf7..476e1e5df 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/backup_frequency_type_select_sheet.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/backup_frequency_type_select_sheet.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; class BackupFrequencyTypeSelectSheet extends ConsumerWidget { const BackupFrequencyTypeSelectSheet({ diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/recovery_phrase_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/recovery_phrase_view.dart index 7b30a21b0..a4f160800 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/recovery_phrase_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/recovery_phrase_view.dart @@ -3,11 +3,10 @@ import 'package:flutter/services.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; class RecoverPhraseView extends StatelessWidget { diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart index 40a0b0f9f..79b52aab0 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_views/stack_restore_progress_view.dart @@ -15,11 +15,11 @@ import 'package:stackwallet/providers/desktop/current_desktop_menu_item.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/stack_restore/stack_restoring_ui_state_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/enums/stack_restoring_status.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; @@ -214,7 +214,7 @@ class _StackRestoreProgressViewState @override void initState() { WidgetsBinding.instance.addPostFrameCallback((_) async { - _restore(); + unawaited(_restore()); }); super.initState(); } @@ -325,7 +325,7 @@ class _StackRestoreProgressViewState : null, ) : RoundedContainer( - padding: EdgeInsets.all(0), + padding: EdgeInsets.zero, color: Theme.of(context) .extension()! .popupBG, @@ -411,7 +411,7 @@ class _StackRestoreProgressViewState : null, ) : RoundedContainer( - padding: EdgeInsets.all(0), + padding: EdgeInsets.zero, color: Theme.of(context) .extension()! .popupBG, @@ -497,7 +497,7 @@ class _StackRestoreProgressViewState : null, ) : RoundedContainer( - padding: EdgeInsets.all(0), + padding: EdgeInsets.zero, color: Theme.of(context) .extension()! .popupBG, @@ -548,44 +548,42 @@ class _StackRestoreProgressViewState final state = ref.watch(stackRestoringUIStateProvider .select((value) => value.trades)); return !isDesktop - ? Container( - child: RestoringItemCard( - left: SizedBox( - width: 32, - height: 32, - child: RoundedContainer( - padding: const EdgeInsets.all(0), - color: Theme.of(context) - .extension()! - .buttonBackSecondary, - child: Center( - child: SvgPicture.asset( - Assets.svg.arrowRotate2, - width: 16, - height: 16, - color: Theme.of(context) - .extension()! - .accentColorDark, - ), + ? RestoringItemCard( + left: SizedBox( + width: 32, + height: 32, + child: RoundedContainer( + padding: const EdgeInsets.all(0), + color: Theme.of(context) + .extension()! + .buttonBackSecondary, + child: Center( + child: SvgPicture.asset( + Assets.svg.arrowsTwoWay, + width: 16, + height: 16, + color: Theme.of(context) + .extension()! + .accentColorDark, ), ), ), - right: SizedBox( - width: 20, - height: 20, - child: _getIconForState(state), - ), - title: "Exchange history", - subTitle: state == StackRestoringStatus.failed - ? Text( - "Something went wrong", - style: STextStyles.errorSmall(context), - ) - : null, ), + right: SizedBox( + width: 20, + height: 20, + child: _getIconForState(state), + ), + title: "Exchange history", + subTitle: state == StackRestoringStatus.failed + ? Text( + "Something went wrong", + style: STextStyles.errorSmall(context), + ) + : null, ) : RoundedContainer( - padding: EdgeInsets.all(0), + padding: EdgeInsets.zero, color: Theme.of(context) .extension()! .popupBG, @@ -603,7 +601,7 @@ class _StackRestoreProgressViewState .buttonBackSecondary, child: Center( child: SvgPicture.asset( - Assets.svg.arrowRotate2, + Assets.svg.arrowsTwoWay, width: 16, height: 16, color: Theme.of(context) @@ -669,7 +667,7 @@ class _StackRestoreProgressViewState }, style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), child: Text( _success ? "OK" : "Cancel restore process", style: STextStyles.button(context).copyWith( diff --git a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_widgets/restoring_wallet_card.dart b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_widgets/restoring_wallet_card.dart index 2239eee71..2db94aff7 100644 --- a/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_widgets/restoring_wallet_card.dart +++ b/lib/pages/settings_views/global_settings_view/stack_backup_views/sub_widgets/restoring_wallet_card.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; @@ -6,11 +8,12 @@ import 'package:stackwallet/pages/settings_views/global_settings_view/stack_back import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/sub_widgets/restoring_item_card.dart'; import 'package:stackwallet/providers/stack_restore/stack_restoring_ui_state_provider.dart'; import 'package:stackwallet/route_generator.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/stack_restoring_status.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; @@ -80,9 +83,9 @@ class _RestoringWalletCardState extends ConsumerState { .extension()! .colorForCoin(coin), child: Center( - child: SvgPicture.asset( - Assets.svg.iconFor( - coin: coin, + child: SvgPicture.file( + File( + ref.watch(coinIconProvider(coin)), ), height: 20, width: 20, @@ -109,6 +112,8 @@ class _RestoringWalletCardState extends ConsumerState { if (mnemonicList.isEmpty) { await manager.recoverFromMnemonic( mnemonic: ref.read(provider).mnemonic!, + mnemonicPassphrase: + ref.read(provider).mnemonicPassphrase!, maxUnusedAddressGap: maxUnusedAddressGap, maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, height: ref.read(provider).height ?? 0, @@ -221,9 +226,11 @@ class _RestoringWalletCardState extends ConsumerState { .extension()! .colorForCoin(coin), child: Center( - child: SvgPicture.asset( - Assets.svg.iconFor( - coin: coin, + child: SvgPicture.file( + File( + ref.watch( + coinIconProvider(coin), + ), ), height: 20, width: 20, @@ -250,6 +257,8 @@ class _RestoringWalletCardState extends ConsumerState { if (mnemonicList.isEmpty) { await manager.recoverFromMnemonic( mnemonic: ref.read(provider).mnemonic!, + mnemonicPassphrase: + ref.read(provider).mnemonicPassphrase!, maxUnusedAddressGap: maxUnusedAddressGap, maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, diff --git a/lib/pages/settings_views/global_settings_view/startup_preferences/startup_preferences_view.dart b/lib/pages/settings_views/global_settings_view/startup_preferences/startup_preferences_view.dart index 186b9b293..b76b63f46 100644 --- a/lib/pages/settings_views/global_settings_view/startup_preferences/startup_preferences_view.dart +++ b/lib/pages/settings_views/global_settings_view/startup_preferences/startup_preferences_view.dart @@ -1,10 +1,14 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/startup_preferences/startup_wallet_selection_view.dart'; -import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -21,6 +25,31 @@ class StartupPreferencesView extends ConsumerStatefulWidget { class _StartupPreferencesViewState extends ConsumerState { + bool safe = true; + + @override + void initState() { + final possibleWalletId = + ref.read(prefsChangeNotifierProvider).startupWalletId; + + // check if wallet exists (hasn't been deleted or otherwise missing) + if (possibleWalletId != null) { + try { + ref.read(walletsChangeNotifierProvider).getManager(possibleWalletId); + } catch (_) { + safe = false; + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + ref.read(prefsChangeNotifierProvider).startupWalletId = null; + setState(() { + safe = true; + }); + }); + } + } + + super.initState(); + } + @override Widget build(BuildContext context) { return Background( @@ -195,13 +224,75 @@ class _StartupPreferencesViewState context), textAlign: TextAlign.left, ), - Text( - "Select a specific wallet to load into on startup", - style: - STextStyles.itemSubtitle( - context), - textAlign: TextAlign.left, - ), + (safe && + ref.watch( + prefsChangeNotifierProvider + .select((value) => + value + .startupWalletId), + ) != + null) + ? Padding( + padding: + const EdgeInsets + .only(top: 12), + child: Row( + children: [ + SvgPicture.file( + File( + ref.watch( + coinIconProvider( + ref + .watch( + walletsChangeNotifierProvider + .select( + (value) => + value.getManager( + ref.watch( + prefsChangeNotifierProvider.select((value) => value.startupWalletId!), + ), + ), + ), + ) + .coin, + ), + ), + ), + ), + const SizedBox( + width: 10, + ), + Text( + ref + .watch( + walletsChangeNotifierProvider + .select( + (value) => + value + .getManager( + ref.watch( + prefsChangeNotifierProvider.select((value) => + value.startupWalletId!), + ), + ), + ), + ) + .walletName, + style: STextStyles + .itemSubtitle( + context), + ), + ], + ), + ) + : Text( + "Select a specific wallet to load into on startup", + style: STextStyles + .itemSubtitle( + context), + textAlign: + TextAlign.left, + ), ], ), ), diff --git a/lib/pages/settings_views/global_settings_view/startup_preferences/startup_wallet_selection_view.dart b/lib/pages/settings_views/global_settings_view/startup_preferences/startup_wallet_selection_view.dart index 975e8394d..9b8a95909 100644 --- a/lib/pages/settings_views/global_settings_view/startup_preferences/startup_wallet_selection_view.dart +++ b/lib/pages/settings_views/global_settings_view/startup_preferences/startup_wallet_selection_view.dart @@ -1,11 +1,13 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; @@ -102,9 +104,12 @@ class _StartupWalletSelectionViewState ), child: Padding( padding: const EdgeInsets.all(4), - child: SvgPicture.asset( - Assets.svg - .iconFor(coin: manager.coin), + child: SvgPicture.file( + File( + ref.watch( + coinIconProvider(manager.coin), + ), + ), width: 20, height: 20, ), diff --git a/lib/pages/settings_views/global_settings_view/support_view.dart b/lib/pages/settings_views/global_settings_view/support_view.dart index 1cc3d35a1..0f38912c0 100644 --- a/lib/pages/settings_views/global_settings_view/support_view.dart +++ b/lib/pages/settings_views/global_settings_view/support_view.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; @@ -190,7 +190,7 @@ class AboutItem extends StatelessWidget { height: iconSize, color: Theme.of(context) .extension()! - .bottomNavIconIcon, + .topNavIconPrimary, ), ), const SizedBox( diff --git a/lib/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_options_view.dart b/lib/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_options_view.dart index 10a93d7e8..eac006e35 100644 --- a/lib/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_options_view.dart +++ b/lib/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_options_view.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/syncing_preferences_views/wallet_syncing_options_view.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/sync_type_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; diff --git a/lib/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_preferences_view.dart b/lib/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_preferences_view.dart index 6ce84627e..628d344e2 100644 --- a/lib/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_preferences_view.dart +++ b/lib/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_preferences_view.dart @@ -2,10 +2,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_options_view.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/sync_type_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; diff --git a/lib/pages/settings_views/global_settings_view/syncing_preferences_views/wallet_syncing_options_view.dart b/lib/pages/settings_views/global_settings_view/syncing_preferences_views/wallet_syncing_options_view.dart index 7cbc86b7a..ad0042dc8 100644 --- a/lib/pages/settings_views/global_settings_view/syncing_preferences_views/wallet_syncing_options_view.dart +++ b/lib/pages/settings_views/global_settings_view/syncing_preferences_views/wallet_syncing_options_view.dart @@ -1,17 +1,16 @@ -import 'package:decimal/decimal.dart'; +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/sync_type_enum.dart'; -import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; -import 'package:stackwallet/widgets/animated_text.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; @@ -119,9 +118,12 @@ class WalletSyncingOptionsView extends ConsumerWidget { ), child: Padding( padding: const EdgeInsets.all(4), - child: SvgPicture.asset( - Assets.svg - .iconFor(coin: manager.coin), + child: SvgPicture.file( + File( + ref.watch( + coinIconProvider(manager.coin), + ), + ), width: 20, height: 20, ), @@ -144,39 +146,18 @@ class WalletSyncingOptionsView extends ConsumerWidget { const SizedBox( height: 2, ), - FutureBuilder( - future: manager.totalBalance, - builder: (builderContext, - AsyncSnapshot snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData) { - return Text( - "${Format.localizedStringAsFixed( - value: snapshot.data!, - locale: ref.watch( - localeServiceChangeNotifierProvider - .select((value) => - value.locale)), - decimalPlaces: 8, - )} ${manager.coin.ticker}", - style: STextStyles.itemSubtitle( - context), - ); - } else { - return AnimatedText( - stringsToLoopThrough: const [ - "Loading balance", - "Loading balance.", - "Loading balance..", - "Loading balance..." - ], - style: STextStyles.itemSubtitle( - context), - ); - } - }, - ), + Text( + "${manager.balance.total.localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider + .select( + (value) => value.locale, + ), + ), + )} ${manager.coin.ticker}", + style: + STextStyles.itemSubtitle(context), + ) ], ), const Spacer(), diff --git a/lib/pages/settings_views/global_settings_view/xpub_view.dart b/lib/pages/settings_views/global_settings_view/xpub_view.dart new file mode 100644 index 000000000..e23131ec8 --- /dev/null +++ b/lib/pages/settings_views/global_settings_view/xpub_view.dart @@ -0,0 +1,308 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:qr_flutter/qr_flutter.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/services/coins/manager.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/clipboard_interface.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/loading_indicator.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class XPubView extends ConsumerStatefulWidget { + const XPubView({ + Key? key, + required this.walletId, + this.clipboardInterface = const ClipboardWrapper(), + }) : super(key: key); + + final String walletId; + final ClipboardInterface clipboardInterface; + + static const String routeName = "/xpub"; + + @override + ConsumerState createState() => _XPubViewState(); +} + +class _XPubViewState extends ConsumerState { + final bool isDesktop = Util.isDesktop; + + late ClipboardInterface _clipboardInterface; + late final Manager manager; + + String? xpub; + + @override + void initState() { + _clipboardInterface = widget.clipboardInterface; + manager = + ref.read(walletsChangeNotifierProvider).getManager(widget.walletId); + + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + Future _copy() async { + await _clipboardInterface.setData(ClipboardData(text: xpub!)); + if (mounted) { + unawaited(showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + )); + } + } + + @override + Widget build(BuildContext context) { + return ConditionalParent( + condition: !isDesktop, + builder: (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () async { + Navigator.of(context).pop(); + }, + ), + title: Text( + "Wallet xPub", + style: STextStyles.navBarTitle(context), + ), + actions: [ + Padding( + padding: const EdgeInsets.all(10), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + color: + Theme.of(context).extension()!.background, + shadows: const [], + icon: SvgPicture.asset( + Assets.svg.copy, + width: 24, + height: 24, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, + ), + onPressed: () { + if (xpub != null) { + _copy(); + } + }, + ), + ), + ), + ], + ), + body: Padding( + padding: const EdgeInsets.only( + top: 12, + left: 16, + right: 16, + ), + child: child, + ), + ), + ), + child: ConditionalParent( + condition: isDesktop, + builder: (child) => DesktopDialog( + maxWidth: 600, + maxHeight: double.infinity, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "${manager.walletName} xPub", + style: STextStyles.desktopH2(context), + ), + ), + DesktopDialogCloseButton( + onPressedOverride: Navigator.of( + context, + rootNavigator: true, + ).pop, + ), + ], + ), + AnimatedSize( + duration: const Duration( + milliseconds: 150, + ), + child: Padding( + padding: const EdgeInsets.fromLTRB(32, 0, 32, 32), + child: child, + ), + ), + ], + ), + ), + child: Column( + children: [ + if (isDesktop) const SizedBox(height: 44), + ConditionalParent( + condition: !isDesktop, + builder: (child) => Expanded( + child: child, + ), + child: FutureBuilder( + future: manager.xpub, + builder: (context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData) { + xpub = snapshot.data!; + } + + const height = 600.0; + Widget child; + if (xpub == null) { + child = const SizedBox( + key: Key("loadingXPUB"), + height: height, + child: Center( + child: LoadingIndicator( + width: 100, + ), + ), + ); + } else { + child = _XPub( + xpub: xpub!, + height: height, + ); + } + + return AnimatedSwitcher( + duration: const Duration( + milliseconds: 200, + ), + child: child, + ); + }, + ), + ), + ], + ), + ), + ); + } +} + +class _XPub extends StatelessWidget { + const _XPub({ + Key? key, + required this.xpub, + required this.height, + this.clipboardInterface = const ClipboardWrapper(), + }) : super(key: key); + + final String xpub; + final double height; + final ClipboardInterface clipboardInterface; + + @override + Widget build(BuildContext context) { + final bool isDesktop = Util.isDesktop; + + return SizedBox( + height: isDesktop ? height : double.infinity, + child: Column( + children: [ + ConditionalParent( + condition: !isDesktop, + builder: (child) => RoundedWhiteContainer( + child: child, + ), + child: QrImage( + data: xpub, + size: isDesktop ? 280 : MediaQuery.of(context).size.width / 1.5, + foregroundColor: + Theme.of(context).extension()!.accentColorDark, + ), + ), + const SizedBox(height: 25), + RoundedWhiteContainer( + padding: const EdgeInsets.all(16), + borderColor: + Theme.of(context).extension()!.backgroundAppBar, + child: SelectableText( + xpub, + style: STextStyles.largeMedium14(context), + ), + ), + const SizedBox(height: 32), + Row( + children: [ + if (isDesktop) + Expanded( + child: SecondaryButton( + buttonHeight: ButtonHeight.xl, + label: "Cancel", + onPressed: Navigator.of( + context, + rootNavigator: true, + ).pop, + ), + ), + if (isDesktop) const SizedBox(width: 16), + Expanded( + child: PrimaryButton( + buttonHeight: ButtonHeight.xl, + label: "Copy", + onPressed: () async { + await clipboardInterface.setData( + ClipboardData( + text: xpub, + ), + ); + if (context.mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + iconAsset: Assets.svg.copy, + context: context, + ), + ); + } + }, + ), + ), + ], + ), + if (!isDesktop) const Spacer(), + ], + ), + ); + } +} diff --git a/lib/pages/settings_views/sub_widgets/settings_list_button.dart b/lib/pages/settings_views/sub_widgets/settings_list_button.dart index 5081e0f76..7ea8e9946 100644 --- a/lib/pages/settings_views/sub_widgets/settings_list_button.dart +++ b/lib/pages/settings_views/sub_widgets/settings_list_button.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; class SettingsListButton extends StatelessWidget { const SettingsListButton({ @@ -70,9 +70,7 @@ class SettingsListButton extends StatelessWidget { child: Text( title, style: STextStyles.smallMed14(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark, + color: Theme.of(context).extension()!.textDark, ), ), ), diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart index a9235172f..82c2e4807 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart @@ -8,13 +8,12 @@ import 'package:qr_flutter/qr_flutter.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/address_utils.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; @@ -140,7 +139,7 @@ class WalletBackupView extends ConsumerWidget { TextButton( style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), onPressed: () { String data = AddressUtils.encodeQRSeedData(mnemonic); @@ -194,7 +193,7 @@ class WalletBackupView extends ConsumerWidget { }, style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), child: Text( "Cancel", style: STextStyles.button(context).copyWith( diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/sub_widgets/confirm_full_rescan.dart b/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/sub_widgets/confirm_full_rescan.dart index 150af6ac5..25c93e43a 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/sub_widgets/confirm_full_rescan.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/sub_widgets/confirm_full_rescan.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; @@ -96,7 +96,7 @@ class ConfirmFullRescanDialog extends StatelessWidget { leftButton: TextButton( style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), child: Text( "Cancel", style: STextStyles.itemSubtitle12(context), @@ -108,7 +108,7 @@ class ConfirmFullRescanDialog extends StatelessWidget { rightButton: TextButton( style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), child: Text( "Rescan", style: STextStyles.button(context), diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/sub_widgets/rescanning_dialog.dart b/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/sub_widgets/rescanning_dialog.dart index da39b1017..ac30cb5e7 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/sub_widgets/rescanning_dialog.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/sub_widgets/rescanning_dialog.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/animated_widgets/rotating_arrows.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; class RescanningDialog extends StatefulWidget { @@ -21,9 +22,12 @@ class _RescanningDialogState extends State late AnimationController? _spinController; late Animation _spinAnimation; + late final bool isDesktop; + // late final VoidCallback onCancel; @override void initState() { + isDesktop = Util.isDesktop; // onCancel = widget.onCancel; _spinController = AnimationController( @@ -53,33 +57,36 @@ class _RescanningDialogState extends State onWillPop: () async { return false; }, - child: StackDialog( - title: "Rescanning blockchain", - message: "This may take a while. Please do not exit this screen.", - icon: RotationTransition( - turns: _spinAnimation, - child: SvgPicture.asset( - Assets.svg.arrowRotate3, + child: ConditionalParent( + condition: isDesktop, + builder: (child) => DesktopDialog( + maxHeight: 200, + maxWidth: 500, + child: child, + ), + child: const StackDialog( + title: "Rescanning blockchain", + message: "This may take a while. Please do not exit this screen.", + icon: RotatingArrows( width: 24, height: 24, - color: Theme.of(context).extension()!.accentColorDark, ), + // rightButton: TextButton( + // style: Theme.of(context).textButtonTheme.style?.copyWith( + // backgroundColor: MaterialStateProperty.all( + // CFColors.buttonGray, + // ), + // ), + // child: Text( + // "Cancel", + // style: STextStyles.itemSubtitle12(context), + // ), + // onPressed: () { + // Navigator.of(context).pop(); + // onCancel.call(); + // }, + // ), ), - // rightButton: TextButton( - // style: Theme.of(context).textButtonTheme.style?.copyWith( - // backgroundColor: MaterialStateProperty.all( - // CFColors.buttonGray, - // ), - // ), - // child: Text( - // "Cancel", - // style: STextStyles.itemSubtitle12(context), - // ), - // onPressed: () { - // Navigator.of(context).pop(); - // onCancel.call(); - // }, - // ), ), ); } diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart index 7e29010b1..d199ba7c1 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart @@ -19,11 +19,11 @@ import 'package:stackwallet/services/event_bus/events/global/node_connection_sta import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/animated_text.dart'; import 'package:stackwallet/widgets/background.dart'; @@ -76,6 +76,8 @@ class _WalletNetworkSettingsViewState StreamSubscription? _blocksRemainingSubscription; // late StreamSubscription _nodeStatusSubscription; + late final bool isDesktop; + late double _percent; late int _blocksRemaining; bool _advancedIsExpanded = false; @@ -114,7 +116,7 @@ class _WalletNetworkSettingsViewState if (mounted) { // pop rescanning dialog - Navigator.pop(context); + Navigator.of(context, rootNavigator: isDesktop).pop(); // show success await showDialog( @@ -126,13 +128,13 @@ class _WalletNetworkSettingsViewState rightButton: TextButton( style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), child: Text( "Ok", style: STextStyles.itemSubtitle12(context), ), onPressed: () { - Navigator.of(context).pop(); + Navigator.of(context, rootNavigator: isDesktop).pop(); }, ), ), @@ -143,7 +145,7 @@ class _WalletNetworkSettingsViewState if (mounted) { // pop rescanning dialog - Navigator.pop(context); + Navigator.of(context, rootNavigator: isDesktop).pop(); // show error await showDialog( @@ -156,13 +158,13 @@ class _WalletNetworkSettingsViewState rightButton: TextButton( style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), child: Text( "Ok", style: STextStyles.itemSubtitle12(context), ), onPressed: () { - Navigator.of(context).pop(); + Navigator.of(context, rootNavigator: isDesktop).pop(); }, ), ), @@ -183,6 +185,7 @@ class _WalletNetworkSettingsViewState @override void initState() { + isDesktop = Util.isDesktop; _currentSyncStatus = widget.initialSyncStatus; // _currentNodeStatus = widget.initialNodeStatus; if (_currentSyncStatus == WalletSyncStatus.synced) { @@ -270,7 +273,6 @@ class _WalletNetworkSettingsViewState @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; - final bool isDesktop = Util.isDesktop; final progressLength = isDesktop ? 430.0 @@ -507,7 +509,7 @@ class _WalletNetworkSettingsViewState children: [ Text( "Synchronized", - style: STextStyles.w600_10(context), + style: STextStyles.w600_12(context), ), Text( "100%", @@ -581,7 +583,7 @@ class _WalletNetworkSettingsViewState mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ AnimatedText( - style: STextStyles.w600_10(context), + style: STextStyles.w600_12(context), stringsToLoopThrough: const [ "Synchronizing", "Synchronizing.", @@ -679,7 +681,7 @@ class _WalletNetworkSettingsViewState children: [ Text( "Unable to synchronize", - style: STextStyles.w600_10(context).copyWith( + style: STextStyles.w600_12(context).copyWith( color: Theme.of(context) .extension()! .accentColorRed, @@ -747,7 +749,7 @@ class _WalletNetworkSettingsViewState ? STextStyles.desktopTextExtraExtraSmall(context) : STextStyles.smallMed12(context), ), - BlueTextButton( + CustomTextButton( text: "Add new node", onTap: () { Navigator.of(context).pushNamed( @@ -878,7 +880,7 @@ class _WalletNetworkSettingsViewState top: 16, bottom: 6, ), - child: BlueTextButton( + child: CustomTextButton( text: "Rescan", onTap: () async { await Navigator.of(context).push( diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart index 92b712111..26c94e4be 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_view.dart @@ -1,15 +1,16 @@ import 'dart:async'; -import 'dart:convert'; import 'package:event_bus/event_bus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/epicbox_config_model.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/address_book_views/address_book_view.dart'; import 'package:stackwallet/pages/home_view/home_view.dart'; import 'package:stackwallet/pages/pinpad_views/lock_screen_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/debug_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_preferences_view.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/xpub_view.dart'; import 'package:stackwallet/pages/settings_views/sub_widgets/settings_list_button.dart'; import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart'; import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart'; @@ -21,10 +22,10 @@ import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart'; import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; @@ -32,7 +33,7 @@ import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:tuple/tuple.dart'; /// [eventBus] should only be set during testing -class WalletSettingsView extends StatefulWidget { +class WalletSettingsView extends ConsumerStatefulWidget { const WalletSettingsView({ Key? key, required this.walletId, @@ -51,12 +52,14 @@ class WalletSettingsView extends StatefulWidget { final EventBus? eventBus; @override - State createState() => _WalletSettingsViewState(); + ConsumerState createState() => _WalletSettingsViewState(); } -class _WalletSettingsViewState extends State { +class _WalletSettingsViewState extends ConsumerState { late final String walletId; late final Coin coin; + late String xpub; + late final bool xPubEnabled; late final EventBus eventBus; @@ -70,6 +73,9 @@ class _WalletSettingsViewState extends State { void initState() { walletId = widget.walletId; coin = widget.coin; + xPubEnabled = + ref.read(walletsChangeNotifierProvider).getManager(walletId).hasXPub; + xpub = ""; _currentSyncStatus = widget.initialSyncStatus; // _currentNodeStatus = widget.initialNodeStatus; @@ -263,13 +269,32 @@ class _WalletSettingsViewState extends State { height: 8, ), SettingsListButton( - iconAssetName: Assets.svg.arrowRotate3, + iconAssetName: Assets.svg.arrowRotate, title: "Syncing preferences", onPressed: () { Navigator.of(context).pushNamed( SyncingPreferencesView.routeName); }, ), + if (xPubEnabled) + const SizedBox( + height: 8, + ), + if (xPubEnabled) + Consumer( + builder: (_, ref, __) { + return SettingsListButton( + iconAssetName: Assets.svg.eye, + title: "Wallet xPub", + onPressed: () { + Navigator.of(context).pushNamed( + XPubView.routeName, + arguments: widget.walletId, + ); + }, + ); + }, + ), const SizedBox( height: 8, ), @@ -306,7 +331,7 @@ class _WalletSettingsViewState extends State { }, style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), child: Text( "Log out", style: STextStyles.button(context).copyWith( @@ -356,10 +381,9 @@ class _EpiBoxInfoFormState extends ConsumerState { .getManager(widget.walletId) .wallet as EpicCashWallet; - wallet.getEpicBoxConfig().then((value) { - final config = jsonDecode(value); - hostController.text = config["domain"] as String; - portController.text = (config["port"] as int).toString(); + wallet.getEpicBoxConfig().then((EpicBoxConfigModel epicBoxConfig) { + hostController.text = epicBoxConfig.host; + portController.text = "${epicBoxConfig.port ?? 443}"; }); super.initState(); } @@ -391,7 +415,8 @@ class _EpiBoxInfoFormState extends ConsumerState { enableSuggestions: Util.isDesktop ? false : true, controller: portController, decoration: const InputDecoration(hintText: "Port"), - keyboardType: const TextInputType.numberWithOptions(), + keyboardType: + Util.isDesktop ? null : const TextInputType.numberWithOptions(), ), const SizedBox( height: 8, diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_recovery_phrase_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_recovery_phrase_view.dart index 5543bf1c0..731164082 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_recovery_phrase_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_recovery_phrase_view.dart @@ -7,12 +7,11 @@ import 'package:stackwallet/pages/add_wallet_views/new_wallet_recovery_phrase_vi import 'package:stackwallet/pages/home_view/home_view.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/coins/manager.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; @@ -156,7 +155,7 @@ class _DeleteWalletRecoveryPhraseViewState TextButton( style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), onPressed: () { showDialog( barrierDismissible: true, @@ -166,7 +165,7 @@ class _DeleteWalletRecoveryPhraseViewState leftButton: TextButton( style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), onPressed: () { Navigator.pop(context); }, @@ -181,7 +180,7 @@ class _DeleteWalletRecoveryPhraseViewState rightButton: TextButton( style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), onPressed: () async { final walletId = _manager.walletId; final walletsInstance = diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_warning_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_warning_view.dart index f740eee12..cf21908d6 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_warning_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_warning_view.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_wallet_recovery_phrase_view.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; @@ -57,7 +57,7 @@ class DeleteWalletWarningView extends ConsumerWidget { .extension()! .warningBackground, child: Text( - "You are going to permanently delete you wallet.\n\nIf you delete your wallet, the only way you can have access to your funds is by using your backup key.\n\nStack Wallet does not keep nor is able to restore your backup key or your wallet.\n\nPLEASE SAVE YOUR BACKUP KEY.", + "You are going to permanently delete your wallet.\n\nIf you delete your wallet, the only way you can have access to your funds is by using your backup key.\n\nStack Wallet does not keep nor is able to restore your backup key or your wallet.\n\nPLEASE SAVE YOUR BACKUP KEY.", style: STextStyles.baseXS(context).copyWith( color: Theme.of(context) .extension()! @@ -69,7 +69,7 @@ class DeleteWalletWarningView extends ConsumerWidget { TextButton( style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), onPressed: () { Navigator.pop(context); }, @@ -87,7 +87,7 @@ class DeleteWalletWarningView extends ConsumerWidget { TextButton( style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), onPressed: () async { final manager = ref .read(walletsChangeNotifierProvider) diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/rename_wallet_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/rename_wallet_view.dart index f76422750..ed1dd1219 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/rename_wallet_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/rename_wallet_view.dart @@ -2,10 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; @@ -114,7 +113,7 @@ class _RenameWalletViewState extends ConsumerState { TextButton( style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), onPressed: () async { final newName = _controller.text; final success = await ref diff --git a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart index a9c0f92af..7e89519e0 100644 --- a/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart +++ b/lib/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart @@ -5,9 +5,9 @@ import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_set import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/rename_wallet_view.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/route_generator.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -105,7 +105,7 @@ class WalletSettingsWalletSettingsView extends ConsumerWidget { leftButton: TextButton( style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), onPressed: () { Navigator.pop(context); }, @@ -120,7 +120,7 @@ class WalletSettingsWalletSettingsView extends ConsumerWidget { rightButton: TextButton( style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), onPressed: () { Navigator.pop(context); Navigator.push( diff --git a/lib/pages/stack_privacy_calls.dart b/lib/pages/stack_privacy_calls.dart index 1a5e23e98..22d48311e 100644 --- a/lib/pages/stack_privacy_calls.dart +++ b/lib/pages/stack_privacy_calls.dart @@ -1,18 +1,20 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/pages/pinpad_views/create_pin_view.dart'; import 'package:stackwallet/pages_desktop_specific/password/create_password_view.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/providers/global/price_provider.dart'; import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; @@ -148,15 +150,16 @@ class _StackPrivacyCalls extends ConsumerState { ), children: infoToggle ? [ + if (Constants.enableExchange) + const TextSpan( + text: + "Exchange data preloaded for a seamless experience.\n\n"), const TextSpan( text: - "Exchange data preloaded for a seamless experience."), - const TextSpan( - text: - "\n\nCoinGecko enabled: (24 hour price change shown in-app, total wallet value shown in USD or other currency)."), + "CoinGecko enabled: (24 hour price change shown in-app, total wallet value shown in USD or other currency).\n\n"), TextSpan( text: - "\n\nRecommended for most crypto users.", + "Recommended for most crypto users.", style: isDesktop ? STextStyles .desktopTextExtraExtraSmall600( @@ -170,15 +173,16 @@ class _StackPrivacyCalls extends ConsumerState { ), ] : [ + if (Constants.enableExchange) + const TextSpan( + text: + "Exchange data not preloaded (slower experience).\n\n"), const TextSpan( text: - "Exchange data not preloaded (slower experience)."), - const TextSpan( - text: - "\n\nCoinGecko disabled (price changes not shown, no wallet value shown in other currencies)."), + "CoinGecko disabled (price changes not shown, no wallet value shown in other currencies).\n\n"), TextSpan( text: - "\n\nRecommended for the privacy conscious.", + "Recommended for the privacy conscious.", style: isDesktop ? STextStyles .desktopTextExtraExtraSmall600( @@ -230,8 +234,12 @@ class _StackPrivacyCalls extends ConsumerState { value: isEasy) .then((_) { if (isEasy) { - unawaited(ExchangeDataLoadingService() - .loadAll(ref)); + unawaited( + ExchangeDataLoadingService.instance + .loadAll(), + ); + // unawaited( + // BuyDataLoadingService().loadAll(ref)); ref .read(priceAnd24hChangeNotifierProvider) .start(true); @@ -270,7 +278,7 @@ class _StackPrivacyCalls extends ConsumerState { } } -class PrivacyToggle extends StatefulWidget { +class PrivacyToggle extends ConsumerStatefulWidget { const PrivacyToggle({ Key? key, required this.externalCallsEnabled, @@ -281,10 +289,10 @@ class PrivacyToggle extends StatefulWidget { final void Function(bool)? onChanged; @override - State createState() => _PrivacyToggleState(); + ConsumerState createState() => _PrivacyToggleState(); } -class _PrivacyToggleState extends State { +class _PrivacyToggleState extends ConsumerState { late bool externalCallsEnabled; late final bool isDesktop; @@ -299,6 +307,11 @@ class _PrivacyToggleState extends State { @override Widget build(BuildContext context) { + final easyFile = + ref.watch(themeProvider.select((value) => value.assets.personaEasy)); + final incognitoFile = ref + .watch(themeProvider.select((value) => value.assets.personaIncognito)); + return Row( children: [ Expanded( @@ -335,19 +348,19 @@ class _PrivacyToggleState extends State { Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - if (isDesktop) - const SizedBox( - height: 10, - ), - SvgPicture.asset( - Assets.svg.personaEasy, - width: isDesktop ? 120 : 140, - height: isDesktop ? 120 : 140, - ), - if (isDesktop) - const SizedBox( - height: 12, - ), + (easyFile.endsWith(".png")) + ? Image.file( + File( + easyFile, + ), + ) + : SvgPicture.file( + File( + easyFile, + ), + width: 140, + height: 140, + ), Center( child: Text( "Easy Crypto", @@ -444,11 +457,24 @@ class _PrivacyToggleState extends State { const SizedBox( height: 10, ), - SvgPicture.asset( - Assets.svg.personaIncognito, - width: isDesktop ? 120 : 140, - height: isDesktop ? 120 : 140, - ), + (incognitoFile.endsWith(".png")) + ? Image.file( + File( + incognitoFile, + ), + ) + : SvgPicture.file( + File( + incognitoFile, + ), + width: 140, + height: 140, + ), + // SvgPicture.asset( + // Assets.svg.personaIncognito(context), + // width: 140, + // height: 140, + // ), if (isDesktop) const SizedBox( height: 12, diff --git a/lib/pages/token_view/my_tokens_view.dart b/lib/pages/token_view/my_tokens_view.dart new file mode 100644 index 000000000..f655bd27d --- /dev/null +++ b/lib/pages/token_view/my_tokens_view.dart @@ -0,0 +1,237 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart'; +import 'package:stackwallet/pages/token_view/sub_widgets/my_tokens_list.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; + +class MyTokensView extends ConsumerStatefulWidget { + const MyTokensView({ + Key? key, + required this.walletId, + }) : super(key: key); + + static const String routeName = "/myTokens"; + final String walletId; + + @override + ConsumerState createState() => _MyTokensViewState(); +} + +class _MyTokensViewState extends ConsumerState { + final bool isDesktop = Util.isDesktop; + + late final String walletAddress; + late final TextEditingController _searchController; + final searchFieldFocusNode = FocusNode(); + String _searchString = ""; + + @override + void initState() { + _searchController = TextEditingController(); + super.initState(); + } + + @override + void dispose() { + _searchController.dispose(); + searchFieldFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + + return ConditionalParent( + condition: !isDesktop, + builder: (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension()!.background, + leading: AppBarBackButton( + onPressed: () async { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + title: Text( + "${ref.watch( + walletsChangeNotifierProvider.select( + (value) => value.getManager(widget.walletId).walletName), + )} Tokens", + style: STextStyles.navBarTitle(context), + ), + actions: [ + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 20, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + key: const Key("addTokenAppBarIconButtonKey"), + size: 36, + shadows: const [], + color: + Theme.of(context).extension()!.background, + icon: SvgPicture.asset( + Assets.svg.circlePlusFilled, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, + width: 20, + height: 20, + ), + onPressed: () async { + final result = await Navigator.of(context).pushNamed( + EditWalletTokensView.routeName, + arguments: widget.walletId, + ); + + if (mounted && result == 42) { + setState(() {}); + } + }, + ), + ), + ), + ], + ), + body: Padding( + padding: const EdgeInsets.only( + left: 12, + top: 12, + right: 12, + ), + child: child, + ), + ), + ), + child: Column( + children: [ + Padding( + padding: EdgeInsets.all(isDesktop ? 0 : 4), + child: Row( + children: [ + ConditionalParent( + condition: isDesktop, + builder: (child) => Expanded( + child: child, + ), + child: ConditionalParent( + condition: !isDesktop, + builder: (child) => Expanded( + child: child, + ), + child: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: !isDesktop, + enableSuggestions: !isDesktop, + controller: _searchController, + focusNode: searchFieldFocusNode, + onChanged: (value) { + setState(() { + _searchString = value; + }); + }, + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + decoration: standardInputDecoration( + "Search...", + searchFieldFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + prefixIcon: Padding( + padding: EdgeInsets.symmetric( + horizontal: isDesktop ? 12 : 10, + vertical: isDesktop ? 18 : 16, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: isDesktop ? 20 : 16, + height: isDesktop ? 20 : 16, + ), + ), + suffixIcon: _searchController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchController.text = ""; + _searchString = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), + ), + ), + ], + ), + ), + const SizedBox( + height: 8, + ), + Expanded( + child: MyTokensList( + walletId: widget.walletId, + searchTerm: _searchString, + tokenContracts: ref + .watch(walletsChangeNotifierProvider.select((value) => value + .getManager(widget.walletId) + .wallet as EthereumWallet)) + .getWalletTokenContractAddresses(), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/token_view/sub_widgets/my_token_select_item.dart b/lib/pages/token_view/sub_widgets/my_token_select_item.dart new file mode 100644 index 000000000..1cd3272ac --- /dev/null +++ b/lib/pages/token_view/sub_widgets/my_token_select_item.dart @@ -0,0 +1,229 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; +import 'package:stackwallet/pages/token_view/token_view.dart'; +import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_token_view.dart'; +import 'package:stackwallet/providers/global/secure_store_provider.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart'; +import 'package:stackwallet/services/ethereum/cached_eth_token_balance.dart'; +import 'package:stackwallet/services/ethereum/ethereum_token_service.dart'; +import 'package:stackwallet/services/transaction_notification_tracker.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/show_loading.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/dialogs/basic_dialog.dart'; +import 'package:stackwallet/widgets/icon_widgets/eth_token_icon.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class MyTokenSelectItem extends ConsumerStatefulWidget { + const MyTokenSelectItem({ + Key? key, + required this.walletId, + required this.token, + }) : super(key: key); + + final String walletId; + final EthContract token; + + @override + ConsumerState createState() => _MyTokenSelectItemState(); +} + +class _MyTokenSelectItemState extends ConsumerState { + final bool isDesktop = Util.isDesktop; + + late final CachedEthTokenBalance cachedBalance; + + Future _loadTokenWallet( + BuildContext context, + WidgetRef ref, + ) async { + try { + await ref.read(tokenServiceProvider)!.initialize(); + return true; + } catch (_) { + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => BasicDialog( + title: "Failed to load token data", + desktopHeight: double.infinity, + desktopWidth: 450, + rightButton: PrimaryButton( + label: "OK", + onPressed: () { + Navigator.of(context).pop(); + if (!isDesktop) { + Navigator.of(context).pop(); + } + }, + ), + ), + ); + return false; + } + } + + void _onPressed() async { + ref.read(tokenServiceStateProvider.state).state = EthTokenWallet( + token: widget.token, + secureStore: ref.read(secureStoreProvider), + ethWallet: ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId) + .wallet as EthereumWallet, + tracker: TransactionNotificationTracker( + walletId: widget.walletId, + ), + ); + + final success = await showLoading( + whileFuture: _loadTokenWallet(context, ref), + context: context, + isDesktop: isDesktop, + message: "Loading ${widget.token.name}", + ); + + if (!success) { + return; + } + + if (mounted) { + await Navigator.of(context).pushNamed( + isDesktop ? DesktopTokenView.routeName : TokenView.routeName, + arguments: widget.walletId, + ); + } + } + + @override + void initState() { + cachedBalance = CachedEthTokenBalance(widget.walletId, widget.token); + + WidgetsBinding.instance.addPostFrameCallback((_) async { + final address = await ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId) + .currentReceivingAddress; + await cachedBalance.fetchAndUpdateCachedBalance(address); + if (mounted) { + setState(() {}); + } + }); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: MaterialButton( + key: Key("walletListItemButtonKey_${widget.token.symbol}"), + padding: isDesktop + ? const EdgeInsets.symmetric(horizontal: 28, vertical: 24) + : const EdgeInsets.symmetric(horizontal: 12, vertical: 13), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(Constants.size.circularBorderRadius), + ), + onPressed: _onPressed, + child: Row( + children: [ + EthTokenIcon( + contractAddress: widget.token.address, + size: isDesktop ? 32 : 28, + ), + SizedBox( + width: isDesktop ? 12 : 10, + ), + Expanded( + child: Consumer( + builder: (_, ref, __) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + children: [ + Text( + widget.token.name, + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ) + : STextStyles.titleBold12(context), + ), + const Spacer(), + Text( + "${cachedBalance.getCachedBalance().total.localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), + ), + )} " + "${widget.token.symbol}", + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ) + : STextStyles.itemSubtitle(context), + ), + ], + ), + const SizedBox( + height: 2, + ), + Row( + children: [ + Text( + widget.token.symbol, + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context) + : STextStyles.itemSubtitle(context), + ), + const Spacer(), + Text( + "${ref.watch( + priceAnd24hChangeNotifierProvider.select( + (value) => value + .getTokenPrice(widget.token.address) + .item1 + .toStringAsFixed(2), + ), + )} " + "${ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.currency, + ), + )}", + style: isDesktop + ? STextStyles.desktopTextExtraExtraSmall( + context) + : STextStyles.itemSubtitle(context), + ), + ], + ), + ], + ); + }, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/token_view/sub_widgets/my_tokens_list.dart b/lib/pages/token_view/sub_widgets/my_tokens_list.dart new file mode 100644 index 000000000..659ed5221 --- /dev/null +++ b/lib/pages/token_view/sub_widgets/my_tokens_list.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; +import 'package:stackwallet/pages/token_view/sub_widgets/my_token_select_item.dart'; +import 'package:stackwallet/utilities/util.dart'; + +class MyTokensList extends StatelessWidget { + const MyTokensList({ + Key? key, + required this.walletId, + required this.searchTerm, + required this.tokenContracts, + }) : super(key: key); + + final String walletId; + final String searchTerm; + final List tokenContracts; + + List _filter(String searchTerm) { + if (tokenContracts.isEmpty) { + return []; + } + + if (searchTerm.isNotEmpty) { + final term = searchTerm.toLowerCase(); + return MainDB.instance + .getEthContracts() + .filter() + .anyOf( + tokenContracts, (q, e) => q.addressEqualTo(e)) + .and() + .group( + (q) => q + .nameContains(term, caseSensitive: false) + .or() + .symbolContains(term, caseSensitive: false) + .or() + .addressContains(term, caseSensitive: false), + ) + .findAllSync(); + // return tokens.toList(); + } + //implement search/filter + return MainDB.instance + .getEthContracts() + .filter() + .anyOf( + tokenContracts, (q, e) => q.addressEqualTo(e)) + .findAllSync(); + } + + @override + Widget build(BuildContext context) { + final bool isDesktop = Util.isDesktop; + + return Consumer( + builder: (_, ref, __) { + final tokens = _filter(searchTerm); + return ListView.builder( + itemCount: tokens.length, + itemBuilder: (ctx, index) { + final token = tokens[index]; + return Padding( + key: Key(token.address), + padding: isDesktop + ? const EdgeInsets.symmetric(vertical: 5) + : const EdgeInsets.all(4), + child: MyTokenSelectItem( + walletId: walletId, + token: token, + ), + ); + }, + ); + }, + ); + } +} diff --git a/lib/pages/token_view/sub_widgets/no_tokens_found.dart b/lib/pages/token_view/sub_widgets/no_tokens_found.dart new file mode 100644 index 000000000..4fbfff50e --- /dev/null +++ b/lib/pages/token_view/sub_widgets/no_tokens_found.dart @@ -0,0 +1,25 @@ +import 'package:flutter/cupertino.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class NoTokensFound extends StatelessWidget { + const NoTokensFound({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + RoundedWhiteContainer( + child: FittedBox( + fit: BoxFit.scaleDown, + child: Text( + "You do not have any tokens", + style: STextStyles.itemSubtitle(context), + ), + ), + ), + ], + ); + } +} diff --git a/lib/pages/token_view/sub_widgets/token_summary.dart b/lib/pages/token_view/sub_widgets/token_summary.dart new file mode 100644 index 000000000..51679c4d6 --- /dev/null +++ b/lib/pages/token_view/sub_widgets/token_summary.dart @@ -0,0 +1,372 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; +import 'package:stackwallet/pages/buy_view/buy_in_wallet_view.dart'; +import 'package:stackwallet/pages/exchange_view/wallet_initiated_exchange_view.dart'; +import 'package:stackwallet/pages/receive_view/receive_view.dart'; +import 'package:stackwallet/pages/send_view/token_send_view.dart'; +import 'package:stackwallet/pages/token_view/token_view.dart'; +import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_refresh_button.dart'; +import 'package:stackwallet/providers/global/locale_provider.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/providers/global/price_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; +import 'package:tuple/tuple.dart'; + +class TokenSummary extends ConsumerWidget { + const TokenSummary({ + Key? key, + required this.walletId, + required this.initialSyncStatus, + }) : super(key: key); + + final String walletId; + final WalletSyncStatus initialSyncStatus; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final token = + ref.watch(tokenServiceProvider.select((value) => value!.tokenContract)); + final balance = + ref.watch(tokenServiceProvider.select((value) => value!.balance)); + + return Stack( + children: [ + RoundedContainer( + color: Theme.of(context).extension()!.tokenSummaryBG, + padding: const EdgeInsets.all(24), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset( + Assets.svg.walletDesktop, + color: Theme.of(context) + .extension()! + .tokenSummaryTextSecondary, + width: 12, + height: 12, + ), + const SizedBox( + width: 6, + ), + Text( + ref.watch( + walletsChangeNotifierProvider.select( + (value) => value.getManager(walletId).walletName, + ), + ), + style: STextStyles.w500_12(context).copyWith( + color: Theme.of(context) + .extension()! + .tokenSummaryTextSecondary, + ), + ), + ], + ), + const SizedBox( + height: 6, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "${balance.total.localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), + ), + )}" + " ${token.symbol}", + style: STextStyles.pageTitleH1(context).copyWith( + color: Theme.of(context) + .extension()! + .tokenSummaryTextPrimary, + ), + ), + const SizedBox( + width: 10, + ), + CoinTickerTag( + walletId: walletId, + ), + ], + ), + const SizedBox( + height: 6, + ), + Text( + "${(balance.total.decimal * ref.watch( + priceAnd24hChangeNotifierProvider.select( + (value) => value.getTokenPrice(token.address).item1, + ), + )).toAmount( + fractionDigits: 2, + ).localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), + ), + )} ${ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.currency, + ), + )}", + style: STextStyles.subtitle500(context).copyWith( + color: Theme.of(context) + .extension()! + .tokenSummaryTextPrimary, + ), + ), + const SizedBox( + height: 20, + ), + TokenWalletOptions( + walletId: walletId, + tokenContract: token, + ), + ], + ), + ), + Positioned( + top: 10, + right: 10, + child: WalletRefreshButton( + walletId: walletId, + initialSyncStatus: initialSyncStatus, + tokenContractAddress: ref.watch( + tokenServiceProvider.select( + (value) => value!.tokenContract.address, + ), + ), + overrideIconColor: + Theme.of(context).extension()!.topNavIconPrimary, + ), + ), + ], + ); + } +} + +class TokenWalletOptions extends ConsumerWidget { + const TokenWalletOptions({ + Key? key, + required this.walletId, + required this.tokenContract, + }) : super(key: key); + + final String walletId; + final EthContract tokenContract; + + void _onExchangePressed(BuildContext context) async { + unawaited( + Navigator.of(context).pushNamed( + WalletInitiatedExchangeView.routeName, + arguments: Tuple3( + walletId, + Coin.ethereum, + tokenContract, + ), + ), + ); + } + + void _onBuyPressed(BuildContext context) { + unawaited( + Navigator.of(context).pushNamed( + BuyInWalletView.routeName, + arguments: Tuple2( + Coin.ethereum, + tokenContract, + ), + ), + ); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TokenOptionsButton( + onPressed: () { + Navigator.of(context).pushNamed( + ReceiveView.routeName, + arguments: Tuple2( + walletId, + tokenContract, + ), + ); + }, + subLabel: "Receive", + iconAssetPathSVG: Assets.svg.arrowDownLeft, + ), + const SizedBox( + width: 16, + ), + TokenOptionsButton( + onPressed: () { + Navigator.of(context).pushNamed( + TokenSendView.routeName, + arguments: Tuple3( + walletId, + Coin.ethereum, + tokenContract, + ), + ); + }, + subLabel: "Send", + iconAssetPathSVG: Assets.svg.arrowUpRight, + ), + const SizedBox( + width: 16, + ), + TokenOptionsButton( + onPressed: () => _onExchangePressed(context), + subLabel: "Swap", + iconAssetPathSVG: ref.watch( + themeProvider.select( + (value) => value.assets.exchange, + ), + ), + ), + const SizedBox( + width: 16, + ), + TokenOptionsButton( + onPressed: () => _onBuyPressed(context), + subLabel: "Buy", + iconAssetPathSVG: Assets.svg.creditCard, + ), + ], + ); + } +} + +class TokenOptionsButton extends StatelessWidget { + const TokenOptionsButton({ + Key? key, + required this.onPressed, + required this.subLabel, + required this.iconAssetPathSVG, + }) : super(key: key); + + final VoidCallback onPressed; + final String subLabel; + final String iconAssetPathSVG; + + @override + Widget build(BuildContext context) { + final iconSize = subLabel == "Send" || subLabel == "Receive" ? 12.0 : 24.0; + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + RawMaterialButton( + fillColor: + Theme.of(context).extension()!.tokenSummaryButtonBG, + elevation: 0, + focusElevation: 0, + hoverElevation: 0, + highlightElevation: 0, + constraints: const BoxConstraints(), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: onPressed, + child: Padding( + padding: const EdgeInsets.all(10), + child: ConditionalParent( + condition: iconSize < 24, + builder: (child) => RoundedContainer( + padding: const EdgeInsets.all(6), + color: Theme.of(context) + .extension()! + .tokenSummaryIcon + .withOpacity(0.4), + radiusMultiplier: 10, + child: Center( + child: child, + ), + ), + child: iconAssetPathSVG.startsWith("assets/") + ? SvgPicture.asset( + iconAssetPathSVG, + color: Theme.of(context) + .extension()! + .tokenSummaryIcon, + width: iconSize, + height: iconSize, + ) + : SvgPicture.file( + File(iconAssetPathSVG), + color: Theme.of(context) + .extension()! + .tokenSummaryIcon, + width: iconSize, + height: iconSize, + ), + ), + ), + ), + const SizedBox( + height: 6, + ), + Text( + subLabel, + style: STextStyles.w500_12(context).copyWith( + color: Theme.of(context) + .extension()! + .tokenSummaryTextPrimary, + ), + ) + ], + ); + } +} + +class CoinTickerTag extends ConsumerWidget { + const CoinTickerTag({ + Key? key, + required this.walletId, + }) : super(key: key); + + final String walletId; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return RoundedContainer( + padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 4), + radiusMultiplier: 0.25, + color: Theme.of(context).extension()!.ethTagBG, + child: Text( + ref.watch( + walletsChangeNotifierProvider + .select((value) => value.getManager(walletId).coin.ticker), + ), + style: STextStyles.w600_12(context).copyWith( + color: Theme.of(context).extension()!.ethTagText, + ), + ), + ); + } +} diff --git a/lib/pages/token_view/sub_widgets/token_transaction_list_widget.dart b/lib/pages/token_view/sub_widgets/token_transaction_list_widget.dart new file mode 100644 index 000000000..03406338e --- /dev/null +++ b/lib/pages/token_view/sub_widgets/token_transaction_list_widget.dart @@ -0,0 +1,292 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/pages/exchange_view/trade_details_view.dart'; +import 'package:stackwallet/pages/token_view/token_view.dart'; +import 'package:stackwallet/pages/wallet_view/sub_widgets/no_transactions_found.dart'; +import 'package:stackwallet/providers/global/trades_service_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/route_generator.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/loading_indicator.dart'; +import 'package:stackwallet/widgets/trade_card.dart'; +import 'package:stackwallet/widgets/transaction_card.dart'; +import 'package:tuple/tuple.dart'; + +class TokenTransactionsList extends ConsumerStatefulWidget { + const TokenTransactionsList({ + Key? key, + required this.walletId, + }) : super(key: key); + + final String walletId; + + @override + ConsumerState createState() => + _TransactionsListState(); +} + +class _TransactionsListState extends ConsumerState { + // + bool _hasLoaded = false; + List _transactions2 = []; + + BorderRadius get _borderRadiusFirst { + return BorderRadius.only( + topLeft: Radius.circular( + Constants.size.circularBorderRadius, + ), + topRight: Radius.circular( + Constants.size.circularBorderRadius, + ), + ); + } + + BorderRadius get _borderRadiusLast { + return BorderRadius.only( + bottomLeft: Radius.circular( + Constants.size.circularBorderRadius, + ), + bottomRight: Radius.circular( + Constants.size.circularBorderRadius, + ), + ); + } + + Widget itemBuilder( + BuildContext context, + Transaction tx, + BorderRadius? radius, + Coin coin, + ) { + final matchingTrades = ref + .read(tradesServiceProvider) + .trades + .where((e) => e.payInTxid == tx.txid || e.payOutTxid == tx.txid); + + if (tx.type == TransactionType.outgoing && matchingTrades.isNotEmpty) { + final trade = matchingTrades.first; + return Container( + decoration: BoxDecoration( + color: Theme.of(context).extension()!.popupBG, + borderRadius: radius, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + TransactionCard( + // this may mess with combined firo transactions + key: tx.isConfirmed( + ref.watch(walletsChangeNotifierProvider.select((value) => + value.getManager(widget.walletId).currentHeight)), + coin.requiredConfirmations) + ? Key(tx.txid + tx.type.name + tx.address.value.toString()) + : UniqueKey(), // + transaction: tx, + walletId: widget.walletId, + ), + TradeCard( + // this may mess with combined firo transactions + key: Key(tx.txid + + tx.type.name + + tx.address.value.toString() + + trade.uuid), // + trade: trade, + onTap: () async { + final walletName = ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId) + .walletName; + if (Util.isDesktop) { + await showDialog( + context: context, + builder: (context) => Navigator( + initialRoute: TradeDetailsView.routeName, + onGenerateRoute: RouteGenerator.generateRoute, + onGenerateInitialRoutes: (_, __) { + return [ + FadePageRoute( + DesktopDialog( + maxHeight: null, + maxWidth: 580, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + bottom: 16, + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Trade details", + style: STextStyles.desktopH3(context), + ), + DesktopDialogCloseButton( + onPressedOverride: Navigator.of( + context, + rootNavigator: true, + ).pop, + ), + ], + ), + ), + Flexible( + child: TradeDetailsView( + tradeId: trade.tradeId, + transactionIfSentFromStack: tx, + walletName: walletName, + walletId: widget.walletId, + ), + ), + ], + ), + ), + const RouteSettings( + name: TradeDetailsView.routeName, + ), + ), + ]; + }, + ), + ); + } else { + unawaited( + Navigator.of(context).pushNamed( + TradeDetailsView.routeName, + arguments: Tuple4( + trade.tradeId, + tx, + widget.walletId, + walletName, + ), + ), + ); + } + }, + ) + ], + ), + ); + } else { + return Container( + decoration: BoxDecoration( + color: Theme.of(context).extension()!.popupBG, + borderRadius: radius, + ), + child: TransactionCard( + // this may mess with combined firo transactions + key: tx.isConfirmed( + ref.watch(walletsChangeNotifierProvider.select((value) => + value.getManager(widget.walletId).currentHeight)), + coin.requiredConfirmations) + ? Key(tx.txid + tx.type.name + tx.address.value.toString()) + : UniqueKey(), + transaction: tx, + walletId: widget.walletId, + ), + ); + } + } + + @override + Widget build(BuildContext context) { + final manager = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(widget.walletId))); + + return FutureBuilder( + future: ref + .watch(tokenServiceProvider.select((value) => value!.transactions)), + builder: (fbContext, AsyncSnapshot> snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData) { + _transactions2 = snapshot.data!; + _hasLoaded = true; + } + if (!_hasLoaded) { + return Column( + children: const [ + Spacer(), + Center( + child: LoadingIndicator( + height: 50, + width: 50, + ), + ), + Spacer( + flex: 4, + ), + ], + ); + } + if (_transactions2.isEmpty) { + return const NoTransActionsFound(); + } else { + _transactions2.sort((a, b) => b.timestamp - a.timestamp); + return RefreshIndicator( + onRefresh: () async { + if (!ref.read(tokenServiceProvider)!.isRefreshing) { + unawaited(ref.read(tokenServiceProvider)!.refresh()); + } + }, + child: Util.isDesktop + ? ListView.separated( + itemBuilder: (context, index) { + BorderRadius? radius; + if (_transactions2.length == 1) { + radius = BorderRadius.circular( + Constants.size.circularBorderRadius, + ); + } else if (index == _transactions2.length - 1) { + radius = _borderRadiusLast; + } else if (index == 0) { + radius = _borderRadiusFirst; + } + final tx = _transactions2[index]; + return itemBuilder(context, tx, radius, manager.coin); + }, + separatorBuilder: (context, index) { + return Container( + width: double.infinity, + height: 2, + color: Theme.of(context) + .extension()! + .background, + ); + }, + itemCount: _transactions2.length, + ) + : ListView.builder( + itemCount: _transactions2.length, + itemBuilder: (context, index) { + BorderRadius? radius; + if (_transactions2.length == 1) { + radius = BorderRadius.circular( + Constants.size.circularBorderRadius, + ); + } else if (index == _transactions2.length - 1) { + radius = _borderRadiusLast; + } else if (index == 0) { + radius = _borderRadiusFirst; + } + final tx = _transactions2[index]; + return itemBuilder(context, tx, radius, manager.coin); + }, + ), + ); + } + }, + ); + } +} diff --git a/lib/pages/token_view/token_contract_details_view.dart b/lib/pages/token_view/token_contract_details_view.dart new file mode 100644 index 000000000..663698197 --- /dev/null +++ b/lib/pages/token_view/token_contract_details_view.dart @@ -0,0 +1,193 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/custom_buttons/simple_copy_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class TokenContractDetailsView extends ConsumerStatefulWidget { + const TokenContractDetailsView({ + Key? key, + required this.contractAddress, + required this.walletId, + }) : super(key: key); + + static const String routeName = "/tokenContractDetailsView"; + + final String contractAddress; + final String walletId; + + @override + ConsumerState createState() => + _TokenContractDetailsViewState(); +} + +class _TokenContractDetailsViewState + extends ConsumerState { + final isDesktop = Util.isDesktop; + + late EthContract contract; + + @override + void initState() { + contract = MainDB.instance.isar.ethContracts + .where() + .addressEqualTo(widget.contractAddress) + .findFirstSync()!; + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return ConditionalParent( + condition: !isDesktop, + builder: (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + backgroundColor: + Theme.of(context).extension()!.backgroundAppBar, + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + titleSpacing: 0, + title: Text( + "Contract details", + style: STextStyles.navBarTitle(context), + ), + ), + body: SafeArea( + child: LayoutBuilder( + builder: (builderContext, constraints) { + return SingleChildScrollView( + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: constraints.maxHeight, + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: child, + ), + ), + ); + }, + ), + ), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + _Item( + title: "Contract address", + data: contract.address, + button: SimpleCopyButton( + data: contract.address, + ), + ), + const SizedBox( + height: 12, + ), + _Item( + title: "Name", + data: contract.name, + button: SimpleCopyButton( + data: contract.name, + ), + ), + const SizedBox( + height: 12, + ), + _Item( + title: "Symbol", + data: contract.symbol, + button: SimpleCopyButton( + data: contract.symbol, + ), + ), + const SizedBox( + height: 12, + ), + _Item( + title: "Type", + data: contract.type.name, + button: SimpleCopyButton( + data: contract.type.name, + ), + ), + const SizedBox( + height: 12, + ), + _Item( + title: "Decimals", + data: contract.decimals.toString(), + button: SimpleCopyButton( + data: contract.decimals.toString(), + ), + ), + ], + ), + ); + } +} + +class _Item extends StatelessWidget { + const _Item({ + Key? key, + required this.title, + required this.data, + required this.button, + }) : super(key: key); + + final String title; + final String data; + final Widget button; + + @override + Widget build(BuildContext context) { + return RoundedWhiteContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: STextStyles.itemSubtitle(context), + ), + button, + ], + ), + const SizedBox( + height: 5, + ), + data.isNotEmpty + ? SelectableText( + data, + style: STextStyles.w500_14(context), + ) + : Text( + "$title will appear here", + style: STextStyles.w500_14(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle3, + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/token_view/token_view.dart b/lib/pages/token_view/token_view.dart new file mode 100644 index 000000000..39bd5c2d6 --- /dev/null +++ b/lib/pages/token_view/token_view.dart @@ -0,0 +1,218 @@ +import 'package:event_bus/event_bus.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/pages/token_view/sub_widgets/token_summary.dart'; +import 'package:stackwallet/pages/token_view/sub_widgets/token_transaction_list_widget.dart'; +import 'package:stackwallet/pages/token_view/token_contract_details_view.dart'; +import 'package:stackwallet/pages/wallet_view/transaction_views/all_transactions_view.dart'; +import 'package:stackwallet/services/ethereum/ethereum_token_service.dart'; +import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; +import 'package:stackwallet/widgets/icon_widgets/eth_token_icon.dart'; +import 'package:tuple/tuple.dart'; + +final tokenServiceStateProvider = StateProvider((ref) => null); +final tokenServiceProvider = ChangeNotifierProvider( + (ref) => ref.watch(tokenServiceStateProvider)); + +/// [eventBus] should only be set during testing +class TokenView extends ConsumerStatefulWidget { + const TokenView({ + Key? key, + required this.walletId, + this.eventBus, + }) : super(key: key); + + static const String routeName = "/token"; + + final String walletId; + final EventBus? eventBus; + + @override + ConsumerState createState() => _TokenViewState(); +} + +class _TokenViewState extends ConsumerState { + late final WalletSyncStatus initialSyncStatus; + + @override + void initState() { + initialSyncStatus = ref.read(tokenServiceProvider)!.isRefreshing + ? WalletSyncStatus.syncing + : WalletSyncStatus.synced; + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + centerTitle: true, + title: Row( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + EthTokenIcon( + contractAddress: ref.watch( + tokenServiceProvider.select( + (value) => value!.tokenContract.address, + ), + ), + size: 24, + ), + const SizedBox( + width: 10, + ), + Flexible( + child: Text( + ref.watch(tokenServiceProvider + .select((value) => value!.tokenContract.name)), + style: STextStyles.navBarTitle(context), + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + ), + ), + ], + ), + ), + ], + ), + actions: [ + Padding( + padding: const EdgeInsets.only(right: 2), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + icon: SvgPicture.asset( + Assets.svg.verticalEllipsis, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, + ), + onPressed: () { + // todo: context menu + Navigator.of(context).pushNamed( + TokenContractDetailsView.routeName, + arguments: Tuple2( + ref.watch(tokenServiceProvider + .select((value) => value!.tokenContract.address)), + widget.walletId, + ), + ); + }, + ), + ), + ), + ], + ), + body: Container( + color: Theme.of(context).extension()!.background, + child: Column( + children: [ + const SizedBox( + height: 10, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: TokenSummary( + walletId: widget.walletId, + initialSyncStatus: initialSyncStatus, + ), + ), + const SizedBox( + height: 20, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Transactions", + style: STextStyles.itemSubtitle(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark3, + ), + ), + CustomTextButton( + text: "See all", + onTap: () { + Navigator.of(context).pushNamed( + AllTransactionsView.routeName, + arguments: widget.walletId, + ); + }, + ), + ], + ), + ), + const SizedBox( + height: 12, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: ClipRRect( + borderRadius: BorderRadius.vertical( + top: Radius.circular( + Constants.size.circularBorderRadius, + ), + bottom: Radius.circular( + // TokenView.navBarHeight / 2.0, + Constants.size.circularBorderRadius, + ), + ), + child: Container( + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: TokenTransactionsList( + walletId: widget.walletId, + ), + ), + ], + ), + ), + ), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/wallet_view/sub_widgets/transactions_list.dart b/lib/pages/wallet_view/sub_widgets/transactions_list.dart index 704a4e3d8..e68b443ed 100644 --- a/lib/pages/wallet_view/sub_widgets/transactions_list.dart +++ b/lib/pages/wallet_view/sub_widgets/transactions_list.dart @@ -2,16 +2,18 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/models/paymint/transactions_model.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; import 'package:stackwallet/pages/exchange_view/trade_details_view.dart'; import 'package:stackwallet/pages/wallet_view/sub_widgets/no_transactions_found.dart'; +import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; import 'package:stackwallet/providers/global/trades_service_provider.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/services/coins/manager.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; @@ -37,19 +39,10 @@ class TransactionsList extends ConsumerStatefulWidget { class _TransactionsListState extends ConsumerState { // bool _hasLoaded = false; - Map _transactions = {}; + List _transactions2 = []; late final ChangeNotifierProvider managerProvider; - void updateTransactions(TransactionData newData) { - _transactions = {}; - final newTransactions = - newData.txChunks.expand((element) => element.transactions); - for (final tx in newTransactions) { - _transactions[tx.txid] = tx; - } - } - BorderRadius get _borderRadiusFirst { return BorderRadius.only( topLeft: Radius.circular( @@ -73,12 +66,22 @@ class _TransactionsListState extends ConsumerState { } Widget itemBuilder( - BuildContext context, Transaction tx, BorderRadius? radius) { + BuildContext context, + Transaction tx, + BorderRadius? radius, + Coin coin, + ) { final matchingTrades = ref .read(tradesServiceProvider) .trades .where((e) => e.payInTxid == tx.txid || e.payOutTxid == tx.txid); - if (tx.txType == "Sent" && matchingTrades.isNotEmpty) { + + final isConfirmed = tx.isConfirmed( + ref.watch( + widget.managerProvider.select((value) => value.currentHeight)), + coin.requiredConfirmations); + + if (tx.type == TransactionType.outgoing && matchingTrades.isNotEmpty) { final trade = matchingTrades.first; return Container( decoration: BoxDecoration( @@ -90,13 +93,21 @@ class _TransactionsListState extends ConsumerState { children: [ TransactionCard( // this may mess with combined firo transactions - key: Key(tx.toString()), // + key: isConfirmed + ? Key(tx.txid + + tx.type.name + + tx.address.value.toString() + + tx.height.toString()) + : UniqueKey(), // transaction: tx, walletId: widget.walletId, ), TradeCard( // this may mess with combined firo transactions - key: Key(tx.toString() + trade.uuid), // + key: Key(tx.txid + + tx.type.name + + tx.address.value.toString() + + trade.uuid), // trade: trade, onTap: () async { if (Util.isDesktop) { @@ -182,7 +193,12 @@ class _TransactionsListState extends ConsumerState { ), child: TransactionCard( // this may mess with combined firo transactions - key: Key(tx.toString()), // + key: isConfirmed + ? Key(tx.txid + + tx.type.name + + tx.address.value.toString() + + tx.height.toString()) + : UniqueKey(), transaction: tx, walletId: widget.walletId, ), @@ -198,17 +214,15 @@ class _TransactionsListState extends ConsumerState { @override Widget build(BuildContext context) { - // final managerProvider = ref - // .watch(walletsChangeNotifierProvider) - // .getManagerProvider(widget.walletId); + final manager = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(widget.walletId))); return FutureBuilder( - future: - ref.watch(managerProvider.select((value) => value.transactionData)), - builder: (fbContext, AsyncSnapshot snapshot) { + future: manager.transactions, + builder: (fbContext, AsyncSnapshot> snapshot) { if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) { - updateTransactions(snapshot.data!); + _transactions2 = snapshot.data!; _hasLoaded = true; } if (!_hasLoaded) { @@ -227,11 +241,10 @@ class _TransactionsListState extends ConsumerState { ], ); } - if (_transactions.isEmpty) { + if (_transactions2.isEmpty) { return const NoTransActionsFound(); } else { - final list = _transactions.values.toList(growable: false); - list.sort((a, b) => b.timestamp - a.timestamp); + _transactions2.sort((a, b) => b.timestamp - a.timestamp); return RefreshIndicator( onRefresh: () async { //todo: check if print needed @@ -245,15 +258,20 @@ class _TransactionsListState extends ConsumerState { }, child: Util.isDesktop ? ListView.separated( + shrinkWrap: true, itemBuilder: (context, index) { BorderRadius? radius; - if (index == list.length - 1) { + if (_transactions2.length == 1) { + radius = BorderRadius.circular( + Constants.size.circularBorderRadius, + ); + } else if (index == _transactions2.length - 1) { radius = _borderRadiusLast; } else if (index == 0) { radius = _borderRadiusFirst; } - final tx = list[index]; - return itemBuilder(context, tx, radius); + final tx = _transactions2[index]; + return itemBuilder(context, tx, radius, manager.coin); }, separatorBuilder: (context, index) { return Container( @@ -264,19 +282,36 @@ class _TransactionsListState extends ConsumerState { .background, ); }, - itemCount: list.length, + itemCount: _transactions2.length, ) : ListView.builder( - itemCount: list.length, + itemCount: _transactions2.length, itemBuilder: (context, index) { BorderRadius? radius; - if (index == list.length - 1) { + bool shouldWrap = false; + if (_transactions2.length == 1) { + radius = BorderRadius.circular( + Constants.size.circularBorderRadius, + ); + } else if (index == _transactions2.length - 1) { radius = _borderRadiusLast; + shouldWrap = true; } else if (index == 0) { radius = _borderRadiusFirst; } - final tx = list[index]; - return itemBuilder(context, tx, radius); + final tx = _transactions2[index]; + if (shouldWrap) { + return Column( + children: [ + itemBuilder(context, tx, radius, manager.coin), + const SizedBox( + height: WalletView.navBarHeight + 14, + ), + ], + ); + } else { + return itemBuilder(context, tx, radius, manager.coin); + } }, ), ); diff --git a/lib/pages/wallet_view/sub_widgets/tx_icon.dart b/lib/pages/wallet_view/sub_widgets/tx_icon.dart index 6222301a6..ed7d94e64 100644 --- a/lib/pages/wallet_view/sub_widgets/tx_icon.dart +++ b/lib/pages/wallet_view/sub_widgets/tx_icon.dart @@ -1,17 +1,31 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/models/paymint/transactions_model.dart'; -import 'package:stackwallet/utilities/assets.dart'; +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; + +class TxIcon extends ConsumerWidget { + const TxIcon({ + Key? key, + required this.transaction, + required this.currentHeight, + required this.coin, + }) : super(key: key); -class TxIcon extends StatelessWidget { - const TxIcon({Key? key, required this.transaction}) : super(key: key); final Transaction transaction; + final int currentHeight; + final Coin coin; static const Size size = Size(32, 32); String _getAssetName( - bool isCancelled, bool isReceived, bool isPending, BuildContext context) { - if (!isReceived && transaction.subType == "mint") { + bool isCancelled, bool isReceived, bool isPending, IThemeAssets assets) { + if (!isReceived && transaction.subType == TransactionSubType.mint) { if (isCancelled) { return Assets.svg.anonymizeFailed; } @@ -23,42 +37,56 @@ class TxIcon extends StatelessWidget { if (isReceived) { if (isCancelled) { - return Assets.svg.receiveCancelled(context); + return assets.receive; } if (isPending) { - return Assets.svg.receivePending(context); + return assets.receivePending; } - return Assets.svg.receive(context); + return assets.receive; } else { if (isCancelled) { - return Assets.svg.sendCancelled(context); + return assets.sendCancelled; } if (isPending) { - return Assets.svg.sendPending(context); + return assets.sendPending; } - return Assets.svg.send(context); + return assets.send; } } @override - Widget build(BuildContext context) { - final txIsReceived = transaction.txType == "Received"; + Widget build(BuildContext context, WidgetRef ref) { + final txIsReceived = transaction.type == TransactionType.incoming; + + final assetName = _getAssetName( + transaction.isCancelled, + txIsReceived, + !transaction.isConfirmed( + currentHeight, + coin.requiredConfirmations, + ), + ref.watch(themeAssetsProvider), + ); return SizedBox( width: size.width, height: size.height, child: Center( - child: SvgPicture.asset( - _getAssetName( - transaction.isCancelled, - txIsReceived, - !transaction.confirmedStatus, - context, - ), - width: size.width, - height: size.height, - ), - ), + // if it starts with "assets" we assume its local + // TODO: a more thorough check + child: assetName.startsWith("assets") + ? SvgPicture.asset( + assetName, + width: size.width, + height: size.height, + ) + : SvgPicture.file( + File( + assetName, + ), + width: size.width, + height: size.height, + )), ); } } diff --git a/lib/pages/wallet_view/sub_widgets/wallet_balance_toggle_sheet.dart b/lib/pages/wallet_view/sub_widgets/wallet_balance_toggle_sheet.dart index 74308f2e8..f63351aac 100644 --- a/lib/pages/wallet_view/sub_widgets/wallet_balance_toggle_sheet.dart +++ b/lib/pages/wallet_view/sub_widgets/wallet_balance_toggle_sheet.dart @@ -1,14 +1,23 @@ -import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/balance.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart'; import 'package:stackwallet/providers/wallet/wallet_balance_toggle_state_provider.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; + +enum _BalanceType { + available, + full, + privateAvailable, + privateFull; +} class WalletBalanceToggleSheet extends ConsumerWidget { const WalletBalanceToggleSheet({ @@ -25,20 +34,31 @@ class WalletBalanceToggleSheet extends ConsumerWidget { final coin = ref.watch(walletsChangeNotifierProvider .select((value) => value.getManager(walletId).coin)); - Future? totalBalanceFuture; - Future? availableBalanceFuture; + final balance = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(walletId).balance)); + + _BalanceType _bal = + ref.watch(walletBalanceToggleStateProvider.state).state == + WalletBalanceToggleState.available + ? _BalanceType.available + : _BalanceType.full; + + Balance? balanceSecondary; if (coin == Coin.firo || coin == Coin.firoTestNet) { - final firoWallet = ref - .watch(walletsChangeNotifierProvider - .select((value) => value.getManager(walletId))) - .wallet as FiroWallet; - totalBalanceFuture = firoWallet.availablePublicBalance(); - availableBalanceFuture = firoWallet.availablePrivateBalance(); - } else { - final wallet = ref.watch(walletsChangeNotifierProvider - .select((value) => value.getManager(walletId))); - totalBalanceFuture = wallet.totalBalance; - availableBalanceFuture = wallet.availableBalance; + balanceSecondary = ref + .watch( + walletsChangeNotifierProvider.select( + (value) => value.getManager(walletId).wallet as FiroWallet?, + ), + ) + ?.balancePrivate; + + if (ref.watch(publicPrivateBalanceStateProvider.state).state == + "Private") { + _bal = _bal == _BalanceType.available + ? _BalanceType.privateAvailable + : _BalanceType.privateFull; + } } return Container( @@ -89,271 +109,103 @@ class WalletBalanceToggleSheet extends ConsumerWidget { const SizedBox( height: 24, ), - RawMaterialButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), + BalanceSelector( + title: "Available balance", + coin: coin, + balance: balance.spendable, onPressed: () { - final state = - ref.read(walletBalanceToggleStateProvider.state).state; - if (state != WalletBalanceToggleState.available) { - ref.read(walletBalanceToggleStateProvider.state).state = - WalletBalanceToggleState.available; - } + ref.read(walletBalanceToggleStateProvider.state).state = + WalletBalanceToggleState.available; + ref.read(publicPrivateBalanceStateProvider.state).state = + "Public"; Navigator.of(context).pop(); }, - child: Container( - color: Colors.transparent, - padding: const EdgeInsets.all(8), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 20, - height: 20, - child: Radio( - activeColor: Theme.of(context) - .extension()! - .radioButtonIconEnabled, - value: WalletBalanceToggleState.available, - groupValue: ref - .watch(walletBalanceToggleStateProvider.state) - .state, - onChanged: (_) { - ref - .read(walletBalanceToggleStateProvider.state) - .state = WalletBalanceToggleState.available; - Navigator.of(context).pop(); - }, - ), - ), - const SizedBox( - width: 12, - ), - if (coin != Coin.firo && coin != Coin.firoTestNet) - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Available balance", - style: STextStyles.titleBold12(context), - ), - const SizedBox( - height: 2, - ), - FutureBuilder( - future: availableBalanceFuture, - builder: (fbContext, - AsyncSnapshot snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData && - snapshot.data != null) { - return Text( - "${snapshot.data!.toStringAsFixed(Constants.decimalPlacesForCoin(coin))} ${coin.ticker}", - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } else { - return Text( - "", - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } - }), - ], - ), - if (coin == Coin.firo || coin == Coin.firoTestNet) - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Private balance", - style: STextStyles.titleBold12(context), - ), - const SizedBox( - height: 2, - ), - FutureBuilder( - future: availableBalanceFuture, - builder: (fbContext, - AsyncSnapshot snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData && - snapshot.data != null) { - return Text( - "${snapshot.data!.toStringAsFixed(Constants.decimalPlacesForCoin(coin))} ${coin.ticker}", - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } else { - return Text( - "", - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } - }), - ], - ), - ], - ), - ), + onChanged: (_) { + ref.read(walletBalanceToggleStateProvider.state).state = + WalletBalanceToggleState.available; + ref.read(publicPrivateBalanceStateProvider.state).state = + "Public"; + Navigator.of(context).pop(); + }, + value: _BalanceType.available, + groupValue: _bal, ), + if (balanceSecondary != null) + const SizedBox( + height: 12, + ), + if (balanceSecondary != null) + BalanceSelector( + title: "Available private balance", + coin: coin, + balance: balanceSecondary.spendable, + onPressed: () { + ref.read(walletBalanceToggleStateProvider.state).state = + WalletBalanceToggleState.available; + ref.read(publicPrivateBalanceStateProvider.state).state = + "Private"; + Navigator.of(context).pop(); + }, + onChanged: (_) { + ref.read(walletBalanceToggleStateProvider.state).state = + WalletBalanceToggleState.available; + ref.read(publicPrivateBalanceStateProvider.state).state = + "Private"; + Navigator.of(context).pop(); + }, + value: _BalanceType.privateAvailable, + groupValue: _bal, + ), const SizedBox( height: 12, ), - RawMaterialButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), + BalanceSelector( + title: "Full balance", + coin: coin, + balance: balance.total, onPressed: () { - final state = - ref.read(walletBalanceToggleStateProvider.state).state; - if (state != WalletBalanceToggleState.full) { - ref.read(walletBalanceToggleStateProvider.state).state = - WalletBalanceToggleState.full; - } + ref.read(walletBalanceToggleStateProvider.state).state = + WalletBalanceToggleState.full; + ref.read(publicPrivateBalanceStateProvider.state).state = + "Public"; Navigator.of(context).pop(); }, - child: Container( - color: Colors.transparent, - padding: const EdgeInsets.all(8), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 20, - height: 20, - child: Radio( - activeColor: Theme.of(context) - .extension()! - .radioButtonIconEnabled, - value: WalletBalanceToggleState.full, - groupValue: ref - .watch(walletBalanceToggleStateProvider.state) - .state, - onChanged: (_) { - ref - .read(walletBalanceToggleStateProvider.state) - .state = WalletBalanceToggleState.full; - Navigator.of(context).pop(); - }, - ), - ), - const SizedBox( - width: 12, - ), - if (coin != Coin.firo && coin != Coin.firoTestNet) - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Full balance", - style: STextStyles.titleBold12(context), - ), - const SizedBox( - height: 2, - ), - FutureBuilder( - future: totalBalanceFuture, - builder: (fbContext, - AsyncSnapshot snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData && - snapshot.data != null) { - return Text( - "${snapshot.data!.toStringAsFixed(Constants.decimalPlacesForCoin(coin))} ${coin.ticker}", - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } else { - return Text( - "", - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } - }), - ], - ), - if (coin == Coin.firo || coin == Coin.firoTestNet) - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Public balance", - style: STextStyles.titleBold12(context), - ), - const SizedBox( - height: 2, - ), - FutureBuilder( - future: totalBalanceFuture, - builder: (fbContext, - AsyncSnapshot snapshot) { - if (snapshot.connectionState == - ConnectionState.done && - snapshot.hasData && - snapshot.data != null) { - return Text( - "${snapshot.data!.toStringAsFixed(Constants.decimalPlacesForCoin(coin))} ${coin.ticker}", - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } else { - return Text( - "", - style: STextStyles.itemSubtitle12(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ); - } - }), - ], - ), - ], - ), - ), + onChanged: (_) { + ref.read(walletBalanceToggleStateProvider.state).state = + WalletBalanceToggleState.full; + ref.read(publicPrivateBalanceStateProvider.state).state = + "Public"; + Navigator.of(context).pop(); + }, + value: _BalanceType.full, + groupValue: _bal, ), + if (balanceSecondary != null) + const SizedBox( + height: 12, + ), + if (balanceSecondary != null) + BalanceSelector( + title: "Full private balance", + coin: coin, + balance: balanceSecondary.total, + onPressed: () { + ref.read(walletBalanceToggleStateProvider.state).state = + WalletBalanceToggleState.full; + ref.read(publicPrivateBalanceStateProvider.state).state = + "Private"; + Navigator.of(context).pop(); + }, + onChanged: (_) { + ref.read(walletBalanceToggleStateProvider.state).state = + WalletBalanceToggleState.full; + ref.read(publicPrivateBalanceStateProvider.state).state = + "Private"; + Navigator.of(context).pop(); + }, + value: _BalanceType.privateFull, + groupValue: _bal, + ), const SizedBox( height: 40, ), @@ -364,3 +216,86 @@ class WalletBalanceToggleSheet extends ConsumerWidget { ); } } + +class BalanceSelector extends ConsumerWidget { + const BalanceSelector({ + Key? key, + required this.title, + required this.coin, + required this.balance, + required this.onPressed, + required this.onChanged, + required this.value, + required this.groupValue, + }) : super(key: key); + + final String title; + final Coin coin; + final Amount balance; + final VoidCallback onPressed; + final void Function(T?) onChanged; + final T value; + final T? groupValue; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return RawMaterialButton( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: onPressed, + child: Container( + color: Colors.transparent, + padding: const EdgeInsets.all(8), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 20, + height: 20, + child: Radio( + activeColor: Theme.of(context) + .extension()! + .radioButtonIconEnabled, + value: value, + groupValue: groupValue, + onChanged: onChanged, + ), + ), + const SizedBox( + width: 12, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: STextStyles.titleBold12(context), + ), + const SizedBox( + height: 2, + ), + Text( + "${balance.localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), + ), + )} ${coin.ticker}", + style: STextStyles.itemSubtitle12(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ) + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart b/lib/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart index 04147c883..8b1378917 100644 --- a/lib/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart +++ b/lib/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart @@ -1,285 +1 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; -class WalletNavigationBar extends StatelessWidget { - const WalletNavigationBar({ - Key? key, - required this.onReceivePressed, - required this.onSendPressed, - required this.onExchangePressed, - required this.onBuyPressed, - required this.height, - required this.enableExchange, - }) : super(key: key); - - final VoidCallback onReceivePressed; - final VoidCallback onSendPressed; - final VoidCallback onExchangePressed; - final VoidCallback onBuyPressed; - final double height; - final bool enableExchange; - - @override - Widget build(BuildContext context) { - return Container( - height: height, - decoration: BoxDecoration( - color: Theme.of(context).extension()!.bottomNavBack, - boxShadow: [ - Theme.of(context).extension()!.standardBoxShadow - ], - borderRadius: BorderRadius.circular( - height / 2.0, - ), - ), - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 6, - vertical: 4, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - const SizedBox( - width: 12, - ), - RawMaterialButton( - constraints: const BoxConstraints( - minWidth: 66, - ), - onPressed: onReceivePressed, - splashColor: - Theme.of(context).extension()!.highlight, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - height / 2.0, - ), - ), - child: Container( - color: Colors.transparent, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 2.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Spacer(), - Container( - decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .accentColorDark - .withOpacity(0.4), - borderRadius: BorderRadius.circular( - 24, - ), - ), - child: Padding( - padding: const EdgeInsets.all(6.0), - child: SvgPicture.asset( - Assets.svg.arrowDownLeft, - width: 12, - height: 12, - color: Theme.of(context) - .extension()! - .accentColorDark, - ), - ), - ), - const SizedBox( - height: 4, - ), - Text( - "Receive", - style: STextStyles.buttonSmall(context), - ), - const Spacer(), - ], - ), - ), - ), - ), - RawMaterialButton( - constraints: const BoxConstraints( - minWidth: 66, - ), - onPressed: onSendPressed, - splashColor: - Theme.of(context).extension()!.highlight, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - height / 2.0, - ), - ), - child: Container( - color: Colors.transparent, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 2.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Spacer(), - Container( - decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .accentColorDark - .withOpacity(0.4), - borderRadius: BorderRadius.circular( - 24, - ), - ), - child: Padding( - padding: const EdgeInsets.all(6.0), - child: SvgPicture.asset( - Assets.svg.arrowUpRight, - width: 12, - height: 12, - color: Theme.of(context) - .extension()! - .accentColorDark, - ), - ), - ), - const SizedBox( - height: 4, - ), - Text( - "Send", - style: STextStyles.buttonSmall(context), - ), - const Spacer(), - ], - ), - ), - ), - ), - if (enableExchange) - RawMaterialButton( - constraints: const BoxConstraints( - minWidth: 66, - ), - onPressed: onExchangePressed, - splashColor: - Theme.of(context).extension()!.highlight, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - height / 2.0, - ), - ), - child: Container( - color: Colors.transparent, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 2.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const Spacer(), - SvgPicture.asset( - Assets.svg.exchange(context), - width: 24, - height: 24, - ), - const SizedBox( - height: 4, - ), - Text( - "Exchange", - style: STextStyles.buttonSmall(context), - ), - const Spacer(), - ], - ), - ), - ), - ), - const SizedBox( - width: 12, - ), - // TODO: Do not delete this code. - // only temporarily disabled - // Spacer( - // flex: 2, - // ), - // GestureDetector( - // onTap: onBuyPressed, - // child: Container( - // color: Colors.transparent, - // child: Padding( - // padding: const EdgeInsets.symmetric(vertical: 2.0), - // child: Column( - // crossAxisAlignment: CrossAxisAlignment.center, - // children: [ - // Spacer(), - // SvgPicture.asset( - // Assets.svg.buy, - // width: 24, - // height: 24, - // ), - // SizedBox( - // height: 4, - // ), - // Text( - // "Buy", - // style: STextStyles.buttonSmall(context), - // ), - // Spacer(), - // ], - // ), - // ), - // ), - // ), - ], - ), - ), - ); - } -} -// -// class BarButton extends StatelessWidget { -// const BarButton( -// {Key? key, required this.icon, required this.text, this.onPressed}) -// : super(key: key); -// -// final Widget icon; -// final String text; -// final VoidCallback? onPressed; -// -// @override -// Widget build(BuildContext context) { -// return Container( -// child: MaterialButton( -// splashColor: Theme.of(context).extension()!.highlight, -// padding: const EdgeInsets.all(0), -// minWidth: 45, -// shape: RoundedRectangleBorder( -// borderRadius: BorderRadius.circular( -// Constants.size.circularBorderRadius, -// ), -// ), -// materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, -// onPressed: onPressed, -// child: Padding( -// padding: const EdgeInsets.all(4.0), -// child: Column( -// mainAxisAlignment: MainAxisAlignment.center, -// children: [ -// icon, -// SizedBox( -// height: 4, -// ), -// Text( -// text, -// style: STextStyles.itemSubtitle12(context).copyWith( -// fontSize: 10, -// ), -// ), -// ], -// ), -// ), -// ), -// ); -// } -// } diff --git a/lib/pages/wallet_view/sub_widgets/wallet_refresh_button.dart b/lib/pages/wallet_view/sub_widgets/wallet_refresh_button.dart index 603b72338..b62323414 100644 --- a/lib/pages/wallet_view/sub_widgets/wallet_refresh_button.dart +++ b/lib/pages/wallet_view/sub_widgets/wallet_refresh_button.dart @@ -3,14 +3,14 @@ import 'dart:async'; import 'package:event_bus/event_bus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/pages/token_view/token_view.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/global_event_bus.dart'; -import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/animated_widgets/rotating_arrows.dart'; /// [eventBus] should only be set during testing class WalletRefreshButton extends ConsumerStatefulWidget { @@ -18,43 +18,33 @@ class WalletRefreshButton extends ConsumerStatefulWidget { Key? key, required this.walletId, required this.initialSyncStatus, + this.tokenContractAddress, this.onPressed, this.eventBus, + this.overrideIconColor, }) : super(key: key); final String walletId; final WalletSyncStatus initialSyncStatus; + final String? tokenContractAddress; final VoidCallback? onPressed; final EventBus? eventBus; + final Color? overrideIconColor; @override ConsumerState createState() => _RefreshButtonState(); } -class _RefreshButtonState extends ConsumerState - with TickerProviderStateMixin { +class _RefreshButtonState extends ConsumerState { late final EventBus eventBus; - late AnimationController? _spinController; - late Animation _spinAnimation; + late RotatingArrowsController _spinController; late StreamSubscription _syncStatusSubscription; @override void initState() { - _spinController = AnimationController( - duration: const Duration(seconds: 2), - vsync: this, - ); - - _spinAnimation = CurvedAnimation( - parent: _spinController!, - curve: Curves.linear, - ); - - if (widget.initialSyncStatus == WalletSyncStatus.syncing) { - _spinController?.repeat(); - } + _spinController = RotatingArrowsController(); eventBus = widget.eventBus != null ? widget.eventBus! : GlobalEventBus.instance; @@ -62,16 +52,30 @@ class _RefreshButtonState extends ConsumerState _syncStatusSubscription = eventBus.on().listen( (event) async { - if (event.walletId == widget.walletId) { + if (event.walletId == widget.walletId && + widget.tokenContractAddress == null) { switch (event.newStatus) { case WalletSyncStatus.unableToSync: - _spinController?.stop(); + _spinController.stop?.call(); break; case WalletSyncStatus.synced: - _spinController?.stop(); + _spinController.stop?.call(); break; case WalletSyncStatus.syncing: - unawaited(_spinController?.repeat()); + _spinController.repeat?.call(); + break; + } + } else if (widget.tokenContractAddress != null && + event.walletId == widget.walletId + widget.tokenContractAddress!) { + switch (event.newStatus) { + case WalletSyncStatus.unableToSync: + _spinController.stop?.call(); + break; + case WalletSyncStatus.synced: + _spinController.stop?.call(); + break; + case WalletSyncStatus.syncing: + _spinController.repeat?.call(); break; } } @@ -83,9 +87,6 @@ class _RefreshButtonState extends ConsumerState @override void dispose() { - _spinController?.dispose(); - _spinController = null; - _syncStatusSubscription.cancel(); super.dispose(); @@ -96,50 +97,64 @@ class _RefreshButtonState extends ConsumerState final isDesktop = Util.isDesktop; return SizedBox( - height: isDesktop ? 22 : 36, - width: isDesktop ? 22 : 36, - child: MaterialButton( - color: isDesktop - ? Theme.of(context).extension()!.buttonBackSecondary - : null, - splashColor: Theme.of(context).extension()!.highlight, - onPressed: () { - final managerProvider = ref - .read(walletsChangeNotifierProvider) - .getManagerProvider(widget.walletId); - final isRefreshing = ref.read(managerProvider).isRefreshing; - if (!isRefreshing) { - _spinController?.repeat(); - ref - .read(managerProvider) - .refresh() - .then((_) => _spinController?.stop()); - } - }, - elevation: 0, - highlightElevation: 0, - hoverElevation: 0, - padding: EdgeInsets.zero, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - child: RotationTransition( - turns: _spinAnimation, - child: SvgPicture.asset( - Assets.svg.arrowRotate, - width: isDesktop ? 12 : 24, - height: isDesktop ? 12 : 24, + height: isDesktop ? 22 : 36, + width: isDesktop ? 22 : 36, + child: Semantics( + label: "Refresh Button. Refreshes The Values In Summary.", + excludeSemantics: true, + child: MaterialButton( color: isDesktop ? Theme.of(context) .extension()! - .textFieldDefaultSearchIconRight - : Theme.of(context).extension()!.textFavoriteCard, + .buttonBackSecondary + : null, + splashColor: Theme.of(context).extension()!.highlight, + onPressed: () { + if (widget.tokenContractAddress == null) { + final managerProvider = ref + .read(walletsChangeNotifierProvider) + .getManagerProvider(widget.walletId); + final isRefreshing = ref.read(managerProvider).isRefreshing; + if (!isRefreshing) { + _spinController.repeat?.call(); + ref + .read(managerProvider) + .refresh() + .then((_) => _spinController.stop?.call()); + } + } else { + if (!ref.read(tokenServiceProvider)!.isRefreshing) { + ref.read(tokenServiceProvider)!.refresh(); + } + } + }, + elevation: 0, + highlightElevation: 0, + hoverElevation: 0, + padding: EdgeInsets.zero, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: RotatingArrows( + spinByDefault: + widget.initialSyncStatus == WalletSyncStatus.syncing, + width: isDesktop ? 12 : 24, + height: isDesktop ? 12 : 24, + controller: _spinController, + color: widget.overrideIconColor != null + ? widget.overrideIconColor! + : isDesktop + ? Theme.of(context) + .extension()! + .textFieldDefaultSearchIconRight + : Theme.of(context) + .extension()! + .textFavoriteCard, + ), ), - ), - ), - ); + )); } } diff --git a/lib/pages/wallet_view/sub_widgets/wallet_summary.dart b/lib/pages/wallet_view/sub_widgets/wallet_summary.dart index 2a5beb705..3b0817cd9 100644 --- a/lib/pages/wallet_view/sub_widgets/wallet_summary.dart +++ b/lib/pages/wallet_view/sub_widgets/wallet_summary.dart @@ -4,9 +4,9 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_summary_info.dart'; import 'package:stackwallet/services/coins/manager.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; class WalletSummary extends StatelessWidget { const WalletSummary({ @@ -48,8 +48,10 @@ class WalletSummary extends StatelessWidget { builder: (_, ref, __) { return Container( decoration: BoxDecoration( - color: Theme.of(context).extension()!.colorForCoin(ref - .watch(managerProvider.select((value) => value.coin))), + color: Theme.of(context) + .extension()! + .colorForCoin(ref.watch( + managerProvider.select((value) => value.coin))), borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, ), @@ -112,7 +114,6 @@ class WalletSummary extends StatelessWidget { padding: const EdgeInsets.all(16.0), child: WalletSummaryInfo( walletId: walletId, - managerProvider: managerProvider, initialSyncStatus: initialSyncStatus, ), ), diff --git a/lib/pages/wallet_view/sub_widgets/wallet_summary_info.dart b/lib/pages/wallet_view/sub_widgets/wallet_summary_info.dart index d38e17a6a..32db97f81 100644 --- a/lib/pages/wallet_view/sub_widgets/wallet_summary_info.dart +++ b/lib/pages/wallet_view/sub_widgets/wallet_summary_info.dart @@ -1,41 +1,42 @@ -import 'package:decimal/decimal.dart'; +import 'dart:async'; +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_balance_toggle_sheet.dart'; import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_refresh_button.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart'; import 'package:stackwallet/providers/wallet/wallet_balance_toggle_state_provider.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; -import 'package:stackwallet/services/coins/manager.dart'; +import 'package:stackwallet/services/event_bus/events/global/balance_refreshed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; +import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart'; -import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; -import 'package:stackwallet/widgets/animated_text.dart'; -class WalletSummaryInfo extends StatefulWidget { +class WalletSummaryInfo extends ConsumerStatefulWidget { const WalletSummaryInfo({ Key? key, required this.walletId, - required this.managerProvider, required this.initialSyncStatus, }) : super(key: key); final String walletId; - final ChangeNotifierProvider managerProvider; final WalletSyncStatus initialSyncStatus; @override - State createState() => _WalletSummaryInfoState(); + ConsumerState createState() => _WalletSummaryInfoState(); } -class _WalletSummaryInfoState extends State { - late final String walletId; - late final ChangeNotifierProvider managerProvider; +class _WalletSummaryInfoState extends ConsumerState { + late StreamSubscription _balanceUpdated; void showSheet() { showModalBottomSheet( @@ -46,249 +47,148 @@ class _WalletSummaryInfoState extends State { top: Radius.circular(20), ), ), - builder: (_) => WalletBalanceToggleSheet(walletId: walletId), + builder: (_) => WalletBalanceToggleSheet(walletId: widget.walletId), ); } - Decimal? _balanceTotalCached; - Decimal? _balanceCached; - @override void initState() { - walletId = widget.walletId; - managerProvider = widget.managerProvider; + _balanceUpdated = + GlobalEventBus.instance.on().listen( + (event) async { + if (event.walletId == widget.walletId) { + setState(() {}); + } + }, + ); super.initState(); } + @override + void dispose() { + _balanceUpdated.cancel(); + super.dispose(); + } + @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); + + final externalCalls = ref.watch( + prefsChangeNotifierProvider.select((value) => value.externalCalls)); + final coin = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(widget.walletId).coin)); + final balance = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(widget.walletId).balance)); + + final locale = ref.watch( + localeServiceChangeNotifierProvider.select((value) => value.locale)); + + final baseCurrency = ref + .watch(prefsChangeNotifierProvider.select((value) => value.currency)); + + final priceTuple = ref.watch(priceAnd24hChangeNotifierProvider + .select((value) => value.getPrice(coin))); + + final _showAvailable = + ref.watch(walletBalanceToggleStateProvider.state).state == + WalletBalanceToggleState.available; + + final Amount balanceToShow; + String title; + + if (coin == Coin.firo || coin == Coin.firoTestNet) { + final _showPrivate = + ref.watch(publicPrivateBalanceStateProvider.state).state == "Private"; + + final firoWallet = ref.watch(walletsChangeNotifierProvider.select( + (value) => value.getManager(widget.walletId).wallet)) as FiroWallet; + + final bal = _showPrivate ? firoWallet.balancePrivate : firoWallet.balance; + + balanceToShow = _showAvailable ? bal.spendable : bal.total; + title = _showAvailable ? "Available" : "Full"; + title += _showPrivate ? " private balance" : " public balance"; + } else { + balanceToShow = _showAvailable ? balance.spendable : balance.total; + title = _showAvailable ? "Available balance" : "Full balance"; + } + return Row( children: [ Expanded( - child: Consumer( - builder: (_, ref, __) { - final Coin coin = - ref.watch(managerProvider.select((value) => value.coin)); - final externalCalls = ref.watch(prefsChangeNotifierProvider - .select((value) => value.externalCalls)); - - Future? totalBalanceFuture; - Future? availableBalanceFuture; - if (coin == Coin.firo || coin == Coin.firoTestNet) { - final firoWallet = - ref.watch(managerProvider.select((value) => value.wallet)) - as FiroWallet; - totalBalanceFuture = firoWallet.availablePublicBalance(); - availableBalanceFuture = firoWallet.availablePrivateBalance(); - } else { - totalBalanceFuture = ref.watch( - managerProvider.select((value) => value.totalBalance)); - - availableBalanceFuture = ref.watch( - managerProvider.select((value) => value.availableBalance)); - } - - final locale = ref.watch(localeServiceChangeNotifierProvider - .select((value) => value.locale)); - - final baseCurrency = ref.watch(prefsChangeNotifierProvider - .select((value) => value.currency)); - - final priceTuple = ref.watch(priceAnd24hChangeNotifierProvider - .select((value) => value.getPrice(coin))); - - final _showAvailable = - ref.watch(walletBalanceToggleStateProvider.state).state == - WalletBalanceToggleState.available; - - return FutureBuilder( - future: _showAvailable - ? availableBalanceFuture - : totalBalanceFuture, - builder: (fbContext, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done && - snapshot.hasData && - snapshot.data != null) { - if (_showAvailable) { - _balanceCached = snapshot.data!; - } else { - _balanceTotalCached = snapshot.data!; - } - } - Decimal? balanceToShow = - _showAvailable ? _balanceCached : _balanceTotalCached; - - if (balanceToShow != null) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - GestureDetector( - onTap: showSheet, - child: Row( - children: [ - if (coin == Coin.firo || coin == Coin.firoTestNet) - Text( - "${_showAvailable ? "Private" : "Public"} Balance", - style: - STextStyles.subtitle500(context).copyWith( - color: Theme.of(context) - .extension()! - .textFavoriteCard, - ), - ), - if (coin != Coin.firo && coin != Coin.firoTestNet) - Text( - "${_showAvailable ? "Available" : "Full"} Balance", - style: - STextStyles.subtitle500(context).copyWith( - color: Theme.of(context) - .extension()! - .textFavoriteCard, - ), - ), - const SizedBox( - width: 4, - ), - SvgPicture.asset( - Assets.svg.chevronDown, - color: Theme.of(context) - .extension()! - .textFavoriteCard, - width: 8, - height: 4, - ), - ], - ), - ), - const Spacer(), - FittedBox( - fit: BoxFit.scaleDown, - child: SelectableText( - "${Format.localizedStringAsFixed( - value: balanceToShow, - locale: locale, - decimalPlaces: 8, - )} ${coin.ticker}", - style: STextStyles.pageTitleH1(context).copyWith( - fontSize: 24, - color: Theme.of(context) - .extension()! - .textFavoriteCard, - ), - ), - ), - if (externalCalls) - Text( - "${Format.localizedStringAsFixed( - value: priceTuple.item1 * balanceToShow, - locale: locale, - decimalPlaces: 2, - )} $baseCurrency", - style: STextStyles.subtitle500(context).copyWith( - color: Theme.of(context) - .extension()! - .textFavoriteCard, - ), - ), - ], - ); - } else { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - GestureDetector( - onTap: showSheet, - child: Row( - children: [ - if (coin == Coin.firo || coin == Coin.firoTestNet) - Text( - "${_showAvailable ? "Private" : "Public"} Balance", - style: - STextStyles.subtitle500(context).copyWith( - color: Theme.of(context) - .extension()! - .textFavoriteCard, - ), - ), - if (coin != Coin.firo && coin != Coin.firoTestNet) - Text( - "${_showAvailable ? "Available" : "Full"} Balance", - style: - STextStyles.subtitle500(context).copyWith( - color: Theme.of(context) - .extension()! - .textFavoriteCard, - ), - ), - const SizedBox( - width: 4, - ), - SvgPicture.asset( - Assets.svg.chevronDown, - width: 8, - height: 4, - color: Theme.of(context) - .extension()! - .textFavoriteCard, - ), - ], - ), - ), - const Spacer(), - AnimatedText( - stringsToLoopThrough: const [ - "Loading balance", - "Loading balance.", - "Loading balance..", - "Loading balance..." - ], - style: STextStyles.pageTitleH1(context).copyWith( - fontSize: 24, - color: Theme.of(context) - .extension()! - .textFavoriteCard, - ), - ), - AnimatedText( - stringsToLoopThrough: const [ - "Loading balance", - "Loading balance.", - "Loading balance..", - "Loading balance..." - ], - style: STextStyles.subtitle500(context).copyWith( - color: Theme.of(context) - .extension()! - .textFavoriteCard, - ), - ), - ], - ); - } - }, - ); - }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + GestureDetector( + onTap: showSheet, + child: Row( + children: [ + Text( + title, + style: STextStyles.subtitle500(context).copyWith( + color: Theme.of(context) + .extension()! + .textFavoriteCard, + ), + ), + const SizedBox( + width: 4, + ), + SvgPicture.asset( + Assets.svg.chevronDown, + color: Theme.of(context) + .extension()! + .textFavoriteCard, + width: 8, + height: 4, + ), + ], + ), + ), + const Spacer(), + FittedBox( + fit: BoxFit.scaleDown, + child: SelectableText( + "${balanceToShow.localizedStringAsFixed( + locale: locale, + )} ${coin.ticker}", + style: STextStyles.pageTitleH1(context).copyWith( + fontSize: 24, + color: Theme.of(context) + .extension()! + .textFavoriteCard, + ), + ), + ), + if (externalCalls) + Text( + "${(priceTuple.item1 * balanceToShow.decimal).toAmount( + fractionDigits: 2, + ).localizedStringAsFixed( + locale: locale, + )} $baseCurrency", + style: STextStyles.subtitle500(context).copyWith( + color: Theme.of(context) + .extension()! + .textFavoriteCard, + ), + ), + ], ), ), Column( children: [ - Consumer( - builder: (_, ref, __) { - return SvgPicture.asset( - Assets.svg.iconFor( - coin: ref.watch( - managerProvider.select((value) => value.coin), - ), - ), - width: 24, - height: 24, - ); - }, + SvgPicture.file( + File( + ref.watch(coinIconProvider(coin)), + ), + width: 24, + height: 24, ), const Spacer(), WalletRefreshButton( - walletId: walletId, + walletId: widget.walletId, initialSyncStatus: widget.initialSyncStatus, ), ], diff --git a/lib/pages/wallet_view/transaction_views/all_transactions_view.dart b/lib/pages/wallet_view/transaction_views/all_transactions_view.dart index 27bb36331..8d23a67cf 100644 --- a/lib/pages/wallet_view/transaction_views/all_transactions_view.dart +++ b/lib/pages/wallet_view/transaction_views/all_transactions_view.dart @@ -3,8 +3,8 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/models/contact.dart'; -import 'package:stackwallet/models/paymint/transactions_model.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart'; +import 'package:stackwallet/models/isar/models/contact_entry.dart'; import 'package:stackwallet/models/transaction_filter.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart'; @@ -13,12 +13,13 @@ import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_sear import 'package:stackwallet/providers/global/address_book_service_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/ui/transaction_filter_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; @@ -89,11 +90,15 @@ class _TransactionDetailsViewState extends ConsumerState { return false; } - if (filter.received && !filter.sent && tx.txType == "Sent") { + if (filter.received && + !filter.sent && + tx.type == TransactionType.outgoing) { return false; } - if (filter.sent && !filter.received && tx.txType == "Received") { + if (filter.sent && + !filter.received && + tx.type == TransactionType.incoming) { return false; } @@ -107,7 +112,7 @@ class _TransactionDetailsViewState extends ConsumerState { return false; } - if (filter.amount != null && filter.amount != tx.amount) { + if (filter.amount != null && filter.amount! != tx.realAmount) { return false; } @@ -115,8 +120,8 @@ class _TransactionDetailsViewState extends ConsumerState { }).toList(); } - bool _isKeywordMatch(Transaction tx, String keyword, List contacts, - Map notes) { + bool _isKeywordMatch(Transaction tx, String keyword, + List contacts, Map notes) { if (keyword.isEmpty) { return true; } @@ -126,12 +131,15 @@ class _TransactionDetailsViewState extends ConsumerState { // check if address book name contains contains |= contacts .where((e) => - e.addresses.where((a) => a.address == tx.address).isNotEmpty && + e.addresses + .where((a) => a.address == tx.address.value?.value) + .isNotEmpty && e.name.toLowerCase().contains(keyword)) .isNotEmpty; // check if address contains - contains |= tx.address.toLowerCase().contains(keyword); + contains |= + tx.address.value?.value.toLowerCase().contains(keyword) ?? false; // check if note contains contains |= notes[tx.txid] != null && @@ -141,11 +149,10 @@ class _TransactionDetailsViewState extends ConsumerState { contains |= tx.txid.toLowerCase().contains(keyword); // check if subType contains - contains |= - tx.subType.isNotEmpty && tx.subType.toLowerCase().contains(keyword); + contains |= tx.subType.name.toLowerCase().contains(keyword); // check if txType contains - contains |= tx.txType.toLowerCase().contains(keyword); + contains |= tx.type.name.toLowerCase().contains(keyword); // check if date contains contains |= @@ -454,17 +461,13 @@ class _TransactionDetailsViewState extends ConsumerState { // debugPrint("Consumer build called"); return FutureBuilder( - future: ref.watch(managerProvider - .select((value) => value.transactionData)), - builder: (_, AsyncSnapshot snapshot) { + future: ref.watch( + managerProvider.select((value) => value.transactions)), + builder: (_, AsyncSnapshot> snapshot) { if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) { final filtered = filter( - transactions: snapshot.data! - .getAllTransactions() - .values - .toList(), - filter: criteria); + transactions: snapshot.data!, filter: criteria); final searched = search(_searchString, filtered); @@ -787,33 +790,35 @@ class _DesktopTransactionCardRowState late final Transaction _transaction; late final String walletId; - String whatIsIt(String type, Coin coin) { + String whatIsIt(TransactionType type, Coin coin, int height) { if (coin == Coin.epicCash && _transaction.slateId == null) { return "Restored Funds"; } - if (_transaction.subType == "mint") { - if (_transaction.confirmedStatus) { + if (_transaction.subType == TransactionSubType.mint) { + if (_transaction.isConfirmed(height, coin.requiredConfirmations)) { return "Anonymized"; } else { return "Anonymizing"; } } - if (type == "Received") { - if (_transaction.confirmedStatus) { + if (type == TransactionType.incoming) { + if (_transaction.isConfirmed(height, coin.requiredConfirmations)) { return "Received"; } else { return "Receiving"; } - } else if (type == "Sent") { - if (_transaction.confirmedStatus) { + } else if (type == TransactionType.outgoing) { + if (_transaction.isConfirmed(height, coin.requiredConfirmations)) { return "Sent"; } else { return "Sending"; } + } else if (type == TransactionType.sentToSelf) { + return "Sent to self"; } else { - return type; + return type.name; } } @@ -843,15 +848,20 @@ class _DesktopTransactionCardRowState late final String prefix; if (Util.isDesktop) { - if (_transaction.txType == "Sent") { + if (_transaction.type == TransactionType.outgoing) { prefix = "-"; - } else if (_transaction.txType == "Received") { + } else if (_transaction.type == TransactionType.incoming) { prefix = "+"; + } else { + prefix = ""; } } else { prefix = ""; } + final currentHeight = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(walletId).currentHeight)); + return Material( color: Theme.of(context).extension()!.popupBG, elevation: 0, @@ -911,7 +921,11 @@ class _DesktopTransactionCardRowState ), child: Row( children: [ - TxIcon(transaction: _transaction), + TxIcon( + transaction: _transaction, + currentHeight: currentHeight, + coin: coin, + ), const SizedBox( width: 12, ), @@ -920,7 +934,11 @@ class _DesktopTransactionCardRowState child: Text( _transaction.isCancelled ? "Cancelled" - : whatIsIt(_transaction.txType, coin), + : whatIsIt( + _transaction.type, + coin, + currentHeight, + ), style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( color: Theme.of(context).extension()!.textDark, @@ -938,9 +956,11 @@ class _DesktopTransactionCardRowState flex: 6, child: Builder( builder: (_) { - final amount = _transaction.amount; + final amount = _transaction.realAmount; return Text( - "$prefix${Format.satoshiAmountToPrettyString(amount, locale, coin)} ${coin.ticker}", + "$prefix${amount.localizedStringAsFixed( + locale: locale, + )} ${coin.ticker}", style: STextStyles.desktopTextExtraExtraSmall(context) .copyWith( color: Theme.of(context) @@ -957,15 +977,14 @@ class _DesktopTransactionCardRowState flex: 4, child: Builder( builder: (_) { - int value = _transaction.amount; + final amount = _transaction.realAmount; return Text( - "$prefix${Format.localizedStringAsFixed( - value: Format.satoshisToAmount(value, coin: coin) * - price, - locale: locale, - decimalPlaces: 2, - )} $baseCurrency", + "$prefix${(amount.decimal * price).toAmount( + fractionDigits: 2, + ).localizedStringAsFixed( + locale: locale, + )} $baseCurrency", style: STextStyles.desktopTextExtraExtraSmall(context), ); }, diff --git a/lib/pages/wallet_view/transaction_views/dialogs/cancelling_transaction_progress_dialog.dart b/lib/pages/wallet_view/transaction_views/dialogs/cancelling_transaction_progress_dialog.dart index 7d737ab41..810579e6c 100644 --- a/lib/pages/wallet_view/transaction_views/dialogs/cancelling_transaction_progress_dialog.dart +++ b/lib/pages/wallet_view/transaction_views/dialogs/cancelling_transaction_progress_dialog.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/animated_widgets/rotating_arrows.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; class CancellingTransactionProgressDialog extends StatefulWidget { @@ -13,51 +11,19 @@ class CancellingTransactionProgressDialog extends StatefulWidget { } class _CancellingTransactionProgressDialogState - extends State - with TickerProviderStateMixin { - late AnimationController? _spinController; - late Animation _spinAnimation; - - @override - void initState() { - _spinController = AnimationController( - duration: const Duration(seconds: 2), - vsync: this, - )..repeat(); - - _spinAnimation = CurvedAnimation( - parent: _spinController!, - curve: Curves.linear, - ); - - super.initState(); - } - - @override - void dispose() { - _spinController?.dispose(); - _spinController = null; - - super.dispose(); - } - + extends State { @override Widget build(BuildContext context) { return WillPopScope( onWillPop: () async { return false; }, - child: StackDialog( + child: const StackDialog( title: "Cancelling transaction", message: "This may take a while. Please do not exit this screen.", - icon: RotationTransition( - turns: _spinAnimation, - child: SvgPicture.asset( - Assets.svg.arrowRotate3, - width: 24, - height: 24, - color: Theme.of(context).extension()!.accentColorDark, - ), + icon: RotatingArrows( + width: 24, + height: 24, ), // rightButton: TextButton( // style: Theme.of(context).textButtonTheme.style?.copyWith( diff --git a/lib/pages/wallet_view/transaction_views/edit_note_view.dart b/lib/pages/wallet_view/transaction_views/edit_note_view.dart index e8d7b05f9..6e984ad66 100644 --- a/lib/pages/wallet_view/transaction_views/edit_note_view.dart +++ b/lib/pages/wallet_view/transaction_views/edit_note_view.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; @@ -204,7 +204,7 @@ class _EditNoteViewState extends ConsumerState { }, style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), child: Text( "Save", style: STextStyles.button(context), diff --git a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart index a5967934a..440356166 100644 --- a/lib/pages/wallet_view/transaction_views/transaction_details_view.dart +++ b/lib/pages/wallet_view/transaction_views/transaction_details_view.dart @@ -1,29 +1,31 @@ import 'dart:async'; -import 'package:decimal/decimal.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:stackwallet/models/models.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages/receive_view/addresses/address_details_view.dart'; import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/dialogs/cancelling_transaction_progress_dialog.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/edit_note_view.dart'; import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; +import 'package:stackwallet/providers/db/main_db_provider.dart'; import 'package:stackwallet/providers/global/address_book_service_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart'; import 'package:stackwallet/services/coins/manager.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/block_explorers.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; @@ -66,9 +68,11 @@ class _TransactionDetailsViewState late final String walletId; late final Coin coin; - late final Decimal amount; - late final Decimal fee; + late final Amount amount; + late final Amount fee; late final String amountPrefix; + late final String unit; + late final bool isTokenTx; bool showFeePending = false; @@ -76,19 +80,27 @@ class _TransactionDetailsViewState void initState() { isDesktop = Util.isDesktop; _transaction = widget.transaction; + isTokenTx = _transaction.subType == TransactionSubType.ethToken; walletId = widget.walletId; coin = widget.coin; - amount = Format.satoshisToAmount(_transaction.amount, coin: coin); - fee = Format.satoshisToAmount(_transaction.fees, coin: coin); + amount = _transaction.realAmount; + fee = _transaction.fee.toAmountAsRaw(fractionDigits: coin.decimals); if ((coin == Coin.firo || coin == Coin.firoTestNet) && - _transaction.subType == "mint") { + _transaction.subType == TransactionSubType.mint) { amountPrefix = ""; } else { - amountPrefix = _transaction.txType.toLowerCase() == "sent" ? "-" : "+"; + amountPrefix = _transaction.type == TransactionType.outgoing ? "-" : "+"; } + unit = isTokenTx + ? ref + .read(mainDBProvider) + .getEthContractSync(_transaction.otherData!)! + .symbol + : coin.ticker; + // if (coin == Coin.firo || coin == Coin.firoTestNet) { // showFeePending = true; // } else { @@ -102,10 +114,11 @@ class _TransactionDetailsViewState super.dispose(); } - String whatIsIt(String type) { + String whatIsIt(Transaction tx, int height) { + final type = tx.type; if (coin == Coin.firo || coin == Coin.firoTestNet) { - if (_transaction.subType == "mint") { - if (_transaction.confirmedStatus) { + if (tx.subType == TransactionSubType.mint) { + if (tx.isConfirmed(height, coin.requiredConfirmations)) { return "Minted"; } else { return "Minting"; @@ -113,23 +126,25 @@ class _TransactionDetailsViewState } } - if (type == "Received") { + if (type == TransactionType.incoming) { // if (_transaction.isMinting) { // return "Minting"; // } else - if (_transaction.confirmedStatus) { + if (tx.isConfirmed(height, coin.requiredConfirmations)) { return "Received"; } else { return "Receiving"; } - } else if (type == "Sent") { - if (_transaction.confirmedStatus) { + } else if (type == TransactionType.outgoing) { + if (tx.isConfirmed(height, coin.requiredConfirmations)) { return "Sent"; } else { return "Sending"; } + } else if (type == TransactionType.sentToSelf) { + return "Sent to self"; } else { - return type; + return type.name; } } @@ -202,7 +217,7 @@ class _TransactionDetailsViewState rightButton: TextButton( style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), onPressed: () { Navigator.of(context).pop(true); }, @@ -298,6 +313,9 @@ class _TransactionDetailsViewState @override Widget build(BuildContext context) { + final currentHeight = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(walletId).currentHeight)); + return ConditionalParent( condition: !isDesktop, builder: (child) => Background( @@ -403,6 +421,8 @@ class _TransactionDetailsViewState children: [ TxIcon( transaction: _transaction, + currentHeight: currentHeight, + coin: coin, ), const SizedBox( width: 16, @@ -411,7 +431,9 @@ class _TransactionDetailsViewState _transaction.isCancelled ? "Cancelled" : whatIsIt( - _transaction.txType), + _transaction, + currentHeight, + ), style: STextStyles.desktopTextMedium( context), @@ -424,16 +446,13 @@ class _TransactionDetailsViewState : CrossAxisAlignment.start, children: [ SelectableText( - "$amountPrefix${Format.localizedStringAsFixed( - value: amount, + "$amountPrefix${amount.localizedStringAsFixed( locale: ref.watch( localeServiceChangeNotifierProvider .select((value) => value.locale), ), - decimalPlaces: Constants - .decimalPlacesForCoin(coin), - )} ${coin.ticker}", + )} $unit", style: isDesktop ? STextStyles .desktopTextExtraExtraSmall( @@ -455,23 +474,26 @@ class _TransactionDetailsViewState .select((value) => value.externalCalls))) SelectableText( - "$amountPrefix${Format.localizedStringAsFixed( - value: amount * - ref.watch( - priceAnd24hChangeNotifierProvider - .select((value) => - value - .getPrice( - coin) - .item1), + "$amountPrefix${(amount.decimal * ref.watch( + priceAnd24hChangeNotifierProvider.select( + (value) => isTokenTx + ? value + .getTokenPrice( + _transaction + .otherData!) + .item1 + : value + .getPrice( + coin) + .item1), + )).toAmount(fractionDigits: 2).localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider + .select( + (value) => value.locale, + ), ), - locale: ref.watch( - localeServiceChangeNotifierProvider - .select((value) => - value.locale), - ), - decimalPlaces: 2, - )} ${ref.watch( + )} ${ref.watch( prefsChangeNotifierProvider .select( (value) => value.currency, @@ -489,6 +511,8 @@ class _TransactionDetailsViewState if (!isDesktop) TxIcon( transaction: _transaction, + currentHeight: currentHeight, + coin: coin, ), ], ), @@ -523,13 +547,17 @@ class _TransactionDetailsViewState SelectableText( _transaction.isCancelled ? "Cancelled" - : whatIsIt(_transaction.txType), + : whatIsIt( + _transaction, + currentHeight, + ), style: isDesktop ? STextStyles .desktopTextExtraExtraSmall( context) .copyWith( - color: _transaction.txType == "Sent" + color: _transaction.type == + TransactionType.outgoing ? Theme.of(context) .extension()! .accentColorOrange @@ -546,11 +574,12 @@ class _TransactionDetailsViewState ), if (!((coin == Coin.monero || coin == Coin.wownero) && - _transaction.txType.toLowerCase() == - "sent") && + _transaction.type == + TransactionType.outgoing) && !((coin == Coin.firo || coin == Coin.firoTestNet) && - _transaction.subType == "mint")) + _transaction.subType == + TransactionSubType.mint)) isDesktop ? const _Divider() : const SizedBox( @@ -558,11 +587,12 @@ class _TransactionDetailsViewState ), if (!((coin == Coin.monero || coin == Coin.wownero) && - _transaction.txType.toLowerCase() == - "sent") && + _transaction.type == + TransactionType.outgoing) && !((coin == Coin.firo || coin == Coin.firoTestNet) && - _transaction.subType == "mint")) + _transaction.subType == + TransactionSubType.mint)) RoundedWhiteContainer( padding: isDesktop ? const EdgeInsets.all(16) @@ -577,32 +607,62 @@ class _TransactionDetailsViewState crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - _transaction.txType.toLowerCase() == - "sent" - ? "Sent to" - : "Receiving address", - style: isDesktop - ? STextStyles - .desktopTextExtraExtraSmall( - context) - : STextStyles.itemSubtitle( - context), + ConditionalParent( + condition: kDebugMode, + builder: (child) { + return Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + child, + CustomTextButton( + text: "Info", + onTap: () { + Navigator.of(context) + .pushNamed( + AddressDetailsView + .routeName, + arguments: Tuple2( + _transaction.address + .value!.id, + widget.walletId, + ), + ); + }, + ) + ], + ); + }, + child: Text( + _transaction.type == + TransactionType.outgoing + ? "Sent to" + : "Receiving address", + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + : STextStyles.itemSubtitle( + context), + ), ), const SizedBox( height: 8, ), - _transaction.txType.toLowerCase() == - "received" + _transaction.type == + TransactionType.incoming ? FutureBuilder( future: fetchContactNameFor( - _transaction.address), + _transaction.address + .value!.value), builder: (builderContext, AsyncSnapshot snapshot) { String addressOrContactName = - _transaction.address; + _transaction.address + .value!.value; if (snapshot.connectionState == ConnectionState .done && @@ -630,7 +690,8 @@ class _TransactionDetailsViewState }, ) : SelectableText( - _transaction.address, + _transaction + .address.value!.value, style: isDesktop ? STextStyles .desktopTextExtraExtraSmall( @@ -651,7 +712,7 @@ class _TransactionDetailsViewState ), if (isDesktop) IconCopyButton( - data: _transaction.address, + data: _transaction.address.value!.value, ), ], ), @@ -854,26 +915,28 @@ class _TransactionDetailsViewState ? const EdgeInsets.all(16) : const EdgeInsets.all(12), child: Builder(builder: (context) { - final feeString = showFeePending - ? _transaction.confirmedStatus - ? Format.localizedStringAsFixed( - value: fee, + String feeString = showFeePending + ? _transaction.isConfirmed( + currentHeight, + coin.requiredConfirmations, + ) + ? fee.localizedStringAsFixed( locale: ref.watch( - localeServiceChangeNotifierProvider - .select((value) => - value.locale)), - decimalPlaces: - Constants.decimalPlacesForCoin( - coin)) + localeServiceChangeNotifierProvider + .select( + (value) => value.locale), + ), + ) : "Pending" - : Format.localizedStringAsFixed( - value: fee, + : fee.localizedStringAsFixed( locale: ref.watch( - localeServiceChangeNotifierProvider - .select( - (value) => value.locale)), - decimalPlaces: - Constants.decimalPlacesForCoin(coin)); + localeServiceChangeNotifierProvider + .select((value) => value.locale), + ), + ); + if (isTokenTx) { + feeString += " ${coin.ticker}"; + } return Row( mainAxisAlignment: @@ -946,12 +1009,25 @@ class _TransactionDetailsViewState ? const EdgeInsets.all(16) : const EdgeInsets.all(12), child: Builder(builder: (context) { - final height = widget.coin != Coin.epicCash && - _transaction.confirmedStatus - ? "${_transaction.height == 0 ? "Unknown" : _transaction.height}" - : _transaction.confirmations > 0 - ? "${_transaction.height}" - : "Pending"; + final String height; + + if (widget.coin == Coin.bitcoincash || + widget.coin == Coin.bitcoincashTestnet) { + height = + "${_transaction.height != null && _transaction.height! > 0 ? _transaction.height! : "Pending"}"; + } else { + height = widget.coin != Coin.epicCash && + _transaction.isConfirmed( + currentHeight, + coin.requiredConfirmations, + ) + ? "${_transaction.height == 0 ? "Unknown" : _transaction.height}" + : _transaction.getConfirmations( + currentHeight) > + 0 + ? "${_transaction.height}" + : "Pending"; + } return Row( mainAxisAlignment: @@ -1013,6 +1089,46 @@ class _TransactionDetailsViewState ); }), ), + if (coin == Coin.ethereum) + isDesktop + ? const _Divider() + : const SizedBox( + height: 12, + ), + if (coin == Coin.ethereum) + RoundedWhiteContainer( + padding: isDesktop + ? const EdgeInsets.all(16) + : const EdgeInsets.all(12), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Nonce", + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + : STextStyles.itemSubtitle(context), + ), + SelectableText( + _transaction.nonce.toString(), + style: isDesktop + ? STextStyles + .desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ) + : STextStyles.itemSubtitle12(context), + ), + ], + ), + ), isDesktop ? const _Divider() : const SizedBox( @@ -1067,7 +1183,7 @@ class _TransactionDetailsViewState height: 8, ), if (coin != Coin.epicCash) - BlueTextButton( + CustomTextButton( text: "Open in block explorer", onTap: () async { final uri = @@ -1102,18 +1218,20 @@ class _TransactionDetailsViewState .externalApplication, ); } catch (_) { - unawaited( - showDialog( - context: context, - builder: (_) => - StackOkDialog( - title: - "Could not open in block explorer", - message: - "Failed to open \"${uri.toString()}\"", + if (mounted) { + unawaited( + showDialog( + context: context, + builder: (_) => + StackOkDialog( + title: + "Could not open in block explorer", + message: + "Failed to open \"${uri.toString()}\"", + ), ), - ), - ); + ); + } } finally { // Future.delayed( // const Duration(seconds: 1), @@ -1297,80 +1415,95 @@ class _TransactionDetailsViewState ), floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, floatingActionButton: (coin == Coin.epicCash && - _transaction.confirmedStatus == false && + _transaction.isConfirmed( + currentHeight, + coin.requiredConfirmations, + ) == + false && _transaction.isCancelled == false && - _transaction.txType == "Sent") - ? SizedBox( - width: MediaQuery.of(context).size.width - 32, - child: TextButton( - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all( - Theme.of(context).extension()!.textError, - ), + _transaction.type == TransactionType.outgoing) + ? ConditionalParent( + condition: isDesktop, + builder: (child) => Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + vertical: 16, ), - onPressed: () async { - final Manager manager = ref - .read(walletsChangeNotifierProvider) - .getManager(walletId); + child: child, + ), + child: SizedBox( + width: MediaQuery.of(context).size.width - 32, + child: TextButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + Theme.of(context).extension()!.textError, + ), + ), + onPressed: () async { + final Manager manager = ref + .read(walletsChangeNotifierProvider) + .getManager(walletId); - if (manager.wallet is EpicCashWallet) { - final String? id = _transaction.slateId; - if (id == null) { + if (manager.wallet is EpicCashWallet) { + final String? id = _transaction.slateId; + if (id == null) { + unawaited(showFloatingFlushBar( + type: FlushBarType.warning, + message: "Could not find Epic transaction ID", + context: context, + )); + return; + } + + unawaited(showDialog( + barrierDismissible: false, + context: context, + builder: (_) => + const CancellingTransactionProgressDialog(), + )); + + final result = await (manager.wallet as EpicCashWallet) + .cancelPendingTransactionAndPost(id); + if (mounted) { + // pop progress dialog + Navigator.of(context).pop(); + + if (result.isEmpty) { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Transaction cancelled", + onOkPressed: (_) { + manager.refresh(); + Navigator.of(context).popUntil( + ModalRoute.withName( + WalletView.routeName)); + }, + ), + ); + } else { + await showDialog( + context: context, + builder: (_) => StackOkDialog( + title: "Failed to cancel transaction", + message: result, + ), + ); + } + } + } else { unawaited(showFloatingFlushBar( type: FlushBarType.warning, - message: "Could not find Epic transaction ID", + message: "ERROR: Wallet type is not Epic Cash", context: context, )); return; } - - unawaited(showDialog( - barrierDismissible: false, - context: context, - builder: (_) => - const CancellingTransactionProgressDialog(), - )); - - final result = await (manager.wallet as EpicCashWallet) - .cancelPendingTransactionAndPost(id); - if (mounted) { - // pop progress dialog - Navigator.of(context).pop(); - - if (result.isEmpty) { - await showDialog( - context: context, - builder: (_) => StackOkDialog( - title: "Transaction cancelled", - onOkPressed: (_) { - manager.refresh(); - Navigator.of(context).popUntil( - ModalRoute.withName(WalletView.routeName)); - }, - ), - ); - } else { - await showDialog( - context: context, - builder: (_) => StackOkDialog( - title: "Failed to cancel transaction", - message: result, - ), - ); - } - } - } else { - unawaited(showFloatingFlushBar( - type: FlushBarType.warning, - message: "ERROR: Wallet type is not Epic Cash", - context: context, - )); - return; - } - }, - child: Text( - "Cancel Transaction", - style: STextStyles.button(context), + }, + child: Text( + "Cancel Transaction", + style: STextStyles.button(context), + ), ), ), ) @@ -1415,13 +1548,15 @@ class IconCopyButton extends StatelessWidget { ), onPressed: () async { await Clipboard.setData(ClipboardData(text: data)); - unawaited( - showFloatingFlushBar( - type: FlushBarType.info, - message: "Copied to clipboard", - context: context, - ), - ); + if (context.mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + context: context, + ), + ); + } }, child: Padding( padding: const EdgeInsets.all(5), diff --git a/lib/pages/wallet_view/transaction_views/transaction_search_filter_view.dart b/lib/pages/wallet_view/transaction_views/transaction_search_filter_view.dart index 7e1b53cbb..6ca10deee 100644 --- a/lib/pages/wallet_view/transaction_views/transaction_search_filter_view.dart +++ b/lib/pages/wallet_view/transaction_views/transaction_search_filter_view.dart @@ -6,14 +6,15 @@ import 'package:flutter_rounded_date_picker/flutter_rounded_date_picker.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/models/transaction_filter.dart'; import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/providers/ui/color_theme_provider.dart'; import 'package:stackwallet/providers/ui/transaction_filter_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; @@ -60,7 +61,7 @@ class _TransactionSearchViewState @override initState() { - baseColor = ref.read(colorThemeProvider.state).state.textSubtitle2; + baseColor = ref.read(themeProvider.state).state.textSubtitle2; final filterState = ref.read(transactionFilterProvider.state).state; if (filterState != null) { _isActiveReceivedCheckbox = filterState.received; @@ -75,13 +76,12 @@ class _TransactionSearchViewState _toDateString = _selectedToDate == null ? "" : Format.formatDate(_selectedToDate!); - // TODO: Fix XMR (modify Format.funcs to take optional Coin parameter) - // final amt = Format.satoshisToAmount(widget.coin == Coin.monero ? ) - String amount = ""; - if (filterState.amount != null) { - amount = Format.satoshiAmountToPrettyString(filterState.amount!, - ref.read(localeServiceChangeNotifierProvider).locale, widget.coin); - } + final String amount = filterState.amount?.localizedStringAsFixed( + locale: ref.read(localeServiceChangeNotifierProvider).locale, + decimalPlaces: widget.coin.decimals, + ) ?? + ""; + _amountTextEditingController.text = amount; } @@ -739,10 +739,12 @@ class _TransactionSearchViewState controller: _amountTextEditingController, focusNode: amountTextFieldFocusNode, onChanged: (_) => setState(() {}), - keyboardType: const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), + keyboardType: Util.isDesktop + ? null + : const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), inputFormatters: [ // regex to validate a crypto amount with 8 decimal places TextInputFormatter.withFunction((oldValue, newValue) => @@ -964,15 +966,13 @@ class _TransactionSearchViewState Future _onApplyPressed() async { final amountText = _amountTextEditingController.text; - Decimal? amountDecimal; + Amount? amount; if (amountText.isNotEmpty && !(amountText == "," || amountText == ".")) { - amountDecimal = amountText.contains(",") + amount = amountText.contains(",") ? Decimal.parse(amountText.replaceFirst(",", ".")) - : Decimal.parse(amountText); - } - int? amount; - if (amountDecimal != null) { - amount = Format.decimalAmountToSatoshis(amountDecimal, widget.coin); + .toAmount(fractionDigits: widget.coin.decimals) + : Decimal.parse(amountText) + .toAmount(fractionDigits: widget.coin.decimals); } final TransactionFilter filter = TransactionFilter( diff --git a/lib/pages/wallet_view/wallet_view.dart b/lib/pages/wallet_view/wallet_view.dart index 4b415074c..ea4804a8a 100644 --- a/lib/pages/wallet_view/wallet_view.dart +++ b/lib/pages/wallet_view/wallet_view.dart @@ -1,27 +1,34 @@ import 'dart:async'; +import 'dart:io'; -import 'package:decimal/decimal.dart'; import 'package:event_bus/event_bus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/models/isar/exchange_cache/currency.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; -import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; +import 'package:stackwallet/pages/buy_view/buy_in_wallet_view.dart'; +import 'package:stackwallet/pages/coin_control/coin_control_view.dart'; import 'package:stackwallet/pages/exchange_view/wallet_initiated_exchange_view.dart'; import 'package:stackwallet/pages/home_view/home_view.dart'; import 'package:stackwallet/pages/notification_views/notifications_view.dart'; +import 'package:stackwallet/pages/paynym/paynym_claim_view.dart'; +import 'package:stackwallet/pages/paynym/paynym_home_view.dart'; import 'package:stackwallet/pages/receive_view/receive_view.dart'; import 'package:stackwallet/pages/send_view/send_view.dart'; import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart'; import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_view.dart'; +import 'package:stackwallet/pages/token_view/my_tokens_view.dart'; import 'package:stackwallet/pages/wallet_view/sub_widgets/transactions_list.dart'; -import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_navigation_bar.dart'; import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_summary.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/all_transactions_view.dart'; import 'package:stackwallet/providers/global/auto_swb_service_provider.dart'; +import 'package:stackwallet/providers/global/paynym_api_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/ui/transaction_filter_provider.dart'; import 'package:stackwallet/providers/ui/unread_notifications_provider.dart'; +import 'package:stackwallet/providers/wallet/my_paynym_account_state_provider.dart'; import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart'; import 'package:stackwallet/providers/wallet/wallet_balance_toggle_state_provider.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; @@ -29,22 +36,37 @@ import 'package:stackwallet/services/coins/manager.dart'; import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/global_event_bus.dart'; -import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart'; +import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart'; import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/show_loading.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/custom_loading_overlay.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/loading_indicator.dart'; import 'package:stackwallet/widgets/stack_dialog.dart'; +import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/buy_nav_icon.dart'; +import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/coin_control_nav_icon.dart'; +import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/exchange_nav_icon.dart'; +import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/paynym_nav_icon.dart'; +import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/receive_nav_icon.dart'; +import 'package:stackwallet/widgets/wallet_navigation_bar/components/icons/send_nav_icon.dart'; +import 'package:stackwallet/widgets/wallet_navigation_bar/components/wallet_navigation_bar_item.dart'; +import 'package:stackwallet/widgets/wallet_navigation_bar/wallet_navigation_bar.dart'; import 'package:tuple/tuple.dart'; /// [eventBus] should only be set during testing @@ -54,6 +76,7 @@ class WalletView extends ConsumerStatefulWidget { required this.walletId, required this.managerProvider, this.eventBus, + this.clipboardInterface = const ClipboardWrapper(), }) : super(key: key); static const String routeName = "/wallet"; @@ -63,6 +86,8 @@ class WalletView extends ConsumerStatefulWidget { final ChangeNotifierProvider managerProvider; final EventBus? eventBus; + final ClipboardInterface clipboardInterface; + @override ConsumerState createState() => _WalletViewState(); } @@ -80,7 +105,7 @@ class _WalletViewState extends ConsumerState { late StreamSubscription _syncStatusSubscription; late StreamSubscription _nodeStatusSubscription; - final _cnLoadingService = ExchangeDataLoadingService(); + bool _rescanningOnOpen = false; @override void initState() { @@ -96,7 +121,18 @@ class _WalletViewState extends ConsumerState { _shouldDisableAutoSyncOnLogOut = false; } - ref.read(managerProvider).refresh(); + if (ref.read(managerProvider).rescanOnOpenVersion == Constants.rescanV1) { + _rescanningOnOpen = true; + ref.read(managerProvider).fullRescan(20, 1000).then( + (_) => ref.read(managerProvider).resetRescanOnOpen().then( + (_) => WidgetsBinding.instance.addPostFrameCallback( + (_) => setState(() => _rescanningOnOpen = false), + ), + ), + ); + } else { + ref.read(managerProvider).refresh(); + } if (ref.read(managerProvider).isRefreshing) { _currentSyncStatus = WalletSyncStatus.syncing; @@ -231,18 +267,9 @@ class _WalletViewState extends ConsumerState { } void _onExchangePressed(BuildContext context) async { - unawaited(_cnLoadingService.loadAll(ref)); + final Coin coin = ref.read(managerProvider).coin; - final coin = ref.read(managerProvider).coin; - - if (coin == Coin.epicCash) { - await showDialog( - context: context, - builder: (_) => const StackOkDialog( - title: "Exchange not available for Epic Cash", - ), - ); - } else if (coin.name.endsWith("TestNet")) { + if (coin.isTestNet) { await showDialog( context: context, builder: (_) => const StackOkDialog( @@ -250,44 +277,33 @@ class _WalletViewState extends ConsumerState { ), ); } else { - ref.read(currentExchangeNameStateProvider.state).state = - ChangeNowExchange.exchangeName; - final walletId = ref.read(managerProvider).walletId; - ref.read(prefsChangeNotifierProvider).exchangeRateType = - ExchangeRateType.estimated; - - ref.read(exchangeFormStateProvider).exchange = ref.read(exchangeProvider); - ref.read(exchangeFormStateProvider).exchangeType = - ExchangeRateType.estimated; - - final currencies = ref - .read(availableChangeNowCurrenciesProvider) - .currencies - .where((element) => - element.ticker.toLowerCase() == coin.ticker.toLowerCase()); - - if (currencies.isNotEmpty) { - ref.read(exchangeFormStateProvider).setCurrencies( - currencies.first, - ref - .read(availableChangeNowCurrenciesProvider) - .currencies - .firstWhere( - (element) => - element.ticker.toLowerCase() != - coin.ticker.toLowerCase(), - ), - ); + Future _future; + try { + _future = ExchangeDataLoadingService.instance.isar.currencies + .where() + .tickerEqualToAnyExchangeNameName(coin.ticker) + .findFirst(); + } catch (_) { + _future = ExchangeDataLoadingService.instance.loadAll().then((_) => + ExchangeDataLoadingService.instance.isar.currencies + .where() + .tickerEqualToAnyExchangeNameName(coin.ticker) + .findFirst()); } + final currency = await showLoading( + whileFuture: _future, + context: context, + message: "Loading...", + ); + if (mounted) { unawaited( Navigator.of(context).pushNamed( WalletInitiatedExchangeView.routeName, - arguments: Tuple3( + arguments: Tuple2( walletId, - coin, - _loadCNData, + currency == null ? Coin.bitcoin : coin, ), ), ); @@ -295,6 +311,28 @@ class _WalletViewState extends ConsumerState { } } + void _onBuyPressed(BuildContext context) async { + final coin = ref.read(managerProvider).coin; + + if (coin.isTestNet) { + await showDialog( + context: context, + builder: (_) => const StackOkDialog( + title: "Buy not available for test net coins", + ), + ); + } else { + if (mounted) { + unawaited( + Navigator.of(context).pushNamed( + BuyInWalletView.routeName, + arguments: coin.hasBuySupport ? coin : Coin.bitcoin, + ), + ); + } + } + } + Future attemptAnonymize() async { bool shouldPop = false; unawaited( @@ -311,8 +349,8 @@ class _WalletViewState extends ConsumerState { ); final firoWallet = ref.read(managerProvider).wallet as FiroWallet; - final publicBalance = await firoWallet.availablePublicBalance(); - if (publicBalance <= Decimal.zero) { + final Amount publicBalance = firoWallet.availablePublicBalance(); + if (publicBalance <= Amount.zero) { shouldPop = true; if (mounted) { Navigator.of(context).popUntil( @@ -361,316 +399,389 @@ class _WalletViewState extends ConsumerState { } } - void _loadCNData() { - // unawaited future - if (ref.read(prefsChangeNotifierProvider).externalCalls) { - _cnLoadingService.loadAll(ref, coin: ref.read(managerProvider).coin); - } else { - Logging.instance.log("User does not want to use external calls", - level: LogLevel.Info); - } - } - @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - final coin = ref.watch(managerProvider.select((value) => value.coin)); + final Coin coin = ref.watch(managerProvider.select((value) => value.coin)); - return WillPopScope( - onWillPop: _onWillPop, - child: Background( - child: Scaffold( - backgroundColor: - Theme.of(context).extension()!.background, - appBar: AppBar( - leading: AppBarBackButton( - onPressed: () { - _logout(); - Navigator.of(context).pop(); - }, - ), - titleSpacing: 0, - title: Row( - children: [ - SvgPicture.asset( - Assets.svg.iconFor(coin: coin), - // color: Theme.of(context).extension()!.accentColorDark - width: 24, - height: 24, - ), - const SizedBox( - width: 16, - ), - Expanded( - child: Text( - ref.watch( - managerProvider.select((value) => value.walletName)), - style: STextStyles.navBarTitle(context), - overflow: TextOverflow.ellipsis, - ), - ) - ], - ), - actions: [ - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, - ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("walletViewRadioButton"), - size: 36, - shadows: const [], - color: - Theme.of(context).extension()!.background, - icon: _buildNetworkIcon(_currentSyncStatus), - onPressed: () { - Navigator.of(context).pushNamed( - WalletNetworkSettingsView.routeName, - arguments: Tuple3( - walletId, - _currentSyncStatus, - _currentNodeStatus, + return ConditionalParent( + condition: _rescanningOnOpen, + builder: (child) { + return WillPopScope( + onWillPop: () async => !_rescanningOnOpen, + child: Stack( + children: [ + child, + Background( + child: CustomLoadingOverlay( + message: + "Migration in progress\nThis could take a while\nPlease don't leave this screen", + subMessage: "This only needs to run once per wallet", + eventBus: null, + textColor: + Theme.of(context).extension()!.textDark, + actionButton: SecondaryButton( + label: "Cancel", + onPressed: () async { + await showDialog( + context: context, + builder: (context) => StackDialog( + title: "Warning!", + message: "Skipping this process can completely" + " break your wallet. It is only meant to be done in" + " emergency situations where the migration fails" + " and will not let you continue. Still skip?", + leftButton: SecondaryButton( + label: "Cancel", + onPressed: + Navigator.of(context, rootNavigator: true).pop, + ), + rightButton: SecondaryButton( + label: "Ok", + onPressed: () { + Navigator.of(context, rootNavigator: true).pop(); + setState(() => _rescanningOnOpen = false); + }, + ), ), ); }, ), ), - ), - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, - ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("walletViewAlertsButton"), - size: 36, - shadows: const [], - color: - Theme.of(context).extension()!.background, - icon: SvgPicture.asset( - ref.watch(notificationsProvider.select((value) => - value.hasUnreadNotificationsFor(walletId))) - ? Assets.svg.bellNew(context) - : Assets.svg.bell, - width: 20, - height: 20, - color: ref.watch(notificationsProvider.select((value) => - value.hasUnreadNotificationsFor(walletId))) - ? null - : Theme.of(context) - .extension()! - .topNavIconPrimary, - ), - onPressed: () { - // reset unread state - ref.refresh(unreadNotificationsStateProvider); - - Navigator.of(context) - .pushNamed( - NotificationsView.routeName, - arguments: walletId, - ) - .then((_) { - final Set unreadNotificationIds = ref - .read(unreadNotificationsStateProvider.state) - .state; - if (unreadNotificationIds.isEmpty) return; - - List> futures = []; - for (int i = 0; - i < unreadNotificationIds.length - 1; - i++) { - futures.add(ref - .read(notificationsProvider) - .markAsRead( - unreadNotificationIds.elementAt(i), false)); - } - - // wait for multiple to update if any - Future.wait(futures).then((_) { - // only notify listeners once - ref - .read(notificationsProvider) - .markAsRead(unreadNotificationIds.last, true); - }); - }); - }, - ), - ), - ), - Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - right: 10, - ), - child: AspectRatio( - aspectRatio: 1, - child: AppBarIconButton( - key: const Key("walletViewSettingsButton"), - size: 36, - shadows: const [], - color: - Theme.of(context).extension()!.background, - icon: SvgPicture.asset( - Assets.svg.bars, - color: Theme.of(context) - .extension()! - .accentColorDark, - width: 20, - height: 20, - ), - onPressed: () { - //todo: check if print needed - // debugPrint("wallet view settings tapped"); - Navigator.of(context).pushNamed( - WalletSettingsView.routeName, - arguments: Tuple4( - walletId, - ref.read(managerProvider).coin, - _currentSyncStatus, - _currentNodeStatus, - ), - ); - }, - ), - ), - ), + ) ], ), - body: SafeArea( - child: Container( - color: Theme.of(context).extension()!.background, - child: Column( - children: [ - const SizedBox( - height: 10, + ); + }, + child: WillPopScope( + onWillPop: _onWillPop, + child: Background( + child: Stack( + children: [ + Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + _logout(); + Navigator.of(context).pop(); + }, ), - Center( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: WalletSummary( - walletId: walletId, - managerProvider: managerProvider, - initialSyncStatus: ref.watch(managerProvider - .select((value) => value.isRefreshing)) - ? WalletSyncStatus.syncing - : WalletSyncStatus.synced, - ), - ), - ), - if (coin == Coin.firo) - const SizedBox( - height: 10, - ), - if (coin == Coin.firo) - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Row( - children: [ - Expanded( - child: TextButton( - style: Theme.of(context) - .extension()! - .getSecondaryEnabledButtonColor(context), - onPressed: () async { - await showDialog( - context: context, - builder: (context) => StackDialog( - title: "Attention!", - message: - "You're about to anonymize all of your public funds.", - leftButton: TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text( - "Cancel", - style: STextStyles.button(context) - .copyWith( - color: Theme.of(context) - .extension()! - .accentColorDark, - ), - ), - ), - rightButton: TextButton( - onPressed: () async { - Navigator.of(context).pop(); - - unawaited(attemptAnonymize()); - }, - style: Theme.of(context) - .extension()! - .getPrimaryEnabledButtonColor( - context), - child: Text( - "Continue", - style: STextStyles.button(context), - ), - ), - ), - ); - }, - child: Text( - "Anonymize funds", - style: STextStyles.button(context).copyWith( - color: Theme.of(context) - .extension()! - .buttonTextSecondary, - ), - ), - ), - ), - ], - ), - ), - const SizedBox( - height: 20, - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Transactions", - style: STextStyles.itemSubtitle(context).copyWith( - color: Theme.of(context) - .extension()! - .textDark3, - ), + titleSpacing: 0, + title: Row( + children: [ + SvgPicture.file( + File( + ref.watch(coinIconProvider(coin)), ), - BlueTextButton( - text: "See all", - onTap: () { + width: 24, + height: 24, + ), + const SizedBox( + width: 16, + ), + Expanded( + child: Text( + ref.watch(managerProvider + .select((value) => value.walletName)), + style: STextStyles.navBarTitle(context), + overflow: TextOverflow.ellipsis, + ), + ) + ], + ), + actions: [ + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + semanticsLabel: + "Network Button. Takes To Network Status Page.", + key: const Key("walletViewRadioButton"), + size: 36, + shadows: const [], + color: Theme.of(context) + .extension()! + .background, + icon: _buildNetworkIcon(_currentSyncStatus), + onPressed: () { Navigator.of(context).pushNamed( - AllTransactionsView.routeName, - arguments: walletId, + WalletNetworkSettingsView.routeName, + arguments: Tuple3( + walletId, + _currentSyncStatus, + _currentNodeStatus, + ), ); }, ), - ], + ), ), - ), - const SizedBox( - height: 12, - ), - Expanded( - child: Stack( + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + semanticsLabel: + "Notifications Button. Takes To Notifications Page.", + key: const Key("walletViewAlertsButton"), + size: 36, + shadows: const [], + color: Theme.of(context) + .extension()! + .background, + icon: ref.watch(notificationsProvider.select( + (value) => value + .hasUnreadNotificationsFor(walletId))) + ? SvgPicture.file( + File( + ref.watch( + themeProvider.select( + (value) => value.assets.bellNew, + ), + ), + ), + width: 20, + height: 20, + color: ref.watch(notificationsProvider.select( + (value) => + value.hasUnreadNotificationsFor( + walletId))) + ? null + : Theme.of(context) + .extension()! + .topNavIconPrimary, + ) + : SvgPicture.asset( + Assets.svg.bell, + width: 20, + height: 20, + color: ref.watch(notificationsProvider.select( + (value) => + value.hasUnreadNotificationsFor( + walletId))) + ? null + : Theme.of(context) + .extension()! + .topNavIconPrimary, + ), + onPressed: () { + // reset unread state + ref.refresh(unreadNotificationsStateProvider); + + Navigator.of(context) + .pushNamed( + NotificationsView.routeName, + arguments: walletId, + ) + .then((_) { + final Set unreadNotificationIds = ref + .read(unreadNotificationsStateProvider.state) + .state; + if (unreadNotificationIds.isEmpty) return; + + List> futures = []; + for (int i = 0; + i < unreadNotificationIds.length - 1; + i++) { + futures.add(ref + .read(notificationsProvider) + .markAsRead( + unreadNotificationIds.elementAt(i), + false)); + } + + // wait for multiple to update if any + Future.wait(futures).then((_) { + // only notify listeners once + ref.read(notificationsProvider).markAsRead( + unreadNotificationIds.last, true); + }); + }); + }, + ), + ), + ), + Padding( + padding: const EdgeInsets.only( + top: 10, + bottom: 10, + right: 10, + ), + child: AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + semanticsLabel: + "Settings Button. Takes To Wallet Settings Page.", + key: const Key("walletViewSettingsButton"), + size: 36, + shadows: const [], + color: Theme.of(context) + .extension()! + .background, + icon: SvgPicture.asset( + Assets.svg.bars, + color: Theme.of(context) + .extension()! + .accentColorDark, + width: 20, + height: 20, + ), + onPressed: () { + //todo: check if print needed + // debugPrint("wallet view settings tapped"); + Navigator.of(context).pushNamed( + WalletSettingsView.routeName, + arguments: Tuple4( + walletId, + ref.read(managerProvider).coin, + _currentSyncStatus, + _currentNodeStatus, + ), + ); + }, + ), + ), + ), + ], + ), + body: SafeArea( + child: Container( + color: + Theme.of(context).extension()!.background, + child: Column( children: [ + const SizedBox( + height: 10, + ), + Center( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: WalletSummary( + walletId: walletId, + managerProvider: managerProvider, + initialSyncStatus: ref.watch(managerProvider + .select((value) => value.isRefreshing)) + ? WalletSyncStatus.syncing + : WalletSyncStatus.synced, + ), + ), + ), + if (coin == Coin.firo) + const SizedBox( + height: 10, + ), + if (coin == Coin.firo) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + children: [ + Expanded( + child: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle( + context), + onPressed: () async { + await showDialog( + context: context, + builder: (context) => StackDialog( + title: "Attention!", + message: + "You're about to anonymize all of your public funds.", + leftButton: TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text( + "Cancel", + style: STextStyles.button(context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark, + ), + ), + ), + rightButton: TextButton( + onPressed: () async { + Navigator.of(context).pop(); + + unawaited(attemptAnonymize()); + }, + style: Theme.of(context) + .extension()! + .getPrimaryEnabledButtonStyle( + context), + child: Text( + "Continue", + style: + STextStyles.button(context), + ), + ), + ), + ); + }, + child: Text( + "Anonymize funds", + style: + STextStyles.button(context).copyWith( + color: Theme.of(context) + .extension()! + .buttonTextSecondary, + ), + ), + ), + ), + ], + ), + ), + const SizedBox( + height: 20, + ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Transactions", + style: + STextStyles.itemSubtitle(context).copyWith( + color: Theme.of(context) + .extension()! + .textDark3, + ), + ), + CustomTextButton( + text: "See all", + onTap: () { + Navigator.of(context).pushNamed( + AllTransactionsView.routeName, + arguments: walletId, + ); + }, + ), + ], + ), + ), + const SizedBox( + height: 12, + ), + Expanded( child: Padding( - padding: const EdgeInsets.only(bottom: 14), + padding: const EdgeInsets.symmetric(horizontal: 16), child: ClipRRect( borderRadius: BorderRadius.vertical( top: Radius.circular( @@ -681,115 +792,214 @@ class _WalletViewState extends ConsumerState { Constants.size.circularBorderRadius, ), ), - child: Container( - decoration: BoxDecoration( - color: Colors.transparent, - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - child: Column( - crossAxisAlignment: - CrossAxisAlignment.stretch, - children: [ - Expanded( - child: TransactionsList( - managerProvider: managerProvider, - walletId: walletId, - ), + child: ShaderMask( + blendMode: BlendMode.dstOut, + shaderCallback: (Rect bounds) { + return const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.transparent, + Colors.transparent, + Colors.white, + ], + stops: [ + 0.0, + 0.8, + 1.0, + ], // 10% purple, 80% transparent, 10% purple + ).createShader(bounds); + }, + child: Container( + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), - ], + ), + child: Column( + crossAxisAlignment: + CrossAxisAlignment.stretch, + children: [ + Expanded( + child: TransactionsList( + managerProvider: managerProvider, + walletId: walletId, + ), + ), + ], + ), ), ), ), ), ), - Column( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - const Spacer(), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.only( - bottom: 14, - left: 16, - right: 16, - ), - child: SizedBox( - height: WalletView.navBarHeight, - child: WalletNavigationBar( - enableExchange: - Constants.enableExchange && - ref.watch(managerProvider.select( - (value) => value.coin)) != - Coin.epicCash, - height: WalletView.navBarHeight, - onExchangePressed: () => - _onExchangePressed(context), - onReceivePressed: () async { - final coin = - ref.read(managerProvider).coin; - if (mounted) { - unawaited( - Navigator.of(context).pushNamed( - ReceiveView.routeName, - arguments: Tuple2( - walletId, - coin, - ), - )); - } - }, - onSendPressed: () { - final walletId = - ref.read(managerProvider).walletId; - final coin = - ref.read(managerProvider).coin; - switch (ref - .read( - walletBalanceToggleStateProvider - .state) - .state) { - case WalletBalanceToggleState.full: - ref - .read( - publicPrivateBalanceStateProvider - .state) - .state = "Public"; - break; - case WalletBalanceToggleState - .available: - ref - .read( - publicPrivateBalanceStateProvider - .state) - .state = "Private"; - break; - } - Navigator.of(context).pushNamed( - SendView.routeName, - arguments: Tuple2( - walletId, - coin, - ), - ); - }, - onBuyPressed: () {}, - ), - ), - ), - ], - ), - ], - ) ], ), ), + ), + ), + WalletNavigationBar( + items: [ + WalletNavigationBarItemData( + label: "Receive", + icon: const ReceiveNavIcon(), + onTap: () { + if (mounted) { + unawaited( + Navigator.of(context).pushNamed( + ReceiveView.routeName, + arguments: walletId, + ), + ); + } + }, + ), + WalletNavigationBarItemData( + label: "Send", + icon: const SendNavIcon(), + onTap: () { + final walletId = ref.read(managerProvider).walletId; + final coin = ref.read(managerProvider).coin; + switch (ref + .read(walletBalanceToggleStateProvider.state) + .state) { + case WalletBalanceToggleState.full: + ref + .read(publicPrivateBalanceStateProvider.state) + .state = "Public"; + break; + case WalletBalanceToggleState.available: + ref + .read(publicPrivateBalanceStateProvider.state) + .state = "Private"; + break; + } + Navigator.of(context).pushNamed( + SendView.routeName, + arguments: Tuple2( + walletId, + coin, + ), + ); + }, + ), + if (Constants.enableExchange) + WalletNavigationBarItemData( + label: "Swap", + icon: const ExchangeNavIcon(), + onTap: () => _onExchangePressed(context), + ), + if (Constants.enableExchange) + WalletNavigationBarItemData( + label: "Buy", + icon: const BuyNavIcon(), + onTap: () => _onBuyPressed(context), + ), + ], + moreItems: [ + if (ref.watch( + walletsChangeNotifierProvider.select( + (value) => + value.getManager(widget.walletId).hasTokenSupport, + ), + )) + WalletNavigationBarItemData( + label: "Tokens", + icon: const CoinControlNavIcon(), + onTap: () { + Navigator.of(context).pushNamed( + MyTokensView.routeName, + arguments: walletId, + ); + }, + ), + if (ref.watch( + walletsChangeNotifierProvider.select( + (value) => value + .getManager(widget.walletId) + .hasCoinControlSupport, + ), + ) && + ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.enableCoinControl, + ), + )) + WalletNavigationBarItemData( + label: "Coin control", + icon: const CoinControlNavIcon(), + onTap: () { + Navigator.of(context).pushNamed( + CoinControlView.routeName, + arguments: Tuple2( + widget.walletId, + CoinControlViewType.manage, + ), + ); + }, + ), + if (ref.watch(walletsChangeNotifierProvider.select((value) => + value.getManager(widget.walletId).hasPaynymSupport))) + WalletNavigationBarItemData( + label: "PayNym", + icon: const PaynymNavIcon(), + onTap: () async { + unawaited( + showDialog( + context: context, + builder: (context) => const LoadingIndicator( + width: 100, + ), + ), + ); + + final manager = ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId); + + final paynymInterface = + manager.wallet as PaynymWalletInterface; + + final code = await paynymInterface.getPaymentCode( + isSegwit: false, + ); + + final account = await ref + .read(paynymAPIProvider) + .nym(code.toString()); + + Logging.instance.log( + "my nym account: $account", + level: LogLevel.Info, + ); + + if (mounted) { + Navigator.of(context).pop(); + + // check if account exists and for matching code to see if claimed + if (account.value != null && + account.value!.nonSegwitPaymentCode.claimed && + account.value!.segwit) { + ref.read(myPaynymAccountStateProvider.state).state = + account.value!; + + await Navigator.of(context).pushNamed( + PaynymHomeView.routeName, + arguments: widget.walletId, + ); + } else { + await Navigator.of(context).pushNamed( + PaynymClaimView.routeName, + arguments: widget.walletId, + ); + } + } + }, + ), ], ), - ), + ], ), ), ), diff --git a/lib/pages/wallets_sheet/wallets_sheet.dart b/lib/pages/wallets_sheet/wallets_sheet.dart deleted file mode 100644 index b69971ab5..000000000 --- a/lib/pages/wallets_sheet/wallets_sheet.dart +++ /dev/null @@ -1,96 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; -import 'package:stackwallet/widgets/wallet_card.dart'; - -class WalletsSheet extends ConsumerWidget { - const WalletsSheet({ - Key? key, - required this.coin, - }) : super(key: key); - - final Coin coin; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final providers = ref.watch(walletsChangeNotifierProvider - .select((value) => value.getManagerProvidersByCoin()))[coin]; - - final maxHeight = MediaQuery.of(context).size.height * 0.60; - - return Container( - decoration: BoxDecoration( - color: Theme.of(context).extension()!.popupBG, - borderRadius: const BorderRadius.vertical( - top: Radius.circular(20), - ), - ), - child: LimitedBox( - maxHeight: maxHeight, - child: Padding( - padding: const EdgeInsets.only( - left: 24, - right: 24, - top: 10, - bottom: 0, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Center( - child: Container( - decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - width: 60, - height: 4, - ), - ), - const SizedBox( - height: 36, - ), - Text( - "${coin.prettyName} (${coin.ticker}) wallets", - style: STextStyles.pageTitleH2(context), - textAlign: TextAlign.left, - ), - const SizedBox( - height: 16, - ), - Flexible( - child: ListView.builder( - shrinkWrap: true, - itemCount: providers!.length, - itemBuilder: (builderContext, index) { - final walletId = ref.watch( - providers[index].select((value) => value.walletId)); - return Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: WalletSheetCard( - walletId: walletId, - popPrevious: true, - ), - ); - }, - ), - ), - const SizedBox( - height: 24, - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/pages/wallets_view/sub_widgets/all_wallets.dart b/lib/pages/wallets_view/sub_widgets/all_wallets.dart index eae7374bf..3f91a739c 100644 --- a/lib/pages/wallets_view/sub_widgets/all_wallets.dart +++ b/lib/pages/wallets_view/sub_widgets/all_wallets.dart @@ -3,8 +3,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart'; import 'package:stackwallet/pages/wallets_view/sub_widgets/wallet_list_item.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; class AllWallets extends StatelessWidget { @@ -21,10 +21,10 @@ class AllWallets extends StatelessWidget { Text( "All wallets", style: STextStyles.itemSubtitle(context).copyWith( - color: Theme.of(context).extension()!.textDark, + color: Theme.of(context).extension()!.textDark3, ), ), - BlueTextButton( + CustomTextButton( text: "Add new", onTap: () { Navigator.of(context).pushNamed(AddWalletView.routeName); @@ -44,9 +44,8 @@ class AllWallets extends StatelessWidget { return ListView.builder( itemCount: providersByCoin.length, itemBuilder: (builderContext, index) { - final coin = - providersByCoin.keys.toList(growable: false)[index]; - final int walletCount = providersByCoin[coin]!.length; + final coin = providersByCoin[index].item1; + final int walletCount = providersByCoin[index].item2.length; return Padding( padding: const EdgeInsets.symmetric(vertical: 4), child: WalletListItem( diff --git a/lib/pages/wallets_view/sub_widgets/empty_wallets.dart b/lib/pages/wallets_view/sub_widgets/empty_wallets.dart index 8691b7d84..23d4686e7 100644 --- a/lib/pages/wallets_view/sub_widgets/empty_wallets.dart +++ b/lib/pages/wallets_view/sub_widgets/empty_wallets.dart @@ -1,16 +1,20 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; -class EmptyWallets extends StatelessWidget { +class EmptyWallets extends ConsumerWidget { const EmptyWallets({Key? key}) : super(key: key); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { debugPrint("BUILD: $runtimeType"); final isDesktop = Util.isDesktop; @@ -29,9 +33,13 @@ class EmptyWallets extends StatelessWidget { const Spacer( flex: 2, ), - Image( - image: AssetImage( - Assets.png.stack, + SvgPicture.file( + File( + ref.watch( + themeProvider.select( + (value) => value.assets.stack, + ), + ), ), width: isDesktop ? 324 : MediaQuery.of(context).size.width / 3, ), @@ -84,17 +92,19 @@ class EmptyWallets extends StatelessWidget { } } -class AddWalletButton extends StatelessWidget { +class AddWalletButton extends ConsumerWidget { const AddWalletButton({Key? key, required this.isDesktop}) : super(key: key); final bool isDesktop; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final bool isOLED = ref.watch(themeProvider).themeId == "oled_black"; + return TextButton( style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), onPressed: () { if (isDesktop) { Navigator.of( @@ -113,11 +123,20 @@ class AddWalletButton extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - SvgPicture.asset( - Assets.svg.plus, - width: isDesktop ? 18 : null, - height: isDesktop ? 18 : null, - ), + isOLED + ? SvgPicture.asset( + Assets.svg.plus, + color: Theme.of(context) + .extension()! + .buttonTextPrimary, + width: isDesktop ? 18 : null, + height: isDesktop ? 18 : null, + ) + : SvgPicture.asset( + Assets.svg.plus, + width: isDesktop ? 18 : null, + height: isDesktop ? 18 : null, + ), SizedBox( width: isDesktop ? 8 : 5, ), diff --git a/lib/pages/wallets_view/sub_widgets/favorite_card.dart b/lib/pages/wallets_view/sub_widgets/favorite_card.dart index 9ec13d459..87ffbccd1 100644 --- a/lib/pages/wallets_view/sub_widgets/favorite_card.dart +++ b/lib/pages/wallets_view/sub_widgets/favorite_card.dart @@ -1,4 +1,5 @@ -import 'package:decimal/decimal.dart'; +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; @@ -6,12 +7,13 @@ import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/coins/manager.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:tuple/tuple.dart'; @@ -38,8 +40,8 @@ class _FavoriteCardState extends ConsumerState { late final String walletId; late final ChangeNotifierProvider managerProvider; - Decimal _cachedBalance = Decimal.zero; - Decimal _cachedFiatValue = Decimal.zero; + Amount _cachedBalance = Amount.zero; + Amount _cachedFiatValue = Amount.zero; @override void initState() { @@ -105,20 +107,25 @@ class _FavoriteCardState extends ConsumerState { ), ), child: GestureDetector( - onTap: () { - if (Util.isDesktop) { - Navigator.of(context).pushNamed( - DesktopWalletView.routeName, - arguments: walletId, - ); - } else { - Navigator.of(context).pushNamed( - WalletView.routeName, - arguments: Tuple2( - walletId, - managerProvider, - ), - ); + onTap: () async { + if (coin == Coin.monero || coin == Coin.wownero) { + await ref.read(managerProvider).initializeExisting(); + } + if (mounted) { + if (Util.isDesktop) { + await Navigator.of(context).pushNamed( + DesktopWalletView.routeName, + arguments: walletId, + ); + } else { + await Navigator.of(context).pushNamed( + WalletView.routeName, + arguments: Tuple2( + walletId, + managerProvider, + ), + ); + } } }, child: SizedBox( @@ -208,8 +215,10 @@ class _FavoriteCardState extends ConsumerState { overflow: TextOverflow.fade, ), ), - SvgPicture.asset( - Assets.svg.iconFor(coin: coin), + SvgPicture.file( + File( + ref.watch(coinIconProvider(coin)), + ), width: 24, height: 24, ), @@ -217,22 +226,24 @@ class _FavoriteCardState extends ConsumerState { ), ), FutureBuilder( - future: ref.watch( - managerProvider.select((value) => value.totalBalance)), - builder: (builderContext, AsyncSnapshot snapshot) { + future: Future(() => ref.watch(managerProvider + .select((value) => value.balance.total))), + builder: (builderContext, AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) { if (snapshot.data != null) { _cachedBalance = snapshot.data!; - if (externalCalls) { - _cachedFiatValue = _cachedBalance * - ref - .watch( - priceAnd24hChangeNotifierProvider.select( - (value) => value.getPrice(coin), - ), - ) - .item1; + if (externalCalls && _cachedBalance > Amount.zero) { + _cachedFiatValue = (_cachedBalance.decimal * + ref + .watch( + priceAnd24hChangeNotifierProvider + .select( + (value) => value.getPrice(coin), + ), + ) + .item1) + .toAmount(fractionDigits: 2); } } } @@ -242,13 +253,13 @@ class _FavoriteCardState extends ConsumerState { FittedBox( fit: BoxFit.scaleDown, child: Text( - "${Format.localizedStringAsFixed( - decimalPlaces: 8, - value: _cachedBalance, + "${_cachedBalance.localizedStringAsFixed( locale: ref.watch( localeServiceChangeNotifierProvider .select((value) => value.locale), ), + decimalPlaces: ref.watch(managerProvider + .select((value) => value.coin.decimals)), )} ${coin.ticker}", style: STextStyles.titleBold12(context).copyWith( fontSize: 16, @@ -264,13 +275,12 @@ class _FavoriteCardState extends ConsumerState { ), if (externalCalls) Text( - "${Format.localizedStringAsFixed( - decimalPlaces: 2, - value: _cachedFiatValue, + "${_cachedFiatValue.localizedStringAsFixed( locale: ref.watch( localeServiceChangeNotifierProvider .select((value) => value.locale), ), + decimalPlaces: 2, )} ${ref.watch( prefsChangeNotifierProvider .select((value) => value.currency), diff --git a/lib/pages/wallets_view/sub_widgets/favorite_wallets.dart b/lib/pages/wallets_view/sub_widgets/favorite_wallets.dart index 073747386..afbea4562 100644 --- a/lib/pages/wallets_view/sub_widgets/favorite_wallets.dart +++ b/lib/pages/wallets_view/sub_widgets/favorite_wallets.dart @@ -7,10 +7,10 @@ import 'package:stackwallet/pages/manage_favorites_view/manage_favorites_view.da import 'package:stackwallet/pages/wallets_view/sub_widgets/favorite_card.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/coins/manager.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_page_view/custom_page_view.dart' as cpv; @@ -106,12 +106,12 @@ class _FavoriteWalletsState extends ConsumerState { ManageFavoritesView.routeName, ); }, - ) + ), ], ), ), const SizedBox( - height: 12, + height: 20, ), !hasFavorites ? Padding( diff --git a/lib/pages/wallets_view/sub_widgets/wallet_list_item.dart b/lib/pages/wallets_view/sub_widgets/wallet_list_item.dart index e3fd5d614..81194fdc3 100644 --- a/lib/pages/wallets_view/sub_widgets/wallet_list_item.dart +++ b/lib/pages/wallets_view/sub_widgets/wallet_list_item.dart @@ -1,16 +1,20 @@ +import 'dart:async'; +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/pages/wallets_sheet/wallets_sheet.dart'; +import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; +import 'package:stackwallet/pages/wallets_view/wallets_overview.dart'; import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/format.dart'; -import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:tuple/tuple.dart'; class WalletListItem extends ConsumerWidget { const WalletListItem({ @@ -41,22 +45,45 @@ class WalletListItem extends ConsumerWidget { borderRadius: BorderRadius.circular(Constants.size.circularBorderRadius), ), - onPressed: () { - showModalBottomSheet( - backgroundColor: Colors.transparent, - context: context, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.vertical( - top: Radius.circular(20), + onPressed: () async { + if (walletCount == 1 && coin != Coin.ethereum) { + final providersByCoin = ref + .watch(walletsChangeNotifierProvider + .select((value) => value.getManagerProvidersByCoin())) + .where((e) => e.item1 == coin) + .map((e) => e.item2) + .expand((e) => e) + .toList(); + final manager = ref.read(providersByCoin.first); + if (coin == Coin.monero || coin == Coin.wownero) { + await manager.initializeExisting(); + } + if (context.mounted) { + unawaited( + Navigator.of(context).pushNamed( + WalletView.routeName, + arguments: Tuple2( + manager.walletId, + providersByCoin.first, + ), + ), + ); + } + } else { + unawaited( + Navigator.of(context).pushNamed( + WalletsOverview.routeName, + arguments: coin, ), - ), - builder: (_) => WalletsSheet(coin: coin), - ); + ); + } }, child: Row( children: [ - SvgPicture.asset( - Assets.svg.iconFor(coin: coin), + SvgPicture.file( + File( + ref.watch(coinIconProvider(coin)), + ), width: 28, height: 28, ), @@ -71,13 +98,12 @@ class WalletListItem extends ConsumerWidget { final calls = ref.watch(prefsChangeNotifierProvider).externalCalls; - final priceString = Format.localizedStringAsFixed( - value: tuple.item1, - locale: ref - .watch(localeServiceChangeNotifierProvider.notifier) - .locale, - decimalPlaces: 2, - ); + final priceString = tuple.item1 + .toAmount(fractionDigits: 2) + .localizedStringAsFixed( + locale: ref.watch(localeServiceChangeNotifierProvider + .select((value) => value.locale)), + ); final double percentChange = tuple.item2; diff --git a/lib/pages/wallets_view/wallets_overview.dart b/lib/pages/wallets_view/wallets_overview.dart new file mode 100644 index 000000000..5cc032a9d --- /dev/null +++ b/lib/pages/wallets_view/wallets_overview.dart @@ -0,0 +1,331 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/add_wallet_list_entity/sub_classes/coin_entity.dart'; +import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; +import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart'; +import 'package:stackwallet/pages_desktop_specific/my_stack_view/dialogs/desktop_expanding_wallet_card.dart'; +import 'package:stackwallet/providers/db/main_db_provider.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart'; +import 'package:stackwallet/services/coins/manager.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/master_wallet_card.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; +import 'package:stackwallet/widgets/wallet_card.dart'; +import 'package:tuple/tuple.dart'; + +class WalletsOverview extends ConsumerStatefulWidget { + const WalletsOverview({ + Key? key, + required this.coin, + this.navigatorState, + }) : super(key: key); + + final Coin coin; + final NavigatorState? navigatorState; + + static const routeName = "/walletsOverview"; + + @override + ConsumerState createState() => _EthWalletsOverviewState(); +} + +class _EthWalletsOverviewState extends ConsumerState { + final isDesktop = Util.isDesktop; + + late final TextEditingController _searchController; + late final FocusNode searchFieldFocusNode; + + String _searchString = ""; + + final List>> wallets = []; + + List>> _filter(String searchTerm) { + if (searchTerm.isEmpty) { + return wallets; + } + + final List>> results = []; + final term = searchTerm.toLowerCase(); + + for (final tuple in wallets) { + bool includeManager = false; + // search wallet name and total balance + includeManager |= _elementContains(tuple.item1.walletName, term); + includeManager |= _elementContains( + tuple.item1.balance.total.decimal.toString(), + term, + ); + + final List contracts = []; + + for (final contract in tuple.item2) { + if (_elementContains(contract.name, term)) { + contracts.add(contract); + } else if (_elementContains(contract.symbol, term)) { + contracts.add(contract); + } else if (_elementContains(contract.type.name, term)) { + contracts.add(contract); + } else if (_elementContains(contract.address, term)) { + contracts.add(contract); + } + } + + if (includeManager || contracts.isNotEmpty) { + results.add(Tuple2(tuple.item1, contracts)); + } + } + + return results; + } + + bool _elementContains(String element, String term) { + return element.toLowerCase().contains(term); + } + + @override + void initState() { + _searchController = TextEditingController(); + searchFieldFocusNode = FocusNode(); + + final walletsData = + ref.read(walletsServiceChangeNotifierProvider).fetchWalletsData(); + walletsData.removeWhere((key, value) => value.coin != widget.coin); + + if (widget.coin == Coin.ethereum) { + for (final data in walletsData.values) { + final List contracts = []; + final manager = + ref.read(walletsChangeNotifierProvider).getManager(data.walletId); + final contractAddresses = (manager.wallet as EthereumWallet) + .getWalletTokenContractAddresses(); + + // fetch each contract + for (final contractAddress in contractAddresses) { + final contract = ref + .read( + mainDBProvider, + ) + .getEthContractSync( + contractAddress, + ); + + // add it to list if it exists in DB + if (contract != null) { + contracts.add(contract); + } + } + + // add tuple to list + wallets.add( + Tuple2( + ref.read(walletsChangeNotifierProvider).getManager( + data.walletId, + ), + contracts, + ), + ); + } + } else { + // add non token wallet tuple to list + for (final data in walletsData.values) { + wallets.add( + Tuple2( + ref.read(walletsChangeNotifierProvider).getManager( + data.walletId, + ), + [], + ), + ); + } + } + + super.initState(); + } + + @override + void dispose() { + _searchController.dispose(); + searchFieldFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ConditionalParent( + condition: !isDesktop, + builder: (child) => Background( + child: Scaffold( + backgroundColor: + Theme.of(context).extension()!.background, + appBar: AppBar( + leading: const AppBarBackButton(), + title: Text( + "${widget.coin.prettyName} (${widget.coin.ticker}) wallets", + style: STextStyles.navBarTitle(context), + ), + actions: [ + AspectRatio( + aspectRatio: 1, + child: AppBarIconButton( + icon: SvgPicture.asset( + Assets.svg.plus, + width: 18, + height: 18, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, + ), + onPressed: () { + Navigator.of(context).pushNamed( + CreateOrRestoreWalletView.routeName, + arguments: CoinEntity(widget.coin), + ); + }, + ), + ), + ], + ), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.all(16), + child: child, + ), + ), + ), + ), + child: Column( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: !isDesktop, + enableSuggestions: !isDesktop, + controller: _searchController, + focusNode: searchFieldFocusNode, + onChanged: (value) { + setState(() { + _searchString = value; + }); + }, + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + decoration: standardInputDecoration( + "Search...", + searchFieldFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + prefixIcon: Padding( + padding: EdgeInsets.symmetric( + horizontal: isDesktop ? 12 : 10, + vertical: isDesktop ? 18 : 16, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: isDesktop ? 20 : 16, + height: isDesktop ? 20 : 16, + ), + ), + suffixIcon: _searchController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchController.text = ""; + _searchString = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), + const SizedBox( + height: 16, + ), + Expanded( + child: Builder( + builder: (context) { + final data = _filter(_searchString); + return ListView.separated( + itemBuilder: (_, index) { + final element = data[index]; + + if (element.item1.hasTokenSupport) { + if (isDesktop) { + return DesktopExpandingWalletCard( + key: Key( + "${element.item1.walletName}_${element.item2.map((e) => e.address).join()}"), + data: element, + navigatorState: widget.navigatorState!, + ); + } else { + return MasterWalletCard( + walletId: element.item1.walletId, + ); + } + } else { + return ConditionalParent( + condition: isDesktop, + builder: (child) => RoundedWhiteContainer( + padding: const EdgeInsets.symmetric( + vertical: 14, + horizontal: 20, + ), + borderColor: Theme.of(context) + .extension()! + .backgroundAppBar, + child: child, + ), + child: SimpleWalletCard( + walletId: element.item1.walletId, + popPrevious: isDesktop, + desktopNavigatorState: + isDesktop ? widget.navigatorState : null, + ), + ); + } + }, + separatorBuilder: (_, __) => SizedBox( + height: isDesktop ? 10 : 8, + ), + itemCount: data.length, + ); + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/wallets_view/wallets_view.dart b/lib/pages/wallets_view/wallets_view.dart index ed970134a..8df30771d 100644 --- a/lib/pages/wallets_view/wallets_view.dart +++ b/lib/pages/wallets_view/wallets_view.dart @@ -5,6 +5,7 @@ import 'package:stackwallet/pages/wallets_view/sub_widgets/all_wallets.dart'; import 'package:stackwallet/pages/wallets_view/sub_widgets/empty_wallets.dart'; import 'package:stackwallet/pages/wallets_view/sub_widgets/favorite_wallets.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; class WalletsView extends ConsumerWidget { const WalletsView({Key? key}) : super(key: key); @@ -22,8 +23,9 @@ class WalletsView extends ConsumerWidget { return SafeArea( child: hasWallets ? Padding( - padding: const EdgeInsets.only( - top: 20, + padding: EdgeInsets.only( + top: + ref.watch(themeProvider).themeId == "fruit_sorbet" ? 6 : 20, ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, diff --git a/lib/pages_desktop_specific/address_book_view/desktop_address_book.dart b/lib/pages_desktop_specific/address_book_view/desktop_address_book.dart index 693cacc74..905948569 100644 --- a/lib/pages_desktop_specific/address_book_view/desktop_address_book.dart +++ b/lib/pages_desktop_specific/address_book_view/desktop_address_book.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/models/contact.dart'; -import 'package:stackwallet/models/contact_address_entry.dart'; +import 'package:stackwallet/models/isar/models/contact_entry.dart'; import 'package:stackwallet/pages/address_book_views/subviews/add_address_book_entry_view.dart'; import 'package:stackwallet/pages/address_book_views/subviews/address_book_filter_view.dart'; import 'package:stackwallet/pages_desktop_specific/address_book_view/subwidgets/desktop_address_book_scaffold.dart'; @@ -10,11 +9,11 @@ import 'package:stackwallet/pages_desktop_specific/address_book_view/subwidgets/ import 'package:stackwallet/providers/global/address_book_service_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/ui/address_book_providers/address_book_filter_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/address_book_card.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; @@ -105,19 +104,18 @@ class _DesktopAddressBook extends ConsumerState { final managers = ref.read(walletsChangeNotifierProvider).managers; for (final manager in managers) { addresses.add( - ContactAddressEntry( - coin: manager.coin, - address: await manager.currentReceivingAddress, - label: "Current Receiving", - other: manager.walletName, - ), + ContactAddressEntry() + ..coinName = manager.coin.name + ..address = await manager.currentReceivingAddress + ..label = "Current Receiving" + ..other = manager.walletName, ); } - final self = Contact( + final self = ContactEntry( name: "My Stack", addresses: addresses, isFavorite: true, - id: "default", + customId: "default", ); await ref.read(addressBookServiceProvider).editContact(self); }); @@ -323,14 +321,14 @@ class _DesktopAddressBook extends ConsumerState { .extension()! .accentColorDark .withOpacity( - currentContactId == favorites[i].id + currentContactId == favorites[i].customId ? 0.08 : 0, ), child: RawMaterialButton( onPressed: () { setState(() { - currentContactId = favorites[i].id; + currentContactId = favorites[i].customId; }); }, padding: const EdgeInsets.symmetric( @@ -346,8 +344,8 @@ class _DesktopAddressBook extends ConsumerState { ), child: AddressBookCard( key: Key( - "favContactCard_${favorites[i].id}_key"), - contactId: favorites[i].id, + "favContactCard_${favorites[i].customId}_key"), + contactId: favorites[i].customId, desktopSendFrom: false, ), ), @@ -393,14 +391,16 @@ class _DesktopAddressBook extends ConsumerState { .extension()! .accentColorDark .withOpacity( - currentContactId == allContacts[i].id + currentContactId == + allContacts[i].customId ? 0.08 : 0, ), child: RawMaterialButton( onPressed: () { setState(() { - currentContactId = allContacts[i].id; + currentContactId = + allContacts[i].customId; }); }, padding: const EdgeInsets.symmetric( @@ -416,8 +416,8 @@ class _DesktopAddressBook extends ConsumerState { ), child: AddressBookCard( key: Key( - "favContactCard_${allContacts[i].id}_key"), - contactId: allContacts[i].id, + "favContactCard_${allContacts[i].customId}_key"), + contactId: allContacts[i].customId, desktopSendFrom: false, ), ), diff --git a/lib/pages_desktop_specific/address_book_view/subwidgets/desktop_address_card.dart b/lib/pages_desktop_specific/address_book_view/subwidgets/desktop_address_card.dart index ad9310df2..f26573c1e 100644 --- a/lib/pages_desktop_specific/address_book_view/subwidgets/desktop_address_card.dart +++ b/lib/pages_desktop_specific/address_book_view/subwidgets/desktop_address_card.dart @@ -2,21 +2,21 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:stackwallet/models/contact_address_entry.dart'; +import 'package:stackwallet/models/isar/models/contact_entry.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/address_book_views/subviews/edit_contact_address_view.dart'; import 'package:stackwallet/providers/ui/address_book_providers/address_entry_data_provider.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; -class DesktopAddressCard extends StatelessWidget { +class DesktopAddressCard extends ConsumerWidget { const DesktopAddressCard({ Key? key, required this.entry, @@ -29,14 +29,12 @@ class DesktopAddressCard extends StatelessWidget { final ClipboardInterface clipboard; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SvgPicture.asset( - Assets.svg.iconFor( - coin: entry.coin, - ), + ref.watch(coinIconProvider(entry.coin)), height: 32, width: 32, ), @@ -66,7 +64,7 @@ class DesktopAddressCard extends StatelessWidget { ), Row( children: [ - BlueTextButton( + CustomTextButton( text: "Copy", onTap: () { clipboard.setData( @@ -87,7 +85,7 @@ class DesktopAddressCard extends StatelessWidget { if (contactId != "default") Consumer( builder: (context, ref, child) { - return BlueTextButton( + return CustomTextButton( text: "Edit", onTap: () async { ref.refresh( diff --git a/lib/pages_desktop_specific/address_book_view/subwidgets/desktop_contact_details.dart b/lib/pages_desktop_specific/address_book_view/subwidgets/desktop_contact_details.dart index 3a70443be..9e1701fd2 100644 --- a/lib/pages_desktop_specific/address_book_view/subwidgets/desktop_contact_details.dart +++ b/lib/pages_desktop_specific/address_book_view/subwidgets/desktop_contact_details.dart @@ -1,8 +1,12 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:stackwallet/models/contact.dart'; -import 'package:stackwallet/models/paymint/transactions_model.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/isar/models/contact_entry.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; import 'package:stackwallet/pages/address_book_views/subviews/add_new_contact_address_view.dart'; import 'package:stackwallet/pages_desktop_specific/address_book_view/subwidgets/desktop_address_card.dart'; import 'package:stackwallet/pages_desktop_specific/address_book_view/subwidgets/desktop_contact_options_menu_popup.dart'; @@ -10,9 +14,10 @@ import 'package:stackwallet/providers/global/address_book_service_provider.dart' import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/providers/ui/address_book_providers/address_entry_data_provider.dart'; import 'package:stackwallet/services/coins/manager.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; @@ -38,7 +43,7 @@ class DesktopContactDetails extends ConsumerStatefulWidget { class _DesktopContactDetailsState extends ConsumerState { List> _cachedTransactions = []; - bool _contactHasAddress(String address, Contact contact) { + bool _contactHasAddress(String address, ContactEntry contact) { for (final entry in contact.addresses) { if (entry.address == address) { return true; @@ -57,11 +62,13 @@ class _DesktopContactDetailsState extends ConsumerState { List> result = []; for (final manager in managers) { - final transactions = (await manager.transactionData) - .getAllTransactions() - .values - .toList() - .where((e) => _contactHasAddress(e.address, contact)); + final transactions = await MainDB.instance + .getTransactions(manager.walletId) + .filter() + .anyOf(contact.addresses.map((e) => e.address), + (q, String e) => q.address((q) => q.valueEqualTo(e))) + .sortByTimestampDesc() + .findAll(); for (final tx in transactions) { result.add(Tuple2(manager.walletId, tx)); @@ -76,7 +83,7 @@ class _DesktopContactDetailsState extends ConsumerState { @override Widget build(BuildContext context) { // provider hack to prevent trying to update widget with deleted contact - Contact? _contact; + ContactEntry? _contact; try { _contact = ref.watch(addressBookServiceProvider .select((value) => value.getContactById(widget.contactId))); @@ -104,17 +111,23 @@ class _DesktopContactDetailsState extends ConsumerState { width: 32, height: 32, decoration: BoxDecoration( - color: contact.id == "default" + color: contact.customId == "default" ? Colors.transparent : Theme.of(context) .extension()! .textFieldDefaultBG, borderRadius: BorderRadius.circular(32), ), - child: contact.id == "default" + child: contact.customId == "default" ? Center( - child: SvgPicture.asset( - Assets.svg.stackIcon(context), + child: SvgPicture.file( + File( + ref.watch( + themeProvider.select( + (value) => value.assets.stackIcon, + ), + ), + ), width: 32, ), ) @@ -149,7 +162,7 @@ class _DesktopContactDetailsState extends ConsumerState { barrierColor: Colors.transparent, builder: (context) { return DesktopContactOptionsMenuPopup( - contactId: contact.id, + contactId: contact.customId, ); }, ); @@ -176,7 +189,7 @@ class _DesktopContactDetailsState extends ConsumerState { style: STextStyles.desktopTextExtraExtraSmall(context), ), - BlueTextButton( + CustomTextButton( text: "Add new", onTap: () async { ref.refresh( @@ -251,7 +264,7 @@ class _DesktopContactDetailsState extends ConsumerState { padding: const EdgeInsets.all(18), child: DesktopAddressCard( entry: contact.addresses[i], - contactId: contact.id, + contactId: contact.customId, ), ), ], diff --git a/lib/pages_desktop_specific/address_book_view/subwidgets/desktop_contact_options_menu_popup.dart b/lib/pages_desktop_specific/address_book_view/subwidgets/desktop_contact_options_menu_popup.dart index 690d1be98..3d8fb3a6a 100644 --- a/lib/pages_desktop_specific/address_book_view/subwidgets/desktop_contact_options_menu_popup.dart +++ b/lib/pages_desktop_specific/address_book_view/subwidgets/desktop_contact_options_menu_popup.dart @@ -4,9 +4,9 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/address_book_views/subviews/edit_contact_name_emoji_view.dart'; import 'package:stackwallet/providers/global/address_book_service_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; @@ -147,7 +147,7 @@ class _DesktopContactOptionsMenuPopupState onPressed: () { ref .read(addressBookServiceProvider) - .removeContact(contact.id); + .removeContact(contact.customId); Navigator.of(context).pop(); showFloatingFlushBar( type: FlushBarType.success, diff --git a/lib/pages_desktop_specific/addresses/desktop_wallet_addresses_view.dart b/lib/pages_desktop_specific/addresses/desktop_wallet_addresses_view.dart new file mode 100644 index 000000000..9128f9417 --- /dev/null +++ b/lib/pages_desktop_specific/addresses/desktop_wallet_addresses_view.dart @@ -0,0 +1,155 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/pages/receive_view/addresses/address_details_view.dart'; +import 'package:stackwallet/pages_desktop_specific/addresses/sub_widgets/desktop_address_list.dart'; +import 'package:stackwallet/providers/db/main_db_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; +import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; + +final desktopSelectedAddressId = StateProvider.autoDispose((ref) => null); + +class DesktopWalletAddressesView extends ConsumerStatefulWidget { + const DesktopWalletAddressesView({ + Key? key, + required this.walletId, + }) : super(key: key); + + static const String routeName = "/desktopWalletAddressesView"; + + final String walletId; + + @override + ConsumerState createState() => + _DesktopWalletAddressesViewState(); +} + +class _DesktopWalletAddressesViewState + extends ConsumerState { + static const _headerHeight = 70.0; + static const _columnWidth0 = 489.0; + + Stream? addressCollectionWatcher; + + void _onAddressCollectionWatcherEvent() { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) { + setState(() {}); + } + }); + } + + @override + void initState() { + addressCollectionWatcher = ref + .read(mainDBProvider) + .isar + .addresses + .watchLazy(fireImmediately: true); + addressCollectionWatcher!.listen((_) => _onAddressCollectionWatcherEvent()); + + super.initState(); + } + + @override + void dispose() { + addressCollectionWatcher = null; + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return DesktopScaffold( + appBar: DesktopAppBar( + background: Theme.of(context).extension()!.popupBG, + leading: Expanded( + child: Row( + children: [ + const SizedBox( + width: 32, + ), + AppBarIconButton( + size: 32, + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + shadows: const [], + icon: SvgPicture.asset( + Assets.svg.arrowLeft, + width: 18, + height: 18, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, + ), + onPressed: Navigator.of(context).pop, + ), + const SizedBox( + width: 12, + ), + Text( + "Address list", + style: STextStyles.desktopH3(context), + ), + const Spacer(), + ], + ), + ), + useSpacers: false, + isCompactHeight: true, + ), + body: Padding( + padding: const EdgeInsets.all(24), + child: Column( + children: [ + Expanded( + child: Row( + children: [ + SizedBox( + width: _columnWidth0, + child: DesktopAddressList( + searchHeight: _headerHeight, + walletId: widget.walletId, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: Column( + children: [ + const SizedBox( + height: _headerHeight, + ), + if (ref.watch(desktopSelectedAddressId.state).state != + null) + Expanded( + child: SingleChildScrollView( + child: AddressDetailsView( + key: Key( + "currentDesktopAddressDetails_key_${ref.watch(desktopSelectedAddressId.state).state}"), + walletId: widget.walletId, + addressId: ref + .watch(desktopSelectedAddressId.state) + .state!, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages_desktop_specific/addresses/sub_widgets/desktop_address_list.dart b/lib/pages_desktop_specific/addresses/sub_widgets/desktop_address_list.dart new file mode 100644 index 000000000..ece508042 --- /dev/null +++ b/lib/pages_desktop_specific/addresses/sub_widgets/desktop_address_list.dart @@ -0,0 +1,235 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/pages/receive_view/addresses/address_card.dart'; +import 'package:stackwallet/pages_desktop_specific/addresses/desktop_wallet_addresses_view.dart'; +import 'package:stackwallet/providers/db/main_db_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; + +class DesktopAddressList extends ConsumerStatefulWidget { + const DesktopAddressList({ + Key? key, + required this.walletId, + this.searchHeight, + }) : super(key: key); + + final String walletId; + final double? searchHeight; + + @override + ConsumerState createState() => _DesktopAddressListState(); +} + +class _DesktopAddressListState extends ConsumerState { + final bool isDesktop = Util.isDesktop; + + String _searchString = ""; + + late final TextEditingController _searchController; + final searchFieldFocusNode = FocusNode(); + + List _search(String term) { + if (term.isEmpty) { + return ref + .read(mainDBProvider) + .getAddresses(widget.walletId) + .filter() + .group((q) => q + .subTypeEqualTo(AddressSubType.change) + .or() + .subTypeEqualTo(AddressSubType.receiving) + .or() + .subTypeEqualTo(AddressSubType.paynymReceive) + .or() + .subTypeEqualTo(AddressSubType.paynymNotification)) + .and() + .not() + .typeEqualTo(AddressType.nonWallet) + .sortByDerivationIndex() + .idProperty() + .findAllSync(); + } + + final labels = ref + .read(mainDBProvider) + .getAddressLabels(widget.walletId) + .filter() + .group( + (q) => q + .valueContains(term, caseSensitive: false) + .or() + .addressStringContains(term, caseSensitive: false) + .or() + .group( + (q) => q + .tagsIsNotNull() + .and() + .tagsElementContains(term, caseSensitive: false), + ), + ) + .findAllSync(); + + if (labels.isEmpty) { + return []; + } + + return ref + .read(mainDBProvider) + .getAddresses(widget.walletId) + .filter() + .anyOf( + labels, (q, e) => q.valueEqualTo(e.addressString)) + .group((q) => q + .subTypeEqualTo(AddressSubType.change) + .or() + .subTypeEqualTo(AddressSubType.receiving) + .or() + .subTypeEqualTo(AddressSubType.paynymReceive) + .or() + .subTypeEqualTo(AddressSubType.paynymNotification)) + .and() + .not() + .typeEqualTo(AddressType.nonWallet) + .sortByDerivationIndex() + .idProperty() + .findAllSync(); + } + + @override + void initState() { + _searchController = TextEditingController(); + + super.initState(); + } + + @override + void dispose() { + _searchController.dispose(); + searchFieldFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final coin = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(widget.walletId).coin)); + + final ids = _search(_searchString); + + return Column( + children: [ + SizedBox( + height: widget.searchHeight!, + child: Center( + child: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: !isDesktop, + enableSuggestions: !isDesktop, + controller: _searchController, + focusNode: searchFieldFocusNode, + onChanged: (value) { + setState(() { + _searchString = value; + }); + }, + style: isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveText, + height: 1.8, + ) + : STextStyles.field(context), + decoration: standardInputDecoration( + "Search...", + searchFieldFocusNode, + context, + desktopMed: isDesktop, + ).copyWith( + prefixIcon: Padding( + padding: EdgeInsets.symmetric( + horizontal: isDesktop ? 12 : 10, + vertical: isDesktop ? 18 : 16, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: isDesktop ? 20 : 16, + height: isDesktop ? 20 : 16, + ), + ), + suffixIcon: _searchController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchController.text = ""; + _searchString = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), + ), + ), + const SizedBox( + height: 20, + ), + Expanded( + child: SingleChildScrollView( + child: RoundedWhiteContainer( + padding: EdgeInsets.zero, + child: ListView.separated( + shrinkWrap: true, + itemCount: ids.length, + separatorBuilder: (_, __) => Container( + height: 1, + color: Theme.of(context) + .extension()! + .backgroundAppBar, + ), + itemBuilder: (_, index) => Padding( + padding: const EdgeInsets.all(4), + child: AddressCard( + key: Key("addressCardDesktop_key_${ids[index]}"), + walletId: widget.walletId, + addressId: ids[index], + coin: coin, + onPressed: () { + ref.read(desktopSelectedAddressId.state).state = + ids[index]; + }, + ), + ), + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/coin_control/desktop_coin_control_use_dialog.dart b/lib/pages_desktop_specific/coin_control/desktop_coin_control_use_dialog.dart new file mode 100644 index 000000000..e14d8bb19 --- /dev/null +++ b/lib/pages_desktop_specific/coin_control/desktop_coin_control_use_dialog.dart @@ -0,0 +1,589 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/utxo.dart'; +import 'package:stackwallet/pages_desktop_specific/coin_control/utxo_row.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/animated_widgets/rotate_icon.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/custom_buttons/dropdown_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/expandable2.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; +import 'package:stackwallet/widgets/toggle.dart'; + +final desktopUseUTXOs = StateProvider((ref) => {}); + +class DesktopCoinControlUseDialog extends ConsumerStatefulWidget { + const DesktopCoinControlUseDialog({ + Key? key, + required this.walletId, + this.amountToSend, + }) : super(key: key); + + final String walletId; + final Amount? amountToSend; + + @override + ConsumerState createState() => + _DesktopCoinControlUseDialogState(); +} + +class _DesktopCoinControlUseDialogState + extends ConsumerState { + late final TextEditingController _searchController; + late final Coin coin; + final searchFieldFocusNode = FocusNode(); + + final Set _selectedUTXOsData = {}; + final Set _selectedUTXOs = {}; + + Map>? _map; + List? _list; + + String _searchString = ""; + + CCFilter _filter = CCFilter.available; + CCSortDescriptor _sort = CCSortDescriptor.age; + + bool selectedChanged(Set newSelected) { + if (ref.read(desktopUseUTXOs).length != newSelected.length) return true; + return !ref.read(desktopUseUTXOs).containsAll(newSelected); + } + + @override + void initState() { + _searchController = TextEditingController(); + coin = ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId) + .coin; + + for (final utxo in ref.read(desktopUseUTXOs)) { + final data = UtxoRowData(utxo.id, true); + _selectedUTXOs.add(utxo); + _selectedUTXOsData.add(data); + } + + super.initState(); + } + + @override + void dispose() { + _searchController.dispose(); + searchFieldFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + + if (_sort == CCSortDescriptor.address) { + _list = null; + _map = MainDB.instance.queryUTXOsGroupedByAddressSync( + walletId: widget.walletId, + filter: _filter, + sort: _sort, + searchTerm: _searchString, + coin: coin, + ); + } else { + _map = null; + _list = MainDB.instance.queryUTXOsSync( + walletId: widget.walletId, + filter: _filter, + sort: _sort, + searchTerm: _searchString, + coin: coin, + ); + } + + final Amount selectedSum = _selectedUTXOs.map((e) => e.value).fold( + Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ), + (value, element) => value += Amount( + rawValue: BigInt.from(element), + fractionDigits: coin.decimals, + ), + ); + + final enableApply = widget.amountToSend == null + ? selectedChanged(_selectedUTXOs) + : selectedChanged(_selectedUTXOs) && + widget.amountToSend! <= selectedSum; + + return DesktopDialog( + maxWidth: 700, + maxHeight: MediaQuery.of(context).size.height - 128, + child: Column( + children: [ + Row( + children: [ + const AppBarBackButton( + size: 40, + iconSize: 24, + ), + Text( + "Coin control", + style: STextStyles.desktopH3(context), + ), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Column( + children: [ + RoundedContainer( + color: Colors.transparent, + borderColor: Theme.of(context) + .extension()! + .textFieldDefaultBG, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "This option allows you to control, freeze, and utilize " + "outputs at your discretion.", + style: + STextStyles.desktopTextExtraExtraSmall(context), + ), + ], + ), + ), + const SizedBox( + height: 16, + ), + Row( + children: [ + Expanded( + child: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: false, + enableSuggestions: false, + controller: _searchController, + focusNode: searchFieldFocusNode, + onChanged: (value) { + setState(() { + _searchString = value; + }); + }, + style: STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveText, + height: 1.8, + ), + decoration: standardInputDecoration( + "Search...", + searchFieldFocusNode, + context, + desktopMed: true, + ).copyWith( + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 18, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: 20, + height: 20, + ), + ), + suffixIcon: _searchController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchController.text = ""; + _searchString = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), + ), + const SizedBox( + width: 16, + ), + SizedBox( + height: 56, + width: 240, + child: Toggle( + isOn: _filter == CCFilter.frozen, + onColor: Theme.of(context) + .extension()! + .rateTypeToggleDesktopColorOn, + offColor: Theme.of(context) + .extension()! + .rateTypeToggleDesktopColorOff, + onIcon: Assets.svg.coinControl.unBlocked, + onText: "Available", + offIcon: Assets.svg.coinControl.blocked, + offText: "Frozen", + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onValueChanged: (value) { + setState(() { + if (value) { + _filter = CCFilter.frozen; + } else { + _filter = CCFilter.available; + } + }); + }, + ), + ), + const SizedBox( + width: 16, + ), + JDropdownIconButton( + redrawOnScreenSizeChanged: true, + groupValue: _sort, + items: CCSortDescriptor.values.toSet(), + onSelectionChanged: (CCSortDescriptor? newValue) { + if (newValue != null && newValue != _sort) { + setState(() { + _sort = newValue; + }); + } + }, + displayPrefix: "Sort by", + ) + ], + ), + const SizedBox( + height: 16, + ), + Expanded( + child: _list != null + ? ListView.separated( + shrinkWrap: true, + primary: false, + itemCount: _list!.length, + separatorBuilder: (context, _) => const SizedBox( + height: 10, + ), + itemBuilder: (context, index) { + final utxo = MainDB.instance.isar.utxos + .where() + .idEqualTo(_list![index]) + .findFirstSync()!; + final data = UtxoRowData(utxo.id, false); + data.selected = _selectedUTXOsData.contains(data); + + return UtxoRow( + key: Key( + "${utxo.walletId}_${utxo.id}_${utxo.isBlocked}"), + data: data, + compact: true, + walletId: widget.walletId, + onSelectionChanged: (value) { + setState(() { + if (data.selected) { + _selectedUTXOsData.add(value); + _selectedUTXOs.add(utxo); + } else { + _selectedUTXOsData.remove(value); + _selectedUTXOs.remove(utxo); + } + }); + }, + ); + }, + ) + : ListView.separated( + itemCount: _map!.entries.length, + separatorBuilder: (context, _) => const SizedBox( + height: 10, + ), + itemBuilder: (context, index) { + final entry = _map!.entries.elementAt(index); + final _controller = RotateIconController(); + + return Expandable2( + border: Theme.of(context) + .extension()! + .backgroundAppBar, + background: Theme.of(context) + .extension()! + .popupBG, + animationDurationMultiplier: + 0.2 * entry.value.length, + onExpandWillChange: (state) { + if (state == Expandable2State.expanded) { + _controller.forward?.call(); + } else { + _controller.reverse?.call(); + } + }, + header: RoundedContainer( + padding: const EdgeInsets.all(20), + color: Colors.transparent, + child: Row( + children: [ + SvgPicture.file( + File( + ref.watch(coinIconProvider(coin)), + ), + width: 24, + height: 24, + ), + const SizedBox( + width: 12, + ), + Expanded( + flex: 3, + child: Text( + entry.key, + style: STextStyles.w600_14(context), + ), + ), + Expanded( + child: Text( + "${entry.value.length} " + "output${entry.value.length > 1 ? "s" : ""}", + style: STextStyles + .desktopTextExtraExtraSmall( + context), + ), + ), + RotateIcon( + animationDurationMultiplier: + 0.2 * entry.value.length, + icon: SvgPicture.asset( + Assets.svg.chevronDown, + width: 14, + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + curve: Curves.easeInOut, + controller: _controller, + ), + ], + ), + ), + children: entry.value.map( + (id) { + final utxo = MainDB.instance.isar.utxos + .where() + .idEqualTo(id) + .findFirstSync()!; + final data = UtxoRowData(utxo.id, false); + data.selected = + _selectedUTXOsData.contains(data); + + return UtxoRow( + key: Key( + "${utxo.walletId}_${utxo.id}_${utxo.isBlocked}"), + data: data, + compact: true, + compactWithBorder: false, + raiseOnSelected: false, + walletId: widget.walletId, + onSelectionChanged: (value) { + setState(() { + if (data.selected) { + _selectedUTXOsData.add(value); + _selectedUTXOs.add(utxo); + } else { + _selectedUTXOsData.remove(value); + _selectedUTXOs.remove(utxo); + } + }); + }, + ); + }, + ).toList(), + ); + }, + ), + ), + const SizedBox( + height: 16, + ), + RoundedContainer( + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + padding: EdgeInsets.zero, + child: ConditionalParent( + condition: widget.amountToSend != null, + builder: (child) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + child, + Container( + height: 1.2, + color: Theme.of(context) + .extension()! + .popupBG, + ), + Padding( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + "Amount to send", + style: + STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + Text( + "${widget.amountToSend!.decimal.toStringAsFixed( + coin.decimals, + )}" + " ${coin.ticker}", + style: + STextStyles.desktopTextExtraExtraSmall( + context, + ).copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + ], + ), + ), + ], + ); + }, + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Selected amount", + style: STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + Text( + "${selectedSum.decimal.toStringAsFixed( + coin.decimals, + )} ${coin.ticker}", + style: STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: widget.amountToSend == null + ? Theme.of(context) + .extension()! + .textDark + : selectedSum < widget.amountToSend! + ? Theme.of(context) + .extension()! + .accentColorRed + : Theme.of(context) + .extension()! + .accentColorGreen, + ), + ), + ], + ), + ), + ), + ), + const SizedBox( + height: 16, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + enabled: _selectedUTXOsData.isNotEmpty, + buttonHeight: ButtonHeight.l, + label: _selectedUTXOsData.isEmpty + ? "Clear selection" + : "Clear selection (${_selectedUTXOsData.length})", + onPressed: () { + setState(() { + _selectedUTXOsData.clear(); + _selectedUTXOs.clear(); + }); + }, + ), + ), + const SizedBox( + width: 20, + ), + Expanded( + child: PrimaryButton( + enabled: enableApply, + buttonHeight: ButtonHeight.l, + label: "Apply", + onPressed: () { + ref.read(desktopUseUTXOs.state).state = + _selectedUTXOs; + + Navigator.of(context, rootNavigator: true).pop(); + }, + ), + ), + ], + ), + const SizedBox( + height: 16, + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages_desktop_specific/coin_control/desktop_coin_control_view.dart b/lib/pages_desktop_specific/coin_control/desktop_coin_control_view.dart new file mode 100644 index 000000000..0685204e4 --- /dev/null +++ b/lib/pages_desktop_specific/coin_control/desktop_coin_control_view.dart @@ -0,0 +1,425 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/utxo.dart'; +import 'package:stackwallet/pages_desktop_specific/coin_control/freeze_button.dart'; +import 'package:stackwallet/pages_desktop_specific/coin_control/utxo_row.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/animated_widgets/rotate_icon.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/custom_buttons/dropdown_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; +import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/expandable2.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; + +class DesktopCoinControlView extends ConsumerStatefulWidget { + const DesktopCoinControlView({ + Key? key, + required this.walletId, + }) : super(key: key); + + static const String routeName = "/desktopCoinControl"; + + final String walletId; + + @override + ConsumerState createState() => + _DesktopCoinControlViewState(); +} + +class _DesktopCoinControlViewState + extends ConsumerState { + late final TextEditingController _searchController; + late final Coin coin; + final searchFieldFocusNode = FocusNode(); + + final Set _selectedUTXOs = {}; + + Map>? _map; + List? _list; + + String _searchString = ""; + + CCFilter _filter = CCFilter.all; + CCSortDescriptor _sort = CCSortDescriptor.age; + + @override + void initState() { + _searchController = TextEditingController(); + coin = ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId) + .coin; + super.initState(); + } + + @override + void dispose() { + _searchController.dispose(); + searchFieldFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + + if (_sort == CCSortDescriptor.address) { + _list = null; + _map = MainDB.instance.queryUTXOsGroupedByAddressSync( + walletId: widget.walletId, + filter: _filter, + sort: _sort, + searchTerm: _searchString, + coin: coin, + ); + } else { + _map = null; + _list = MainDB.instance.queryUTXOsSync( + walletId: widget.walletId, + filter: _filter, + sort: _sort, + searchTerm: _searchString, + coin: coin, + ); + } + + return DesktopScaffold( + appBar: DesktopAppBar( + background: Theme.of(context).extension()!.popupBG, + leading: Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const SizedBox( + width: 32, + ), + AppBarIconButton( + size: 32, + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + shadows: const [], + icon: SvgPicture.asset( + Assets.svg.arrowLeft, + width: 18, + height: 18, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, + ), + onPressed: Navigator.of(context).pop, + ), + const SizedBox( + width: 18, + ), + SvgPicture.asset( + Assets.svg.coinControl.gamePad, + width: 32, + height: 32, + color: + Theme.of(context).extension()!.textSubtitle1, + ), + const SizedBox( + width: 12, + ), + Text( + "Coin control", + style: STextStyles.desktopH3(context), + ), + ], + ), + ), + useSpacers: false, + isCompactHeight: true, + ), + body: Column( + children: [ + Padding( + padding: const EdgeInsets.all(24), + child: Row( + children: [ + Expanded( + child: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: false, + enableSuggestions: false, + controller: _searchController, + focusNode: searchFieldFocusNode, + onChanged: (value) { + setState(() { + _searchString = value; + }); + }, + style: + STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveText, + height: 1.8, + ), + decoration: standardInputDecoration( + "Search...", + searchFieldFocusNode, + context, + desktopMed: true, + ).copyWith( + prefixIcon: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 18, + ), + child: SvgPicture.asset( + Assets.svg.search, + width: 20, + height: 20, + ), + ), + suffixIcon: _searchController.text.isNotEmpty + ? Padding( + padding: const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + children: [ + TextFieldIconButton( + child: const XIcon(), + onTap: () async { + setState(() { + _searchController.text = ""; + _searchString = ""; + }); + }, + ), + ], + ), + ), + ) + : null, + ), + ), + ), + ), + const SizedBox( + width: 24, + ), + AnimatedCrossFade( + firstChild: JDropdownButton( + redrawOnScreenSizeChanged: true, + showIcon: true, + width: 200, + items: CCFilter.values.toSet(), + groupValue: _filter, + onSelectionChanged: (CCFilter? newValue) { + if (newValue != null && newValue != _filter) { + setState(() { + _filter = newValue; + }); + } + }, + ), + secondChild: FreezeButton( + key: Key("${_selectedUTXOs.length}"), + selectedUTXOs: _selectedUTXOs, + ), + crossFadeState: _selectedUTXOs.isEmpty + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + duration: const Duration( + milliseconds: 200, + ), + ), + const SizedBox( + width: 24, + ), + AnimatedCrossFade( + firstChild: JDropdownButton( + redrawOnScreenSizeChanged: true, + label: "Sort by...", + width: 200, + groupValue: _sort, + items: CCSortDescriptor.values.toSet(), + onSelectionChanged: (CCSortDescriptor? newValue) { + if (newValue != null && newValue != _sort) { + setState(() { + _sort = newValue; + }); + } + }, + ), + secondChild: SecondaryButton( + buttonHeight: ButtonHeight.l, + width: 200, + label: "Clear selection (${_selectedUTXOs.length})", + onPressed: () => setState(() => _selectedUTXOs.clear()), + ), + crossFadeState: _selectedUTXOs.isEmpty + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + duration: const Duration( + milliseconds: 200, + ), + ), + ], + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 24, + ), + child: _list != null + ? ListView.separated( + itemCount: _list!.length, + separatorBuilder: (context, _) => const SizedBox( + height: 10, + ), + itemBuilder: (context, index) { + final utxo = MainDB.instance.isar.utxos + .where() + .idEqualTo(_list![index]) + .findFirstSync()!; + final data = UtxoRowData(utxo.id, false); + data.selected = _selectedUTXOs.contains(data); + + return UtxoRow( + key: Key( + "${utxo.walletId}_${utxo.id}_${utxo.isBlocked}"), + data: data, + walletId: widget.walletId, + onSelectionChanged: (value) { + setState(() { + if (data.selected) { + _selectedUTXOs.add(value); + } else { + _selectedUTXOs.remove(value); + } + }); + }, + ); + }, + ) + : ListView.separated( + itemCount: _map!.entries.length, + separatorBuilder: (context, _) => const SizedBox( + height: 10, + ), + itemBuilder: (context, index) { + final entry = _map!.entries.elementAt(index); + final _controller = RotateIconController(); + + return Expandable2( + border: Theme.of(context) + .extension()! + .backgroundAppBar, + background: Theme.of(context) + .extension()! + .popupBG, + animationDurationMultiplier: 0.2 * entry.value.length, + onExpandWillChange: (state) { + if (state == Expandable2State.expanded) { + _controller.forward?.call(); + } else { + _controller.reverse?.call(); + } + }, + header: RoundedContainer( + padding: const EdgeInsets.all(20), + color: Colors.transparent, + child: Row( + children: [ + SvgPicture.file( + File( + ref.watch(coinIconProvider(coin)), + ), + width: 24, + height: 24, + ), + const SizedBox( + width: 12, + ), + Expanded( + child: Text( + entry.key, + style: STextStyles.w600_14(context), + ), + ), + Expanded( + child: Text( + "${entry.value.length} " + "output${entry.value.length > 1 ? "s" : ""}", + style: + STextStyles.desktopTextExtraExtraSmall( + context), + ), + ), + RotateIcon( + animationDurationMultiplier: + 0.2 * entry.value.length, + icon: SvgPicture.asset( + Assets.svg.chevronDown, + width: 14, + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + curve: Curves.easeInOut, + controller: _controller, + ), + ], + ), + ), + children: entry.value.map( + (id) { + final utxo = MainDB.instance.isar.utxos + .where() + .idEqualTo(id) + .findFirstSync()!; + final data = UtxoRowData(utxo.id, false); + data.selected = _selectedUTXOs.contains(data); + + return UtxoRow( + key: Key( + "${utxo.walletId}_${utxo.id}_${utxo.isBlocked}"), + data: data, + walletId: widget.walletId, + raiseOnSelected: false, + onSelectionChanged: (value) { + setState(() { + if (data.selected) { + _selectedUTXOs.add(value); + } else { + _selectedUTXOs.remove(value); + } + }); + }, + ); + }, + ).toList(), + ); + }, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages_desktop_specific/coin_control/freeze_button.dart b/lib/pages_desktop_specific/coin_control/freeze_button.dart new file mode 100644 index 000000000..b33bc89fa --- /dev/null +++ b/lib/pages_desktop_specific/coin_control/freeze_button.dart @@ -0,0 +1,117 @@ +import 'package:async/async.dart'; +import 'package:flutter/material.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/utxo.dart'; +import 'package:stackwallet/pages_desktop_specific/coin_control/utxo_row.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; + +class FreezeButton extends StatefulWidget { + const FreezeButton({ + Key? key, + required this.selectedUTXOs, + }) : super(key: key); + + final Set selectedUTXOs; + + @override + State createState() => _FreezeButtonState(); +} + +class _FreezeButtonState extends State { + String _freezeLabelCache = "Freeze"; + + String _freezeLabel(Set dataSet) { + if (dataSet.isEmpty) return _freezeLabelCache; + + bool hasUnblocked = false; + for (final data in dataSet) { + if (!MainDB.instance.isar.utxos + .where() + .idEqualTo(data.utxoId) + .findFirstSync()! + .isBlocked) { + hasUnblocked = true; + break; + } + } + _freezeLabelCache = hasUnblocked ? "Freeze" : "Unfreeze"; + return _freezeLabelCache; + } + + Future _onFreezeStateButtonPressed() async { + List utxosToUpdate = []; + switch (_freezeLabelCache) { + case "Freeze": + for (final e in widget.selectedUTXOs) { + final utxo = MainDB.instance.isar.utxos + .where() + .idEqualTo(e.utxoId) + .findFirstSync()!; + if (!utxo.isBlocked) { + utxosToUpdate.add(utxo.copyWith(isBlocked: true)); + } + } + break; + + case "Unfreeze": + for (final e in widget.selectedUTXOs) { + final utxo = MainDB.instance.isar.utxos + .where() + .idEqualTo(e.utxoId) + .findFirstSync()!; + if (utxo.isBlocked) { + utxosToUpdate.add(utxo.copyWith(isBlocked: false)); + } + } + break; + + default: + Logging.instance.log( + "Unknown utxo method name found in $runtimeType", + level: LogLevel.Fatal, + ); + return; + } + + // final update utxo set in db + if (utxosToUpdate.isNotEmpty) { + await MainDB.instance.putUTXOs(utxosToUpdate); + } + } + + late Stream bigStream; + + @override + void initState() { + List> streams = []; + for (final data in widget.selectedUTXOs) { + final stream = MainDB.instance.watchUTXO(id: data.utxoId); + + streams.add(stream); + } + + bigStream = StreamGroup.merge(streams); + bigStream.listen((event) { + if (mounted) { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + setState(() {}); + }); + } + }); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + return PrimaryButton( + buttonHeight: ButtonHeight.l, + width: 200, + label: _freezeLabel(widget.selectedUTXOs), + onPressed: _onFreezeStateButtonPressed, + ); + } +} diff --git a/lib/pages_desktop_specific/coin_control/utxo_row.dart b/lib/pages_desktop_specific/coin_control/utxo_row.dart new file mode 100644 index 000000000..4d3796e06 --- /dev/null +++ b/lib/pages_desktop_specific/coin_control/utxo_row.dart @@ -0,0 +1,227 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/pages/coin_control/utxo_details_view.dart'; +import 'package:stackwallet/providers/global/locale_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/icon_widgets/utxo_status_icon.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; + +class UtxoRowData { + UtxoRowData(this.utxoId, this.selected); + + Id utxoId; + bool selected; + + @override + String toString() { + return "selected=$selected: $utxoId"; + } + + @override + bool operator ==(Object other) { + return other is UtxoRowData && other.utxoId == utxoId; + } + + @override + int get hashCode => Object.hashAll([utxoId.hashCode]); +} + +class UtxoRow extends ConsumerStatefulWidget { + const UtxoRow({ + Key? key, + required this.data, + required this.walletId, + this.onSelectionChanged, + this.compact = false, + this.compactWithBorder = true, + this.raiseOnSelected = true, + }) : super(key: key); + + final String walletId; + final UtxoRowData data; + final void Function(UtxoRowData)? onSelectionChanged; + final bool compact; + final bool compactWithBorder; + final bool raiseOnSelected; + + @override + ConsumerState createState() => _UtxoRowState(); +} + +class _UtxoRowState extends ConsumerState { + late Stream stream; + late UTXO utxo; + + void _details() async { + await showDialog( + context: context, + builder: (context) => UtxoDetailsView( + utxoId: utxo.id, + walletId: widget.walletId, + ), + ); + } + + @override + void initState() { + utxo = MainDB.instance.isar.utxos + .where() + .idEqualTo(widget.data.utxoId) + .findFirstSync()!; + + stream = MainDB.instance.watchUTXO(id: utxo.id); + super.initState(); + } + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + + final coin = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(widget.walletId).coin)); + + final currentChainHeight = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(widget.walletId).currentHeight)); + + return StreamBuilder( + stream: stream, + builder: (context, snapshot) { + if (snapshot.hasData) { + utxo = snapshot.data!; + } + + return RoundedContainer( + borderColor: widget.compact && widget.compactWithBorder + ? Theme.of(context).extension()!.textFieldDefaultBG + : null, + color: Theme.of(context).extension()!.popupBG, + boxShadow: widget.data.selected && widget.raiseOnSelected + ? [ + Theme.of(context).extension()!.standardBoxShadow, + ] + : null, + child: Row( + children: [ + if (!(widget.compact && utxo.isBlocked)) + Checkbox( + value: widget.data.selected, + onChanged: (value) { + setState(() { + widget.data.selected = value!; + }); + widget.onSelectionChanged?.call(widget.data); + }, + ), + if (!(widget.compact && utxo.isBlocked)) + const SizedBox( + width: 10, + ), + UTXOStatusIcon( + blocked: utxo.isBlocked, + status: utxo.isConfirmed( + currentChainHeight, + coin.requiredConfirmations, + ) + ? UTXOStatusIconStatus.confirmed + : UTXOStatusIconStatus.unconfirmed, + background: Theme.of(context).extension()!.popupBG, + selected: false, + width: 32, + height: 32, + ), + const SizedBox( + width: 10, + ), + if (!widget.compact) + Text( + "${Amount( + rawValue: BigInt.from(utxo.value), + fractionDigits: coin.decimals, + ).localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), + ), + )} ${coin.ticker}", + textAlign: TextAlign.right, + style: STextStyles.w600_14(context), + ), + if (!widget.compact) + const SizedBox( + width: 10, + ), + Expanded( + child: ConditionalParent( + condition: widget.compact, + builder: (child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "${Amount( + rawValue: BigInt.from(utxo.value), + fractionDigits: coin.decimals, + ).localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), + ), + )} ${coin.ticker}", + textAlign: TextAlign.right, + style: STextStyles.w600_14(context), + ), + const SizedBox( + height: 2, + ), + child, + ], + ); + }, + child: Text( + utxo.name.isNotEmpty + ? utxo.name + : utxo.address ?? utxo.txid, + textAlign: + widget.compact ? TextAlign.left : TextAlign.center, + style: STextStyles.w500_12(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ), + ), + const SizedBox( + width: 10, + ), + widget.compact + ? CustomTextButton( + text: "Details", + onTap: _details, + ) + : SecondaryButton( + width: 120, + buttonHeight: ButtonHeight.xs, + label: "Details", + onPressed: _details, + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/pages_desktop_specific/desktop_buy/desktop_buy_view.dart b/lib/pages_desktop_specific/desktop_buy/desktop_buy_view.dart new file mode 100644 index 000000000..45f9cc0be --- /dev/null +++ b/lib/pages_desktop_specific/desktop_buy/desktop_buy_view.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/pages/buy_view/buy_form.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; +import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class DesktopBuyView extends StatefulWidget { + const DesktopBuyView({Key? key}) : super(key: key); + + static const String routeName = "/desktopBuyView"; + + @override + State createState() => _DesktopBuyViewState(); +} + +class _DesktopBuyViewState extends State { + @override + Widget build(BuildContext context) { + return DesktopScaffold( + appBar: DesktopAppBar( + isCompactHeight: true, + leading: Padding( + padding: const EdgeInsets.only( + left: 24, + ), + child: Text( + "Buy crypto", + style: STextStyles.desktopH3(context), + ), + ), + ), + body: Padding( + padding: const EdgeInsets.only( + left: 24, + right: 24, + bottom: 24, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + SizedBox( + height: 16, + ), + RoundedWhiteContainer( + padding: EdgeInsets.all(24), + child: BuyForm(), + ), + ], + ), + ), + const SizedBox( + width: 16, + ), + // Expanded( + // child: Row( + // children: const [ + // Expanded( + // child: DesktopTradeHistory(), + // ), + // ], + // ), + // ), + ], + ), + ), + ); + } +} diff --git a/lib/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart b/lib/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart index 472460469..e92d743ba 100644 --- a/lib/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart +++ b/lib/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart @@ -1,20 +1,27 @@ import 'dart:async'; +import 'dart:io'; import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; import 'package:stackwallet/pages/exchange_view/trade_details_view.dart'; import 'package:stackwallet/providers/exchange/trade_sent_from_stack_lookup_provider.dart'; import 'package:stackwallet/providers/global/trades_service_provider.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/route_generator.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; @@ -26,8 +33,6 @@ import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; import 'package:tuple/tuple.dart'; -import '../../route_generator.dart'; - class DesktopAllTradesView extends ConsumerStatefulWidget { const DesktopAllTradesView({Key? key}) : super(key: key); @@ -283,15 +288,25 @@ class DesktopTradeRowCard extends ConsumerStatefulWidget { class _DesktopTradeRowCardState extends ConsumerState { late final String tradeId; - String _fetchIconAssetForStatus(String statusString, BuildContext context) { + String _fetchIconAssetForStatus(String statusString, IThemeAssets assets) { ChangeNowTransactionStatus? status; try { if (statusString.toLowerCase().startsWith("waiting")) { - statusString = "waiting"; + statusString = "Waiting"; } status = changeNowTransactionStatusFromStringIgnoreCase(statusString); } on ArgumentError catch (_) { - status = ChangeNowTransactionStatus.Failed; + switch (statusString.toLowerCase()) { + case "funds confirming": + case "processing payment": + return assets.txExchangePending; + + case "completed": + return assets.txExchange; + + default: + status = ChangeNowTransactionStatus.Failed; + } } switch (status) { @@ -302,11 +317,11 @@ class _DesktopTradeRowCardState extends ConsumerState { case ChangeNowTransactionStatus.Sending: case ChangeNowTransactionStatus.Refunded: case ChangeNowTransactionStatus.Verifying: - return Assets.svg.txExchangePending(context); + return assets.txExchangePending; case ChangeNowTransactionStatus.Finished: - return Assets.svg.txExchange(context); + return assets.txExchange; case ChangeNowTransactionStatus.Failed: - return Assets.svg.txExchangeFailed(context); + return assets.txExchangeFailed; } } @@ -349,10 +364,12 @@ class _DesktopTradeRowCardState extends ConsumerState { //todo: check if print needed // debugPrint("name: ${manager.walletName}"); - // TODO store tx data completely locally in isar so we don't lock up ui here when querying txData - final txData = await manager.transactionData; + final tx = await MainDB.instance + .getTransactions(walletIds.first) + .filter() + .txidEqualTo(txid) + .findFirst(); - final tx = txData.getAllTransactions()[txid]; await showDialog( context: context, builder: (context) => DesktopDialog( @@ -506,10 +523,14 @@ class _DesktopTradeRowCardState extends ConsumerState { borderRadius: BorderRadius.circular(32), ), child: Center( - child: SvgPicture.asset( - _fetchIconAssetForStatus( - trade.status, - context, + child: SvgPicture.file( + File( + _fetchIconAssetForStatus( + trade.status, + ref.watch( + themeAssetsProvider, + ), + ), ), width: 32, height: 32, diff --git a/lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart b/lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart index 105c485f0..f63165567 100644 --- a/lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart +++ b/lib/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart @@ -1,77 +1,182 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/pages/exchange_view/exchange_form.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart'; +import 'package:stackwallet/providers/exchange/exchange_form_state_provider.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; +import 'package:stackwallet/widgets/custom_loading_overlay.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; -class DesktopExchangeView extends StatefulWidget { +class DesktopExchangeView extends ConsumerStatefulWidget { const DesktopExchangeView({Key? key}) : super(key: key); static const String routeName = "/desktopExchange"; @override - State createState() => _DesktopExchangeViewState(); + ConsumerState createState() => + _DesktopExchangeViewState(); } -class _DesktopExchangeViewState extends State { +class _DesktopExchangeViewState extends ConsumerState { + bool _initialCachePopulationUnderway = false; + + @override + void initState() { + if (!ref.read(prefsChangeNotifierProvider).externalCalls) { + if (ExchangeDataLoadingService.currentCacheVersion < + ExchangeDataLoadingService.cacheVersion) { + _initialCachePopulationUnderway = true; + ExchangeDataLoadingService.instance.onLoadingComplete = () { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty( + ref.read(efCurrencyPairProvider), + ref.read(efRateTypeProvider), + ); + setState(() { + _initialCachePopulationUnderway = false; + }); + }); + }; + } + ExchangeDataLoadingService.instance.loadAll(); + } else if (ExchangeDataLoadingService.instance.isLoading && + ExchangeDataLoadingService.currentCacheVersion < + ExchangeDataLoadingService.cacheVersion) { + _initialCachePopulationUnderway = true; + ExchangeDataLoadingService.instance.onLoadingComplete = () { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { + await ExchangeDataLoadingService.instance.setCurrenciesIfEmpty( + ref.read(efCurrencyPairProvider), + ref.read(efRateTypeProvider), + ); + setState(() { + _initialCachePopulationUnderway = false; + }); + }); + }; + } + + super.initState(); + } + @override Widget build(BuildContext context) { - return DesktopScaffold( - appBar: DesktopAppBar( - isCompactHeight: true, - leading: Padding( + return ConditionalParent( + condition: _initialCachePopulationUnderway, + builder: (child) { + return Stack( + children: [ + child, + Material( + color: Theme.of(context) + .extension()! + .overlay + .withOpacity(0.6), + child: const CustomLoadingOverlay( + message: "Updating exchange data", + subMessage: "This could take a few minutes", + eventBus: null, + ), + ) + ], + ); + }, + child: DesktopScaffold( + appBar: DesktopAppBar( + isCompactHeight: true, + leading: Padding( + padding: const EdgeInsets.only( + left: 24, + ), + child: Text( + "Swap", + style: STextStyles.desktopH3(context), + ), + ), + ), + body: Padding( padding: const EdgeInsets.only( left: 24, + right: 24, + bottom: 24, ), - child: Text( - "Exchange", - style: STextStyles.desktopH3(context), - ), - ), - ), - body: Padding( - padding: const EdgeInsets.only( - left: 24, - right: 24, - bottom: 24, - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, + child: Column( + children: [ + Row( children: [ - Text( - "Exchange details", - style: STextStyles.desktopTextExtraExtraSmall(context), + Expanded( + child: Text( + "Exchange details", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), ), const SizedBox( - height: 16, + width: 16, ), - const RoundedWhiteContainer( - padding: EdgeInsets.all(24), - child: ExchangeForm(), - ), - ], - ), - ), - const SizedBox( - width: 16, - ), - Expanded( - child: Row( - children: const [ Expanded( - child: DesktopTradeHistory(), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Recent trades", + style: + STextStyles.desktopTextExtraExtraSmall(context), + ), + CustomTextButton( + text: "See all", + onTap: () { + Navigator.of(context).pushNamed( + DesktopAllTradesView.routeName, + ); + }, + ), + ], + ), ), ], ), - ), - ], + const SizedBox( + height: 16, + ), + Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: ListView( + children: const [ + RoundedWhiteContainer( + padding: EdgeInsets.all(24), + child: ExchangeForm(), + ), + ], + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: Row( + children: const [ + Expanded( + child: DesktopTradeHistory(), + ), + ], + ), + ), + ], + ), + ), + ], + ), ), ), ); diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart index 88a8c34ff..0412541e9 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart @@ -7,21 +7,22 @@ import 'package:qr_flutter/qr_flutter.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/pages/exchange_view/send_from_view.dart'; -import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/subwidgets/desktop_exchange_steps_indicator.dart'; -import 'package:stackwallet/providers/exchange/exchange_provider.dart'; +import 'package:stackwallet/providers/exchange/exchange_form_state_provider.dart'; import 'package:stackwallet/providers/global/trades_service_provider.dart'; import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; import 'package:stackwallet/services/notifications_api.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_loading_overlay.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; @@ -83,7 +84,7 @@ class _StepScaffoldState extends ConsumerState { ); final ExchangeResponse response = await ref - .read(exchangeProvider) + .read(efExchangeProvider) .createTrade( from: ref.read(desktopExchangeModelProvider)!.sendTicker, to: ref.read(desktopExchangeModelProvider)!.receiveTicker, @@ -96,24 +97,24 @@ class _StepScaffoldState extends ConsumerState { extraId: null, addressRefund: ref.read(desktopExchangeModelProvider)!.refundAddress!, refundExtraId: "", - rateId: ref.read(desktopExchangeModelProvider)!.rateId, + estimate: ref.read(desktopExchangeModelProvider)!.estimate, reversed: ref.read(desktopExchangeModelProvider)!.reversed, ); if (response.value == null) { if (mounted) { Navigator.of(context).pop(); - } - unawaited( - showDialog( - context: context, - barrierDismissible: true, - builder: (_) => SimpleDesktopDialog( - title: "Failed to create trade", - message: response.exception?.toString() ?? ""), - ), - ); + unawaited( + showDialog( + context: context, + barrierDismissible: true, + builder: (_) => SimpleDesktopDialog( + title: "Failed to create trade", + message: response.exception?.toString() ?? ""), + ), + ); + } return false; } @@ -181,10 +182,11 @@ class _StepScaffoldState extends ConsumerState { void sendFromStack() { final trade = ref.read(desktopExchangeModelProvider)!.trade!; - final amount = Decimal.parse(trade.payInAmount); final address = trade.payInAddress; - final coin = coinFromTickerCaseInsensitive(trade.payInCurrency); + final amount = Decimal.parse(trade.payInAmount).toAmount( + fractionDigits: coin.decimals, + ); showDialog( context: context, diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart index 4ec96f95d..af5f93408 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_1.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; class DesktopStep1 extends ConsumerWidget { @@ -37,8 +37,8 @@ class DesktopStep1 extends ConsumerWidget { child: Column( children: [ DesktopStepItem( - label: "Exchange", - value: ref.watch(currentExchangeNameStateProvider.state).state, + label: "Swap", + value: ref.watch(efExchangeProviderNameProvider), ), Container( height: 1, diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart index 027de6cd7..d8e131c77 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_2.dart @@ -7,12 +7,12 @@ import 'package:stackwallet/pages_desktop_specific/desktop_exchange/subwidgets/d import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart'; import 'package:stackwallet/providers/exchange/exchange_send_from_wallet_id_provider.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; @@ -300,8 +300,8 @@ class _DesktopStep2State extends ConsumerState { ), if (isStackCoin(ref.watch(desktopExchangeModelProvider .select((value) => value!.receiveTicker)))) - BlueTextButton( - text: "Choose from stack", + CustomTextButton( + text: "Choose from Stack", onTap: selectRecipientAddressFromStack, ), ], @@ -432,8 +432,8 @@ class _DesktopStep2State extends ConsumerState { ), if (isStackCoin(ref.watch(desktopExchangeModelProvider .select((value) => value!.sendTicker)))) - BlueTextButton( - text: "Choose from stack", + CustomTextButton( + text: "Choose from Stack", onTap: selectRefundAddressFromStack, ), ], diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart index 714cee971..5641070b5 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_3.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart'; -import 'package:stackwallet/providers/exchange/current_exchange_name_state_provider.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; class DesktopStep3 extends ConsumerStatefulWidget { @@ -35,8 +35,8 @@ class _DesktopStep3State extends ConsumerState { child: Column( children: [ DesktopStepItem( - label: "Exchange", - value: ref.watch(currentExchangeNameStateProvider.state).state, + label: "Swap", + value: ref.watch(efExchangeProviderNameProvider), ), Container( height: 1, diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart index 4943ed442..c88db957e 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_4.dart @@ -5,9 +5,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/step_scaffold.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -45,7 +45,8 @@ class _DesktopStep4State extends ConsumerState { return; } - final statusResponse = await ref.read(exchangeProvider).updateTrade(trade); + final statusResponse = + await ref.read(efExchangeProvider).updateTrade(trade); String status = "Waiting"; if (statusResponse.value != null) { status = statusResponse.value!.status; diff --git a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart index 323517e13..b5eb35a49 100644 --- a/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart +++ b/lib/pages_desktop_specific/desktop_exchange/exchange_steps/subwidgets/desktop_step_item.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; class DesktopStepItem extends StatelessWidget { diff --git a/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_choose_from_stack.dart b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_choose_from_stack.dart index efa939871..9d6106129 100644 --- a/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_choose_from_stack.dart +++ b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_choose_from_stack.dart @@ -1,15 +1,14 @@ -import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; -import 'package:stackwallet/widgets/animated_text.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; @@ -215,13 +214,13 @@ class _DesktopChooseFromStackState ], ), const Spacer(), - BalanceDisplay( + _BalanceDisplay( walletId: walletIds[index], ), const SizedBox( width: 80, ), - BlueTextButton( + CustomTextButton( text: "Select wallet", onTap: () async { final address = @@ -268,8 +267,8 @@ class _DesktopChooseFromStackState } } -class BalanceDisplay extends ConsumerStatefulWidget { - const BalanceDisplay({ +class _BalanceDisplay extends ConsumerWidget { + const _BalanceDisplay({ Key? key, required this.walletId, }) : super(key: key); @@ -277,64 +276,25 @@ class BalanceDisplay extends ConsumerStatefulWidget { final String walletId; @override - ConsumerState createState() => _BalanceDisplayState(); -} - -class _BalanceDisplayState extends ConsumerState { - late final String walletId; - - Decimal? _cachedBalance; - - static const loopedText = [ - "Loading balance ", - "Loading balance. ", - "Loading balance.. ", - "Loading balance..." - ]; - - @override - void initState() { - walletId = widget.walletId; - super.initState(); - } - - @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final manager = ref.watch(walletsChangeNotifierProvider .select((value) => value.getManager(walletId))); final locale = ref.watch( localeServiceChangeNotifierProvider.select((value) => value.locale)); - return FutureBuilder( - future: manager.availableBalance, - builder: (context, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done && - snapshot.hasData && - snapshot.data != null) { - _cachedBalance = snapshot.data; - } + Amount total = manager.balance.total; + if (manager.coin == Coin.firo || manager.coin == Coin.firoTestNet) { + final firoWallet = manager.wallet as FiroWallet; + total += firoWallet.balancePrivate.total; + } - if (_cachedBalance == null) { - return AnimatedText( - stringsToLoopThrough: loopedText, - style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context).extension()!.textSubtitle1, - ), - ); - } else { - return Text( - "${Format.localizedStringAsFixed( - value: _cachedBalance!, - locale: locale, - decimalPlaces: 8, - )} ${manager.coin.ticker}", - style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context).extension()!.textSubtitle1, - ), - textAlign: TextAlign.right, - ); - } - }, + return Text( + "${total.localizedStringAsFixed(locale: locale)} " + "${manager.coin.ticker}", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context).extension()!.textSubtitle1, + ), + textAlign: TextAlign.right, ); } } diff --git a/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_exchange_steps_indicator.dart b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_exchange_steps_indicator.dart index 926429ad7..880b0a5a0 100644 --- a/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_exchange_steps_indicator.dart +++ b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_exchange_steps_indicator.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; class DesktopExchangeStepsIndicator extends StatelessWidget { diff --git a/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart index 2ba3078ac..29919098e 100644 --- a/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart +++ b/lib/pages_desktop_specific/desktop_exchange/subwidgets/desktop_trade_history.dart @@ -2,21 +2,23 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart'; import 'package:stackwallet/pages/exchange_view/trade_details_view.dart'; -import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart'; import 'package:stackwallet/providers/exchange/trade_sent_from_stack_lookup_provider.dart'; import 'package:stackwallet/providers/global/trades_service_provider.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/route_generator.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; -import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/trade_card.dart'; +import '../../../db/isar/main_db.dart'; + class DesktopTradeHistory extends ConsumerStatefulWidget { const DesktopTradeHistory({Key? key}) : super(key: key); @@ -61,35 +63,21 @@ class _DesktopTradeHistoryState extends ConsumerState { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Recent trades", - style: STextStyles.desktopTextExtraExtraSmall(context), - ), - BlueTextButton( - text: "See all", - onTap: () { - Navigator.of(context) - .pushNamed(DesktopAllTradesView.routeName); - }, - ), - ], - ), - const SizedBox( - height: 16, - ), Expanded( child: ListView.separated( shrinkWrap: true, primary: false, itemBuilder: (context, index) { BorderRadius? radius; - if (index == tradeCount - 1) { - radius = _borderRadiusLast; - } else if (index == 0) { + if (index == 0) { radius = _borderRadiusFirst; + if (tradeCount == 1) { + radius = BorderRadius.circular( + Constants.size.checkboxBorderRadius, + ); + } + } else if (index == tradeCount - 1) { + radius = _borderRadiusLast; } return Container( @@ -126,10 +114,11 @@ class _DesktopTradeHistoryState extends ConsumerState { //todo: check if print needed // debugPrint("name: ${manager.walletName}"); - // TODO store tx data completely locally in isar so we don't lock up ui here when querying txData - final txData = await manager.transactionData; - - final tx = txData.getAllTransactions()[txid]; + final tx = await MainDB.instance + .getTransactions(walletIds.first) + .filter() + .txidEqualTo(txid) + .findFirst(); if (mounted) { await showDialog( @@ -280,13 +269,6 @@ class _DesktopTradeHistoryState extends ConsumerState { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - "Recent trades", - style: STextStyles.desktopTextExtraExtraSmall(context), - ), - const SizedBox( - height: 16, - ), RoundedWhiteContainer( child: Center( child: Text( diff --git a/lib/pages_desktop_specific/desktop_home_view.dart b/lib/pages_desktop_specific/desktop_home_view.dart index 3ef490512..0b2f99c81 100644 --- a/lib/pages_desktop_specific/desktop_home_view.dart +++ b/lib/pages_desktop_specific/desktop_home_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/pages_desktop_specific/address_book_view/desktop_address_book.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_buy/desktop_buy_view.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_menu.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/my_stack_view.dart'; @@ -16,8 +17,8 @@ import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/providers/ui/transaction_filter_provider.dart'; import 'package:stackwallet/providers/ui/unread_notifications_provider.dart'; import 'package:stackwallet/route_generator.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; final currentWalletIdProvider = StateProvider((_) => null); @@ -56,6 +57,11 @@ class _DesktopHomeViewState extends ConsumerState { onGenerateRoute: RouteGenerator.generateRoute, initialRoute: DesktopExchangeView.routeName, ), + DesktopMenuItemId.buy: const Navigator( + key: Key("desktopBuyHomeKey"), + onGenerateRoute: RouteGenerator.generateRoute, + initialRoute: DesktopBuyView.routeName, + ), DesktopMenuItemId.notifications: const Navigator( key: Key("desktopNotificationsHomeKey"), onGenerateRoute: RouteGenerator.generateRoute, @@ -83,10 +89,11 @@ class _DesktopHomeViewState extends ConsumerState { ), }; - DesktopMenuItemId prev = DesktopMenuItemId.myStack; - void onMenuSelectionWillChange(DesktopMenuItemId newKey) { - if (prev == DesktopMenuItemId.myStack && prev == newKey) { + // handle logging out of active wallet + if (ref.read(prevDesktopMenuItemProvider.state).state == + DesktopMenuItemId.myStack && + ref.read(prevDesktopMenuItemProvider.state).state == newKey) { Navigator.of(myStackViewNavKey.currentContext!) .popUntil(ModalRoute.withName(MyStackView.routeName)); if (ref.read(currentWalletIdProvider.state).state != null) { @@ -105,7 +112,7 @@ class _DesktopHomeViewState extends ConsumerState { ref.read(managerProvider.notifier).isActiveWallet = false; } } - prev = newKey; + ref.read(prevDesktopMenuItemProvider.state).state = newKey; // check for unread notifications and refresh provider before // showing notifications view diff --git a/lib/pages_desktop_specific/desktop_menu.dart b/lib/pages_desktop_specific/desktop_menu.dart index 220fa26ab..bcdf7f031 100644 --- a/lib/pages_desktop_specific/desktop_menu.dart +++ b/lib/pages_desktop_specific/desktop_menu.dart @@ -6,14 +6,15 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_menu_item.dart'; import 'package:stackwallet/providers/desktop/current_desktop_menu_item.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/living_stack_icon.dart'; enum DesktopMenuItemId { myStack, exchange, + buy, notifications, addressBook, settings, @@ -44,6 +45,8 @@ class _DesktopMenuState extends ConsumerState { double _width = expandedWidth; + // final _buyDataLoadingService = BuyDataLoadingService(); + void updateSelectedMenuItem(DesktopMenuItemId idKey) { widget.onSelectionWillChange?.call(idKey); @@ -75,6 +78,7 @@ class _DesktopMenuState extends ConsumerState { DMIController(), DMIController(), DMIController(), + DMIController(), ]; super.initState(); @@ -151,7 +155,7 @@ class _DesktopMenuState extends ConsumerState { DesktopMenuItem( duration: duration, icon: const DesktopExchangeIcon(), - label: "Exchange", + label: "Swap", value: DesktopMenuItemId.exchange, onChanged: updateSelectedMenuItem, controller: controllers[1], @@ -159,13 +163,24 @@ class _DesktopMenuState extends ConsumerState { const SizedBox( height: 2, ), + DesktopMenuItem( + duration: duration, + icon: const DesktopBuyIcon(), + label: "Buy crypto", + value: DesktopMenuItemId.buy, + onChanged: updateSelectedMenuItem, + controller: controllers[2], + ), + const SizedBox( + height: 2, + ), DesktopMenuItem( duration: duration, icon: const DesktopNotificationsIcon(), label: "Notifications", value: DesktopMenuItemId.notifications, onChanged: updateSelectedMenuItem, - controller: controllers[2], + controller: controllers[3], ), const SizedBox( height: 2, @@ -176,7 +191,7 @@ class _DesktopMenuState extends ConsumerState { label: "Address Book", value: DesktopMenuItemId.addressBook, onChanged: updateSelectedMenuItem, - controller: controllers[3], + controller: controllers[4], ), const SizedBox( height: 2, @@ -187,7 +202,7 @@ class _DesktopMenuState extends ConsumerState { label: "Settings", value: DesktopMenuItemId.settings, onChanged: updateSelectedMenuItem, - controller: controllers[4], + controller: controllers[5], ), const SizedBox( height: 2, @@ -198,7 +213,7 @@ class _DesktopMenuState extends ConsumerState { label: "Support", value: DesktopMenuItemId.support, onChanged: updateSelectedMenuItem, - controller: controllers[5], + controller: controllers[6], ), const SizedBox( height: 2, @@ -209,26 +224,23 @@ class _DesktopMenuState extends ConsumerState { label: "About", value: DesktopMenuItemId.about, onChanged: updateSelectedMenuItem, - controller: controllers[6], - ), - const Spacer(), - DesktopMenuItem( - duration: duration, - labelLength: 123, - icon: const DesktopExitIcon(), - label: "Exit", - value: 7, - onChanged: (_) { - // todo: save stuff/ notify before exit? - // exit(0); - if (Platform.isWindows) { - exit(0); - } else { - SystemNavigator.pop(); - } - }, controller: controllers[7], ), + const Spacer(), + if (!Platform.isIOS) + DesktopMenuItem( + duration: duration, + labelLength: 123, + icon: const DesktopExitIcon(), + label: "Exit", + value: 7, + onChanged: (_) { + // todo: save stuff/ notify before exit? + // exit(0); + SystemNavigator.pop(); + }, + controller: controllers[8], + ), ], ), ), diff --git a/lib/pages_desktop_specific/desktop_menu_item.dart b/lib/pages_desktop_specific/desktop_menu_item.dart index e75f3410f..10c2ad25d 100644 --- a/lib/pages_desktop_specific/desktop_menu_item.dart +++ b/lib/pages_desktop_specific/desktop_menu_item.dart @@ -1,12 +1,15 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_menu.dart'; import 'package:stackwallet/providers/desktop/current_desktop_menu_item.dart'; import 'package:stackwallet/providers/global/notifications_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; class DMIController { VoidCallback? toggle; @@ -55,29 +58,61 @@ class DesktopExchangeIcon extends ConsumerWidget { } } +class DesktopBuyIcon extends ConsumerWidget { + const DesktopBuyIcon({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return SvgPicture.asset( + ref.watch(themeProvider.select((value) => value.assets.buy)), + width: 20, + height: 20, + color: DesktopMenuItemId.buy == + ref.watch(currentDesktopMenuItemProvider.state).state + ? Theme.of(context).extension()!.accentColorDark + : Theme.of(context) + .extension()! + .accentColorDark + .withOpacity(0.8), + ); + } +} + class DesktopNotificationsIcon extends ConsumerWidget { const DesktopNotificationsIcon({Key? key}) : super(key: key); @override Widget build(BuildContext context, WidgetRef ref) { - return SvgPicture.asset( - ref.watch(notificationsProvider - .select((value) => value.hasUnreadNotifications)) - ? Assets.svg.bellNew(context) - : Assets.svg.bell, - width: 20, - height: 20, - color: ref.watch(notificationsProvider - .select((value) => value.hasUnreadNotifications)) - ? null - : DesktopMenuItemId.notifications == - ref.watch(currentDesktopMenuItemProvider.state).state - ? Theme.of(context).extension()!.accentColorDark - : Theme.of(context) - .extension()! - .accentColorDark - .withOpacity(0.8), - ); + return ref.watch(notificationsProvider + .select((value) => value.hasUnreadNotifications)) + ? SvgPicture.file( + File( + ref.watch( + themeProvider.select( + (value) => value.assets.bellNew, + ), + ), + ), + width: 20, + height: 20, + ) + : SvgPicture.asset( + Assets.svg.bell, + width: 20, + height: 20, + color: ref.watch(notificationsProvider + .select((value) => value.hasUnreadNotifications)) + ? null + : DesktopMenuItemId.notifications == + ref.watch(currentDesktopMenuItemProvider.state).state + ? Theme.of(context) + .extension()! + .accentColorDark + : Theme.of(context) + .extension()! + .accentColorDark + .withOpacity(0.8), + ); } } @@ -262,10 +297,10 @@ class _DesktopMenuItemState extends ConsumerState> style: value == group ? Theme.of(context) .extension()! - .getDesktopMenuButtonColorSelected(context) + .getDesktopMenuButtonStyleSelected(context) : Theme.of(context) .extension()! - .getDesktopMenuButtonColor(context), + .getDesktopMenuButtonStyle(context), onPressed: () { onChanged(value); }, diff --git a/lib/pages_desktop_specific/my_stack_view/coin_wallets_table.dart b/lib/pages_desktop_specific/my_stack_view/coin_wallets_table.dart index a0c561e3c..16b43a5e3 100644 --- a/lib/pages_desktop_specific/my_stack_view/coin_wallets_table.dart +++ b/lib/pages_desktop_specific/my_stack_view/coin_wallets_table.dart @@ -3,9 +3,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/wallet_info_row/wallet_info_row.dart'; @@ -40,6 +40,7 @@ class CoinWalletsTable extends ConsumerWidget { children: [ for (int i = 0; i < walletIds.length; i++) Column( + key: Key("${coin.name}_$runtimeType${walletIds[i]}_key"), children: [ if (i != 0) const SizedBox( @@ -60,6 +61,14 @@ class CoinWalletsTable extends ConsumerWidget { ref.read(currentWalletIdProvider.state).state = walletIds[i]; + final manager = ref + .read(walletsChangeNotifierProvider) + .getManager(walletIds[i]); + if (manager.coin == Coin.monero || + manager.coin == Coin.wownero) { + await manager.initializeExisting(); + } + await Navigator.of(context).pushNamed( DesktopWalletView.routeName, arguments: walletIds[i], diff --git a/lib/pages_desktop_specific/my_stack_view/desktop_favorite_wallets.dart b/lib/pages_desktop_specific/my_stack_view/desktop_favorite_wallets.dart index 91130fe19..b9ce38eb4 100644 --- a/lib/pages_desktop_specific/my_stack_view/desktop_favorite_wallets.dart +++ b/lib/pages_desktop_specific/my_stack_view/desktop_favorite_wallets.dart @@ -4,10 +4,10 @@ import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages/manage_favorites_view/manage_favorites_view.dart'; import 'package:stackwallet/pages/wallets_view/sub_widgets/favorite_card.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; class DesktopFavoriteWallets extends ConsumerWidget { @@ -39,7 +39,7 @@ class DesktopFavoriteWallets extends ConsumerWidget { .textFieldActiveSearchIconRight, ), ), - BlueTextButton( + CustomTextButton( text: "Edit", onTap: () { Navigator.of(context).pushNamed(ManageFavoritesView.routeName); @@ -64,12 +64,14 @@ class DesktopFavoriteWallets extends ConsumerWidget { children: [ ...favorites.map((p0) { final walletId = ref.read(p0).walletId; + final walletName = ref.read(p0).walletName; final managerProvider = ref .read(walletsChangeNotifierProvider) .getManagerProvider(walletId); return FavoriteCard( walletId: walletId, + key: Key(walletName), width: cardWidth, height: cardHeight, managerProvider: managerProvider, diff --git a/lib/pages_desktop_specific/my_stack_view/dialogs/desktop_expanding_wallet_card.dart b/lib/pages_desktop_specific/my_stack_view/dialogs/desktop_expanding_wallet_card.dart new file mode 100644 index 000000000..e6f64a25a --- /dev/null +++ b/lib/pages_desktop_specific/my_stack_view/dialogs/desktop_expanding_wallet_card.dart @@ -0,0 +1,192 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; +import 'package:stackwallet/services/coins/manager.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/animated_widgets/rotate_icon.dart'; +import 'package:stackwallet/widgets/expandable.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/wallet_card.dart'; +import 'package:stackwallet/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance.dart'; +import 'package:stackwallet/widgets/wallet_info_row/sub_widgets/wallet_info_row_coin_icon.dart'; +import 'package:tuple/tuple.dart'; + +class DesktopExpandingWalletCard extends StatefulWidget { + const DesktopExpandingWalletCard({ + Key? key, + required this.data, + required this.navigatorState, + }) : super(key: key); + + final Tuple2> data; + final NavigatorState navigatorState; + + @override + State createState() => + _DesktopExpandingWalletCardState(); +} + +class _DesktopExpandingWalletCardState + extends State { + final expandableController = ExpandableController(); + final rotateIconController = RotateIconController(); + final List tokenContractAddresses = []; + + @override + void initState() { + if (widget.data.item1.hasTokenSupport) { + tokenContractAddresses.addAll( + widget.data.item2.map((e) => e.address), + ); + } + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return RoundedWhiteContainer( + padding: EdgeInsets.zero, + borderColor: Theme.of(context).extension()!.backgroundAppBar, + child: Expandable( + initialState: widget.data.item1.hasTokenSupport + ? ExpandableState.expanded + : ExpandableState.collapsed, + controller: expandableController, + onExpandWillChange: (toState) { + if (toState == ExpandableState.expanded) { + rotateIconController.forward?.call(); + } else { + rotateIconController.reverse?.call(); + } + }, + header: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 14, + ), + child: Row( + children: [ + Expanded( + child: Row( + children: [ + Expanded( + flex: 2, + child: Row( + children: [ + WalletInfoCoinIcon( + coin: widget.data.item1.coin, + ), + const SizedBox( + width: 12, + ), + Text( + widget.data.item1.walletName, + style: STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + ], + ), + ), + Expanded( + flex: 4, + child: WalletInfoRowBalance( + walletId: widget.data.item1.walletId, + ), + ), + ], + ), + ), + MaterialButton( + padding: const EdgeInsets.all(5), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + minWidth: 32, + height: 32, + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + elevation: 0, + hoverElevation: 0, + disabledElevation: 0, + highlightElevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + if (expandableController.state == ExpandableState.collapsed) { + rotateIconController.forward?.call(); + } else { + rotateIconController.reverse?.call(); + } + expandableController.toggle?.call(); + }, + child: RotateIcon( + controller: rotateIconController, + icon: RotatedBox( + quarterTurns: 2, + child: SvgPicture.asset( + Assets.svg.chevronDown, + width: 14, + ), + ), + curve: Curves.easeInOut, + ), + ), + ], + ), + ), + body: ListView( + shrinkWrap: true, + primary: false, + children: [ + Container( + width: double.infinity, + height: 1, + color: + Theme.of(context).extension()!.backgroundAppBar, + ), + Padding( + padding: const EdgeInsets.only( + left: 32, + right: 14, + top: 14, + bottom: 14, + ), + child: SimpleWalletCard( + walletId: widget.data.item1.walletId, + popPrevious: true, + desktopNavigatorState: widget.navigatorState, + ), + ), + ...tokenContractAddresses.map( + (e) => Padding( + padding: const EdgeInsets.only( + left: 32, + right: 14, + top: 14, + bottom: 14, + ), + child: SimpleWalletCard( + walletId: widget.data.item1.walletId, + contractAddress: e, + popPrevious: true, + desktopNavigatorState: widget.navigatorState, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart b/lib/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart index d09bc6310..e210f1653 100644 --- a/lib/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart +++ b/lib/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; class ExitToMyStackButton extends StatelessWidget { const ExitToMyStackButton({ @@ -22,7 +22,7 @@ class ExitToMyStackButton extends StatelessWidget { child: TextButton( style: Theme.of(context) .extension()! - .getSmallSecondaryEnabledButtonColor(context), + .getSmallSecondaryEnabledButtonStyle(context), onPressed: onPressed ?? () { Navigator.of(context).popUntil( diff --git a/lib/pages_desktop_specific/my_stack_view/my_stack_view.dart b/lib/pages_desktop_specific/my_stack_view/my_stack_view.dart index 47ffdf4fe..cf231a30c 100644 --- a/lib/pages_desktop_specific/my_stack_view/my_stack_view.dart +++ b/lib/pages_desktop_specific/my_stack_view/my_stack_view.dart @@ -1,10 +1,12 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/pages/wallets_view/sub_widgets/empty_wallets.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/my_wallets.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; -import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/widgets/background.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; @@ -40,11 +42,11 @@ class _MyStackViewState extends ConsumerState { } } -class DesktopMyStackTitle extends StatelessWidget { +class DesktopMyStackTitle extends ConsumerWidget { const DesktopMyStackTitle({Key? key}) : super(key: key); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { return Row( children: [ const SizedBox( @@ -53,8 +55,14 @@ class DesktopMyStackTitle extends StatelessWidget { SizedBox( width: 32, height: 32, - child: SvgPicture.asset( - Assets.svg.stackIcon(context), + child: SvgPicture.file( + File( + ref.watch( + themeProvider.select( + (value) => value.assets.stackIcon, + ), + ), + ), ), ), const SizedBox( diff --git a/lib/pages_desktop_specific/my_stack_view/my_wallets.dart b/lib/pages_desktop_specific/my_stack_view/my_wallets.dart index 41d0f0ce5..ecd21186d 100644 --- a/lib/pages_desktop_specific/my_stack_view/my_wallets.dart +++ b/lib/pages_desktop_specific/my_stack_view/my_wallets.dart @@ -4,8 +4,8 @@ import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/add_wallet_vi import 'package:stackwallet/pages_desktop_specific/my_stack_view/desktop_favorite_wallets.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_summary_table.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; class MyWallets extends ConsumerStatefulWidget { @@ -22,32 +22,48 @@ class _MyWalletsState extends ConsumerState { .select((value) => value.showFavoriteWallets)); return Padding( - padding: const EdgeInsets.all(24), + padding: const EdgeInsets.only( + top: 24, + left: 14, + right: 14, + bottom: 0, + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (showFavorites) const DesktopFavoriteWallets(), - Row( - children: [ - Text( - "All wallets", - style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveSearchIconRight, + if (showFavorites) + const Padding( + padding: EdgeInsets.symmetric( + horizontal: 10, + ), + child: DesktopFavoriteWallets(), + ), + Padding( + padding: const EdgeInsets.all( + 10, + ), + child: Row( + children: [ + Text( + "All wallets", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconRight, + ), ), - ), - const Spacer(), - BlueTextButton( - text: "Add new wallet", - onTap: () { - Navigator.of( - context, - rootNavigator: true, - ).pushNamed(AddWalletView.routeName); - }, - ), - ], + const Spacer(), + CustomTextButton( + text: "Add new wallet", + onTap: () { + Navigator.of( + context, + rootNavigator: true, + ).pushNamed(AddWalletView.routeName); + }, + ), + ], + ), ), const SizedBox( height: 20, diff --git a/lib/pages_desktop_specific/my_stack_view/paynym/desktop_paynym_send_dialog.dart b/lib/pages_desktop_specific/my_stack_view/paynym/desktop_paynym_send_dialog.dart new file mode 100644 index 000000000..8776a3394 --- /dev/null +++ b/lib/pages_desktop_specific/my_stack_view/paynym/desktop_paynym_send_dialog.dart @@ -0,0 +1,176 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/paynym/paynym_account_lite.dart'; +import 'package:stackwallet/models/send_view_auto_fill_data.dart'; +import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart'; +import 'package:stackwallet/providers/global/locale_provider.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/providers/global/price_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart'; +import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/barcode_scanner_interface.dart'; +import 'package:stackwallet/utilities/clipboard_interface.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class DesktopPaynymSendDialog extends ConsumerStatefulWidget { + const DesktopPaynymSendDialog({ + Key? key, + required this.walletId, + this.autoFillData, + this.clipboard = const ClipboardWrapper(), + this.barcodeScanner = const BarcodeScannerWrapper(), + this.accountLite, + }) : super(key: key); + + final String walletId; + final SendViewAutoFillData? autoFillData; + final ClipboardInterface clipboard; + final BarcodeScannerInterface barcodeScanner; + final PaynymAccountLite? accountLite; + + @override + ConsumerState createState() => + _DesktopPaynymSendDialogState(); +} + +class _DesktopPaynymSendDialogState + extends ConsumerState { + @override + Widget build(BuildContext context) { + final manager = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(widget.walletId))); + final String locale = ref.watch( + localeServiceChangeNotifierProvider.select((value) => value.locale)); + + final coin = manager.coin; + + final isFiro = coin == Coin.firo || coin == Coin.firoTestNet; + + return DesktopDialog( + maxHeight: double.infinity, + maxWidth: 580, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Send ${manager.coin.ticker.toUpperCase()}", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: RoundedWhiteContainer( + borderColor: + Theme.of(context).extension()!.background, + // Theme.of(context).extension()!.textSubtitle4, + child: Row( + children: [ + SvgPicture.file( + File( + ref.watch(coinIconProvider(coin)), + ), + width: 36, + height: 36, + ), + const SizedBox( + width: 12, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + manager.walletName, + style: STextStyles.titleBold12(context), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + const SizedBox( + height: 2, + ), + Text( + isFiro + ? "${ref.watch(publicPrivateBalanceStateProvider.state).state} balance" + : "Available balance", + style: STextStyles.baseXS(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ], + ), + const Spacer(), + Container( + color: Colors.transparent, + child: Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + "${!isFiro ? manager.balance.spendable.localizedStringAsFixed( + locale: locale, + ) : ref.watch( + publicPrivateBalanceStateProvider.state, + ).state == "Private" ? (manager.wallet as FiroWallet).availablePrivateBalance().localizedStringAsFixed( + locale: locale, + ) : (manager.wallet as FiroWallet).availablePublicBalance().localizedStringAsFixed( + locale: locale, + )} ${coin.ticker}", + style: STextStyles.titleBold12(context), + textAlign: TextAlign.right, + ), + const SizedBox( + height: 2, + ), + Text( + "${((!isFiro ? manager.balance.spendable.decimal : ref.watch(publicPrivateBalanceStateProvider.state).state == "Private" ? (manager.wallet as FiroWallet).availablePrivateBalance().decimal : (manager.wallet as FiroWallet).availablePublicBalance().decimal) * ref.watch(priceAnd24hChangeNotifierProvider.select((value) => value.getPrice(coin).item1))).toAmount(fractionDigits: 2).localizedStringAsFixed(locale: locale)} ${ref.watch(prefsChangeNotifierProvider.select((value) => value.currency))}", + style: STextStyles.baseXS(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + textAlign: TextAlign.right, + ) + ], + ), + ), + ], + ), + ), + ), + const SizedBox( + height: 20, + ), + Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: DesktopSend( + walletId: manager.walletId, + accountLite: widget.accountLite, + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_summary_table.dart b/lib/pages_desktop_specific/my_stack_view/wallet_summary_table.dart index 40a72f6e0..4a14cb5a5 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_summary_table.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_summary_table.dart @@ -1,17 +1,19 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/pages_desktop_specific/my_stack_view/coin_wallets_table.dart'; +import 'package:stackwallet/pages/wallets_view/wallets_overview.dart'; import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; -import 'package:stackwallet/widgets/table_view/table_view.dart'; -import 'package:stackwallet/widgets/table_view/table_view_cell.dart'; -import 'package:stackwallet/widgets/table_view/table_view_row.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; class WalletSummaryTable extends ConsumerStatefulWidget { const WalletSummaryTable({Key? key}) : super(key: key); @@ -24,88 +26,168 @@ class _WalletTableState extends ConsumerState { @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); - final providersByCoin = ref - .watch( - walletsChangeNotifierProvider.select( - (value) => value.getManagerProvidersByCoin(), + final providersByCoin = ref.watch( + walletsChangeNotifierProvider.select( + (value) => value.getManagerProvidersByCoin(), + ), + ); + + return ListView.separated( + itemBuilder: (_, index) { + final providers = providersByCoin[index].item2; + final coin = providersByCoin[index].item1; + + return ConditionalParent( + condition: index + 1 == providersByCoin.length, + builder: (child) => Padding( + padding: const EdgeInsets.only( + bottom: 16, + ), + child: child, ), - ) - .entries - .toList(growable: false); + child: DesktopWalletSummaryRow( + key: Key("DesktopWalletSummaryRow_key_${coin.name}"), + coin: coin, + walletCount: providers.length, + ), + ); + }, + separatorBuilder: (_, __) => const SizedBox( + height: 10, + ), + itemCount: providersByCoin.length, + ); + } +} - return TableView( - rows: [ - for (int i = 0; i < providersByCoin.length; i++) - Builder( - builder: (context) { - final providers = ref.watch(walletsChangeNotifierProvider.select( - (value) => value - .getManagerProvidersForCoin(providersByCoin[i].key))); +class DesktopWalletSummaryRow extends ConsumerStatefulWidget { + const DesktopWalletSummaryRow({ + Key? key, + required this.coin, + required this.walletCount, + }) : super(key: key); - return TableViewRow( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 16, - ), - decoration: BoxDecoration( - color: Theme.of(context).extension()!.popupBG, - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + final Coin coin; + final int walletCount; + + @override + ConsumerState createState() => + _DesktopWalletSummaryRowState(); +} + +class _DesktopWalletSummaryRowState + extends ConsumerState { + bool _hovering = false; + + void _onPressed() { + showDialog( + context: context, + builder: (_) => DesktopDialog( + maxHeight: 600, + maxWidth: 700, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "${widget.coin.prettyName} (${widget.coin.ticker}) wallets", + style: STextStyles.desktopH3(context), ), ), - cells: [ - TableViewCell( - flex: 4, - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.iconFor(coin: providersByCoin[i].key), - width: 28, - height: 28, - ), - const SizedBox( - width: 10, - ), - Text( - providersByCoin[i].key.prettyName, - style: STextStyles.desktopTextExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), - ) - ], + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: WalletsOverview( + coin: widget.coin, + navigatorState: Navigator.of(context), + ), + ), + ), + ], + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return MouseRegion( + onEnter: (_) => setState( + () => _hovering = true, + ), + onExit: (_) => setState( + () => _hovering = false, + ), + child: AnimatedScale( + scale: _hovering ? 1.00 : 0.98, + duration: const Duration( + milliseconds: 200, + ), + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(20), + hoverColor: Colors.transparent, + onPressed: _onPressed, + child: Row( + children: [ + Expanded( + flex: 4, + child: Row( + children: [ + SvgPicture.file( + File( + ref.watch(coinIconProvider(widget.coin)), + ), + width: 28, + height: 28, ), - ), - TableViewCell( - flex: 4, - child: Text( - providers.length == 1 - ? "${providers.length} wallet" - : "${providers.length} wallets", + const SizedBox( + width: 10, + ), + Text( + widget.coin.prettyName, style: STextStyles.desktopTextExtraSmall(context).copyWith( color: Theme.of(context) .extension()! - .textSubtitle1, + .textDark, ), - ), - ), - TableViewCell( - flex: 6, - child: TablePriceInfo( - coin: providersByCoin[i].key, - ), - ), - ], - expandingChild: CoinWalletsTable( - coin: providersByCoin[i].key, + ) + ], ), - ); - }, + ), + Expanded( + flex: 4, + child: Text( + widget.walletCount == 1 + ? "${widget.walletCount} wallet" + : "${widget.walletCount} wallets", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + ), + Expanded( + flex: 6, + child: TablePriceInfo( + coin: widget.coin, + ), + ), + ], ), - ], + ), + ), ); } } @@ -129,8 +211,10 @@ class TablePriceInfo extends ConsumerWidget { ), ); - final priceString = Format.localizedStringAsFixed( - value: tuple.item1, + final priceString = Amount.fromDecimal( + tuple.item1, + fractionDigits: 2, + ).localizedStringAsFixed( locale: ref .watch( localeServiceChangeNotifierProvider.notifier, diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_token_view.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_token_view.dart new file mode 100644 index 000000000..153feb92c --- /dev/null +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_token_view.dart @@ -0,0 +1,254 @@ +import 'package:event_bus/event_bus.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart'; +import 'package:stackwallet/pages/token_view/sub_widgets/token_summary.dart'; +import 'package:stackwallet/pages/token_view/sub_widgets/token_transaction_list_widget.dart'; +import 'package:stackwallet/pages/token_view/token_view.dart'; +import 'package:stackwallet/pages/wallet_view/transaction_views/all_transactions_view.dart'; +import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart'; +import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart'; +import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; +import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/icon_widgets/eth_token_icon.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +/// [eventBus] should only be set during testing +class DesktopTokenView extends ConsumerStatefulWidget { + const DesktopTokenView({ + Key? key, + required this.walletId, + this.eventBus, + }) : super(key: key); + + static const String routeName = "/desktopTokenView"; + + final String walletId; + final EventBus? eventBus; + + @override + ConsumerState createState() => _DesktopTokenViewState(); +} + +class _DesktopTokenViewState extends ConsumerState { + static const double sendReceiveColumnWidth = 460; + + late final WalletSyncStatus initialSyncStatus; + + @override + void initState() { + initialSyncStatus = ref.read(tokenServiceProvider)!.isRefreshing + ? WalletSyncStatus.syncing + : WalletSyncStatus.synced; + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + + return DesktopScaffold( + appBar: DesktopAppBar( + background: Theme.of(context).extension()!.popupBG, + leading: Expanded( + flex: 3, + child: Row( + children: [ + const SizedBox( + width: 32, + ), + SecondaryButton( + padding: const EdgeInsets.only( + left: 12, + right: 18, + ), + buttonHeight: ButtonHeight.s, + label: ref.watch( + walletsChangeNotifierProvider.select( + (value) => value.getManager(widget.walletId).walletName, + ), + ), + icon: SvgPicture.asset( + Assets.svg.arrowLeft, + width: 18, + height: 18, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, + ), + onPressed: () { + ref.refresh(feeSheetSessionCacheProvider); + Navigator.of(context).pop(); + }, + ), + const SizedBox( + width: 15, + ), + ], + ), + ), + center: Expanded( + flex: 4, + child: Row( + children: [ + EthTokenIcon( + contractAddress: ref.watch( + tokenServiceProvider.select( + (value) => value!.tokenContract.address, + ), + ), + size: 32, + ), + const SizedBox( + width: 12, + ), + Text( + ref.watch( + tokenServiceProvider.select( + (value) => value!.tokenContract.name, + ), + ), + style: STextStyles.desktopH3(context), + ), + const SizedBox( + width: 12, + ), + CoinTickerTag( + walletId: widget.walletId, + ), + ], + ), + ), + useSpacers: false, + isCompactHeight: true, + ), + body: Padding( + padding: const EdgeInsets.all(24), + child: Column( + children: [ + RoundedWhiteContainer( + padding: const EdgeInsets.all(20), + child: Row( + children: [ + EthTokenIcon( + contractAddress: ref.watch( + tokenServiceProvider.select( + (value) => value!.tokenContract.address, + ), + ), + size: 40, + ), + const SizedBox( + width: 10, + ), + DesktopWalletSummary( + walletId: widget.walletId, + isToken: true, + initialSyncStatus: ref.watch( + walletsChangeNotifierProvider.select((value) => + value.getManager(widget.walletId).isRefreshing)) + ? WalletSyncStatus.syncing + : WalletSyncStatus.synced, + ), + const Spacer(), + DesktopWalletFeatures( + walletId: widget.walletId, + ), + ], + ), + ), + const SizedBox( + height: 24, + ), + Row( + children: [ + SizedBox( + width: sendReceiveColumnWidth, + child: Text( + "My wallet", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconLeft, + ), + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Recent transactions", + style: + STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconLeft, + ), + ), + CustomTextButton( + text: "See all", + onTap: () { + Navigator.of(context).pushNamed( + AllTransactionsView.routeName, + arguments: widget.walletId, + ); + }, + ), + ], + ), + ), + ], + ), + const SizedBox( + height: 14, + ), + Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: sendReceiveColumnWidth, + child: MyWallet( + walletId: widget.walletId, + contractAddress: ref.watch( + tokenServiceProvider.select( + (value) => value!.tokenContract.address, + ), + ), + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: TokenTransactionsList( + walletId: widget.walletId, + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart index 7e57beac9..59fe4acf9 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart @@ -1,44 +1,45 @@ import 'dart:async'; +import 'dart:io'; -import 'package:decimal/decimal.dart'; import 'package:event_bus/event_bus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/notifications/show_flush_bar.dart'; -import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; -import 'package:stackwallet/pages/exchange_view/wallet_initiated_exchange_view.dart'; -import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart'; +import 'package:stackwallet/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart'; +import 'package:stackwallet/pages/token_view/my_tokens_view.dart'; +import 'package:stackwallet/pages/wallet_view/sub_widgets/transactions_list.dart'; +import 'package:stackwallet/pages/wallet_view/transaction_views/all_transactions_view.dart'; +import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/network_info_button.dart'; -import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/recent_desktop_transactions.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_button.dart'; +import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart'; import 'package:stackwallet/providers/global/auto_swb_service_provider.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/ui/transaction_filter_provider.dart'; -import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/global_event_bus.dart'; -import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; -import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/custom_loading_overlay.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/hover_text_field.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; -import 'package:stackwallet/widgets/stack_dialog.dart'; -import 'package:tuple/tuple.dart'; /// [eventBus] should only be set during testing class DesktopWalletView extends ConsumerStatefulWidget { @@ -58,12 +59,13 @@ class DesktopWalletView extends ConsumerStatefulWidget { } class _DesktopWalletViewState extends ConsumerState { + static const double sendReceiveColumnWidth = 460; + late final TextEditingController controller; late final EventBus eventBus; late final bool _shouldDisableAutoSyncOnLogOut; - - final _cnLoadingService = ExchangeDataLoadingService(); + bool _rescanningOnOpen = false; Future onBackPressed() async { await _logout(); @@ -89,198 +91,6 @@ class _DesktopWalletViewState extends ConsumerState { ref.read(managerProvider.notifier).isActiveWallet = false; } - void _loadCNData() { - // unawaited future - if (ref.read(prefsChangeNotifierProvider).externalCalls) { - _cnLoadingService.loadAll(ref, - coin: ref - .read(walletsChangeNotifierProvider) - .getManager(widget.walletId) - .coin); - } else { - Logging.instance.log("User does not want to use external calls", - level: LogLevel.Info); - } - } - - void _onExchangePressed(BuildContext context) async { - final managerProvider = ref - .read(walletsChangeNotifierProvider) - .getManagerProvider(widget.walletId); - unawaited(_cnLoadingService.loadAll(ref)); - - final coin = ref.read(managerProvider).coin; - - if (coin == Coin.epicCash) { - await showDialog( - context: context, - builder: (_) => const StackOkDialog( - title: "Exchange not available for Epic Cash", - ), - ); - } else if (coin.name.endsWith("TestNet")) { - await showDialog( - context: context, - builder: (_) => const StackOkDialog( - title: "Exchange not available for test net coins", - ), - ); - } else { - ref.read(currentExchangeNameStateProvider.state).state = - ChangeNowExchange.exchangeName; - ref.read(prefsChangeNotifierProvider).exchangeRateType = - ExchangeRateType.estimated; - - ref.read(exchangeFormStateProvider).exchange = ref.read(exchangeProvider); - ref.read(exchangeFormStateProvider).exchangeType = - ExchangeRateType.estimated; - - final currencies = ref - .read(availableChangeNowCurrenciesProvider) - .currencies - .where((element) => - element.ticker.toLowerCase() == coin.ticker.toLowerCase()); - - if (currencies.isNotEmpty) { - ref.read(exchangeFormStateProvider).setCurrencies( - currencies.first, - ref - .read(availableChangeNowCurrenciesProvider) - .currencies - .firstWhere( - (element) => - element.ticker.toLowerCase() != - coin.ticker.toLowerCase(), - ), - ); - } - - if (mounted) { - unawaited( - Navigator.of(context).pushNamed( - WalletInitiatedExchangeView.routeName, - arguments: Tuple3( - widget.walletId, - coin, - _loadCNData, - ), - ), - ); - } - } - } - - Future attemptAnonymize() async { - final managerProvider = ref - .read(walletsChangeNotifierProvider) - .getManagerProvider(widget.walletId); - - bool shouldPop = false; - unawaited( - showDialog( - context: context, - builder: (context) => WillPopScope( - child: const CustomLoadingOverlay( - message: "Anonymizing balance", - eventBus: null, - ), - onWillPop: () async => shouldPop, - ), - ), - ); - final firoWallet = ref.read(managerProvider).wallet as FiroWallet; - - final publicBalance = await firoWallet.availablePublicBalance(); - if (publicBalance <= Decimal.zero) { - shouldPop = true; - if (mounted) { - Navigator.of(context, rootNavigator: true).pop(); - Navigator.of(context).popUntil( - ModalRoute.withName(DesktopWalletView.routeName), - ); - unawaited( - showFloatingFlushBar( - type: FlushBarType.info, - message: "No funds available to anonymize!", - context: context, - ), - ); - } - return; - } - - try { - await firoWallet.anonymizeAllPublicFunds(); - shouldPop = true; - if (mounted) { - Navigator.of(context, rootNavigator: true).pop(); - Navigator.of(context).popUntil( - ModalRoute.withName(DesktopWalletView.routeName), - ); - unawaited( - showFloatingFlushBar( - type: FlushBarType.success, - message: "Anonymize transaction submitted", - context: context, - ), - ); - } - } catch (e) { - shouldPop = true; - if (mounted) { - Navigator.of(context, rootNavigator: true).pop(); - Navigator.of(context).popUntil( - ModalRoute.withName(DesktopWalletView.routeName), - ); - await showDialog( - context: context, - builder: (_) => DesktopDialog( - maxWidth: 400, - maxHeight: 300, - child: Padding( - padding: const EdgeInsets.all(24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Anonymize all failed", - style: STextStyles.desktopH3(context), - ), - const Spacer( - flex: 1, - ), - Text( - "Reason: $e", - style: STextStyles.desktopTextSmall(context), - ), - const Spacer( - flex: 2, - ), - Row( - children: [ - const Spacer(), - const SizedBox( - width: 16, - ), - Expanded( - child: PrimaryButton( - label: "Ok", - buttonHeight: ButtonHeight.l, - onPressed: - Navigator.of(context, rootNavigator: true).pop, - ), - ), - ], - ) - ], - ), - ), - ), - ); - } - } - } - @override void initState() { controller = TextEditingController(); @@ -302,7 +112,19 @@ class _DesktopWalletViewState extends ConsumerState { _shouldDisableAutoSyncOnLogOut = false; } - ref.read(managerProvider).refresh(); + if (ref.read(managerProvider).coin != Coin.ethereum && + ref.read(managerProvider).rescanOnOpenVersion == Constants.rescanV1) { + _rescanningOnOpen = true; + ref.read(managerProvider).fullRescan(20, 1000).then( + (_) => ref.read(managerProvider).resetRescanOnOpen().then( + (_) => WidgetsBinding.instance.addPostFrameCallback( + (_) => setState(() => _rescanningOnOpen = false), + ), + ), + ); + } else { + ref.read(managerProvider).refresh(); + } super.initState(); } @@ -321,224 +143,323 @@ class _DesktopWalletViewState extends ConsumerState { final managerProvider = ref.watch(walletsChangeNotifierProvider .select((value) => value.getManagerProvider(widget.walletId))); - return DesktopScaffold( - appBar: DesktopAppBar( - background: Theme.of(context).extension()!.popupBG, - leading: Expanded( - child: Row( - children: [ - const SizedBox( - width: 32, - ), - AppBarIconButton( - size: 32, - color: Theme.of(context) - .extension()! - .textFieldDefaultBG, - shadows: const [], - icon: SvgPicture.asset( - Assets.svg.arrowLeft, - width: 18, - height: 18, - color: Theme.of(context) - .extension()! - .topNavIconPrimary, - ), - onPressed: onBackPressed, - ), - const SizedBox( - width: 15, - ), - SvgPicture.asset( - Assets.svg.iconFor(coin: coin), - width: 32, - height: 32, - ), - const SizedBox( - width: 12, - ), - ConstrainedBox( - constraints: const BoxConstraints( - minWidth: 48, - ), - child: IntrinsicWidth( - child: DesktopWalletNameField( - walletId: widget.walletId, - ), - ), - ), - const Spacer(), - Row( - children: [ - NetworkInfoButton( - walletId: widget.walletId, - eventBus: eventBus, - ), - const SizedBox( - width: 2, - ), - WalletKeysButton( - walletId: widget.walletId, - ), - const SizedBox( - width: 2, - ), - DeleteWalletButton( - walletId: widget.walletId, - ), - const SizedBox( - width: 12, - ), - ], - ), - ], - ), - ), - useSpacers: false, - isCompactHeight: true, - ), - body: Padding( - padding: const EdgeInsets.all(24), - child: Column( + return ConditionalParent( + condition: _rescanningOnOpen, + builder: (child) { + return Stack( children: [ - RoundedWhiteContainer( - padding: const EdgeInsets.all(20), - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.iconFor(coin: coin), - width: 40, - height: 40, - ), - const SizedBox( - width: 10, - ), - DesktopWalletSummary( - walletId: widget.walletId, - managerProvider: managerProvider, - initialSyncStatus: ref.watch(managerProvider - .select((value) => value.isRefreshing)) - ? WalletSyncStatus.syncing - : WalletSyncStatus.synced, - ), - const Spacer(), - if (coin == Coin.firo) const SizedBox(width: 10), - if (coin == Coin.firo) - SecondaryButton( - width: 180, - buttonHeight: ButtonHeight.l, - label: "Anonymize funds", - onPressed: () async { - await showDialog( - context: context, - barrierDismissible: false, - builder: (context) => DesktopDialog( - maxWidth: 500, - maxHeight: 210, - child: Padding( - padding: const EdgeInsets.symmetric( - horizontal: 32, vertical: 20), - child: Column( + child, + Background( + child: CustomLoadingOverlay( + message: + "Migration in progress\nThis could take a while\nPlease don't leave this screen", + subMessage: "This only needs to run once per wallet", + eventBus: null, + textColor: Theme.of(context).extension()!.textDark, + actionButton: SecondaryButton( + label: "Skip", + buttonHeight: ButtonHeight.l, + onPressed: () async { + await showDialog( + context: context, + builder: (context) => DesktopDialog( + maxWidth: 500, + maxHeight: double.infinity, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, children: [ Text( - "Attention!", - style: STextStyles.desktopH2(context), - ), - const SizedBox(height: 16), - Text( - "You're about to anonymize all of your public funds.", - style: - STextStyles.desktopTextSmall(context), - ), - const SizedBox(height: 32), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SecondaryButton( - width: 200, - buttonHeight: ButtonHeight.l, - label: "Cancel", - onPressed: () { - Navigator.of(context).pop(); - }, - ), - const SizedBox(width: 20), - PrimaryButton( - width: 200, - buttonHeight: ButtonHeight.l, - label: "Continue", - onPressed: () { - Navigator.of(context).pop(); - - unawaited(attemptAnonymize()); - }, - ) - ], + "Warning!", + style: STextStyles.desktopH3(context), ), + const DesktopDialogCloseButton(), ], ), ), - ), - ); - }, - ), - // if (coin == Coin.firo) const SizedBox(width: 16), - // SecondaryButton( - // width: 180, - // buttonHeight: ButtonHeight.l, - // onPressed: () { - // _onExchangePressed(context); - // }, - // label: "Exchange", - // icon: Container( - // width: 24, - // height: 24, - // decoration: BoxDecoration( - // borderRadius: BorderRadius.circular(24), - // color: Theme.of(context) - // .extension()! - // .buttonBackPrimary - // .withOpacity(0.2), - // ), - // child: Center( - // child: SvgPicture.asset( - // Assets.svg.arrowRotate2, - // width: 14, - // height: 14, - // color: Theme.of(context) - // .extension()! - // .buttonTextSecondary, - // ), - // ), - // ), - // ), - ], + Padding( + padding: + const EdgeInsets.symmetric(horizontal: 32), + child: Text( + "Skipping this process can completely" + " break your wallet. It is only meant to be done in" + " emergency situations where the migration fails" + " and will not let you continue. Still skip?", + style: STextStyles.desktopTextSmall(context), + ), + ), + const SizedBox( + height: 32, + ), + Padding( + padding: const EdgeInsets.all(32), + child: Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + buttonHeight: ButtonHeight.l, + onPressed: Navigator.of(context, + rootNavigator: true) + .pop, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Ok", + buttonHeight: ButtonHeight.l, + onPressed: () { + Navigator.of(context, + rootNavigator: true) + .pop(); + setState( + () => _rescanningOnOpen = false); + }, + ), + ), + ], + ), + ) + ], + ), + ), + ); + }, + ), ), + ) + ], + ); + }, + child: DesktopScaffold( + appBar: DesktopAppBar( + background: Theme.of(context).extension()!.popupBG, + leading: Expanded( + child: Row( + children: [ + const SizedBox( + width: 32, + ), + AppBarIconButton( + size: 32, + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + shadows: const [], + icon: SvgPicture.asset( + Assets.svg.arrowLeft, + width: 18, + height: 18, + color: Theme.of(context) + .extension()! + .topNavIconPrimary, + ), + onPressed: onBackPressed, + ), + const SizedBox( + width: 15, + ), + SvgPicture.file( + File( + ref.watch(coinIconProvider(coin)), + ), + width: 32, + height: 32, + ), + const SizedBox( + width: 12, + ), + ConstrainedBox( + constraints: const BoxConstraints( + minWidth: 48, + ), + child: IntrinsicWidth( + child: DesktopWalletNameField( + walletId: widget.walletId, + ), + ), + ), + const Spacer(), + Row( + children: [ + NetworkInfoButton( + walletId: widget.walletId, + eventBus: eventBus, + ), + const SizedBox( + width: 2, + ), + WalletKeysButton( + walletId: widget.walletId, + ), + const SizedBox( + width: 2, + ), + WalletOptionsButton( + walletId: widget.walletId, + ), + const SizedBox( + width: 12, + ), + ], + ), + ], ), - const SizedBox( - height: 24, - ), - Expanded( - child: Row( + ), + useSpacers: false, + isCompactHeight: true, + ), + body: Padding( + padding: const EdgeInsets.all(24), + child: Column( + children: [ + RoundedWhiteContainer( + padding: const EdgeInsets.all(20), + child: Row( + children: [ + SvgPicture.file( + File( + ref.watch(coinIconProvider(coin)), + ), + width: 40, + height: 40, + ), + const SizedBox( + width: 10, + ), + DesktopWalletSummary( + walletId: widget.walletId, + initialSyncStatus: ref.watch(managerProvider + .select((value) => value.isRefreshing)) + ? WalletSyncStatus.syncing + : WalletSyncStatus.synced, + ), + const Spacer(), + DesktopWalletFeatures( + walletId: widget.walletId, + ), + ], + ), + ), + const SizedBox( + height: 24, + ), + Row( children: [ SizedBox( - width: 450, - child: MyWallet( - walletId: widget.walletId, + width: sendReceiveColumnWidth, + child: Text( + "My wallet", + style: + STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconLeft, + ), ), ), const SizedBox( width: 16, ), Expanded( - child: RecentDesktopTransactions( - walletId: widget.walletId, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + ref.watch(walletsChangeNotifierProvider.select( + (value) => value + .getManager(widget.walletId) + .hasTokenSupport)) + ? "Tokens" + : "Recent transactions", + style: STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconLeft, + ), + ), + CustomTextButton( + text: ref.watch(walletsChangeNotifierProvider.select( + (value) => value + .getManager(widget.walletId) + .hasTokenSupport)) + ? "Edit" + : "See all", + onTap: ref.watch(walletsChangeNotifierProvider.select( + (value) => value + .getManager(widget.walletId) + .hasTokenSupport)) + ? () async { + final result = await showDialog( + context: context, + builder: (context) => EditWalletTokensView( + walletId: widget.walletId, + isDesktopPopup: true, + ), + ); + + if (result == 42) { + // wallet tokens were edited so update ui + setState(() {}); + } + } + : () { + Navigator.of(context).pushNamed( + AllTransactionsView.routeName, + arguments: widget.walletId, + ); + }, + ), + ], ), ), ], ), - ), - ], + const SizedBox( + height: 14, + ), + Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: sendReceiveColumnWidth, + child: MyWallet( + walletId: widget.walletId, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: ref.watch(walletsChangeNotifierProvider.select( + (value) => value + .getManager(widget.walletId) + .hasTokenSupport)) + ? MyTokensView( + walletId: widget.walletId, + ) + : TransactionsList( + managerProvider: ref.watch( + walletsChangeNotifierProvider.select( + (value) => value.getManagerProvider( + widget.walletId))), + walletId: widget.walletId, + ), + ), + ], + ), + ), + ], + ), ), ), ); diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart index 994624d94..1078bea5d 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart @@ -1,14 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:stackwallet/models/contact.dart'; +import 'package:stackwallet/models/isar/models/contact_entry.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/sub_widgets/contact_list_item.dart'; import 'package:stackwallet/providers/global/address_book_service_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; @@ -34,7 +34,7 @@ class _AddressBookAddressChooserState extends State { String _searchTerm = ""; - int _compareContactFavorite(Contact a, Contact b) { + int _compareContactFavorite(ContactEntry a, ContactEntry b) { if (a.isFavorite && b.isFavorite) { return 0; } else if (a.isFavorite) { @@ -44,8 +44,8 @@ class _AddressBookAddressChooserState extends State { } } - List pullOutFavorites(List contacts) { - final List favorites = []; + List pullOutFavorites(List contacts) { + final List favorites = []; contacts.removeWhere((contact) { if (contact.isFavorite) { favorites.add(contact); @@ -57,7 +57,7 @@ class _AddressBookAddressChooserState extends State { return favorites; } - List filter(List contacts, String searchTerm) { + List filter(List contacts, String searchTerm) { if (widget.coin != null) { contacts.removeWhere( (e) => e.addresses.where((a) => a.coin == widget.coin!).isEmpty); @@ -75,7 +75,7 @@ class _AddressBookAddressChooserState extends State { return contacts; } - bool _matches(String term, Contact contact) { + bool _matches(String term, ContactEntry contact) { final text = term.toLowerCase(); if (contact.name.toLowerCase().contains(text)) { return true; @@ -191,7 +191,7 @@ class _AddressBookAddressChooserState extends State { ), child: Consumer( builder: (context, ref, _) { - List contacts = ref + List contacts = ref .watch(addressBookServiceProvider .select((value) => value.contacts)) .toList(); @@ -228,7 +228,7 @@ class _AddressBookAddressChooserState extends State { ), ); } else if (index < favorites.length + 1) { - final id = favorites[index - 1].id; + final id = favorites[index - 1].customId; return ContactListItem( key: Key("contactContactListItem_${id}_key"), contactId: id, @@ -248,7 +248,8 @@ class _AddressBookAddressChooserState extends State { ), ); } else { - final id = contacts[index - favorites.length - 2].id; + final id = + contacts[index - favorites.length - 2].customId; return ContactListItem( key: Key("contactContactListItem_${id}_key"), contactId: id, diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/sub_widgets/contact_list_item.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/sub_widgets/contact_list_item.dart index d7bfefb1f..e18070686 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/sub_widgets/contact_list_item.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/sub_widgets/contact_list_item.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/providers/global/address_book_service_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/address_book_card.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/expandable.dart'; @@ -42,6 +42,9 @@ class _ContactListItemState extends ConsumerState { final contact = ref.watch(addressBookServiceProvider .select((value) => value.getContactById(contactId))); + // hack fix until we use a proper database (not Hive) + int i = 0; + return RoundedWhiteContainer( padding: const EdgeInsets.all(0), borderColor: Theme.of(context).extension()!.background, @@ -70,7 +73,8 @@ class _ContactListItemState extends ConsumerState { filterByCoin != null ? e.coin == filterByCoin! : true) .map( (e) => Column( - key: Key("contactAddress_${e.address}_${e.label}_key"), + key: Key( + "contactAddress_${e.address}_${e.label}_${++i}_key"), mainAxisSize: MainAxisSize.min, children: [ Container( @@ -129,7 +133,7 @@ class _ContactListItemState extends ConsumerState { ], ), ), - BlueTextButton( + CustomTextButton( text: "Select wallet", onTap: () { Navigator.of(context).pop(e); diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart deleted file mode 100644 index 005c230f3..000000000 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/delete_wallet_button.dart +++ /dev/null @@ -1,164 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart'; -import 'package:stackwallet/route_generator.dart'; -import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; - -class DeleteWalletButton extends ConsumerStatefulWidget { - const DeleteWalletButton({ - Key? key, - required this.walletId, - }) : super(key: key); - - final String walletId; - - @override - ConsumerState createState() => _DeleteWalletButton(); -} - -class _DeleteWalletButton extends ConsumerState { - late final String walletId; - - @override - void initState() { - walletId = widget.walletId; - - super.initState(); - } - - @override - Widget build(BuildContext context) { - return RawMaterialButton( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(1000), - ), - onPressed: () async { - final shouldOpenDeleteDialog = await showDialog( - context: context, - barrierColor: Colors.transparent, - builder: (context) { - return DeletePopupButton( - onTap: () async { - Navigator.of(context).pop(true); - }, - ); - }, - ); - - if (shouldOpenDeleteDialog == true) { - final result = await showDialog( - context: context, - barrierDismissible: false, - builder: (context) => Navigator( - initialRoute: DesktopDeleteWalletDialog.routeName, - onGenerateRoute: RouteGenerator.generateRoute, - onGenerateInitialRoutes: (_, __) { - return [ - RouteGenerator.generateRoute( - RouteSettings( - name: DesktopDeleteWalletDialog.routeName, - arguments: walletId, - ), - ), - ]; - }, - ), - ); - - if (result == true) { - if (mounted) { - Navigator.of(context).pop(); - } - } - } - }, - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 19, - horizontal: 32, - ), - child: Row( - children: [ - SvgPicture.asset( - Assets.svg.ellipsis, - width: 20, - height: 20, - color: Theme.of(context) - .extension()! - .buttonTextSecondary, - ), - ], - ), - ), - ); - } -} - -class DeletePopupButton extends StatefulWidget { - const DeletePopupButton({ - Key? key, - this.onTap, - }) : super(key: key); - - final VoidCallback? onTap; - - @override - State createState() => _DeletePopupButtonState(); -} - -class _DeletePopupButtonState extends State { - @override - Widget build(BuildContext context) { - return Stack( - children: [ - Positioned( - top: 24, - left: MediaQuery.of(context).size.width - 234, - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: widget.onTap, - child: Container( - width: 210, - height: 70, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius * 2, - ), - color: Theme.of(context).extension()!.popupBG, - boxShadow: [ - Theme.of(context) - .extension()! - .standardBoxShadow, - ], - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - const SizedBox(width: 24), - SvgPicture.asset( - Assets.svg.trash, - ), - const SizedBox(width: 14), - Text( - "Delete wallet", - style: STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark), - ), - ], - ), - ), - ), - ), - ), - ], - ); - } -} diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart index a2d58465b..006b5141c 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart @@ -8,10 +8,10 @@ import 'package:stackwallet/pages/add_wallet_views/new_wallet_recovery_phrase_vi import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/providers/global/wallets_service_provider.dart'; import 'package:stackwallet/route_generator.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart index febb9f44d..06ddb59d5 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; @@ -66,7 +66,7 @@ class _DesktopAttentionDeleteWallet child: Padding( padding: const EdgeInsets.all(10.0), child: Text( - "You are going to permanently delete you wallet.\n\nIf you delete your wallet, " + "You are going to permanently delete your wallet.\n\nIf you delete your wallet, " "the only way you can have access to your funds is by using your backup key." "\n\nStack Wallet does not keep nor is able to restore your backup key or your wallet." "\n\nPLEASE SAVE YOUR BACKUP KEY.", @@ -74,7 +74,7 @@ class _DesktopAttentionDeleteWallet .copyWith( color: Theme.of(context) .extension()! - .textDark3, + .snackBarTextError, ), ), ), diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart index 20bb3f95a..0ee5d9ff1 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_auth_send.dart @@ -4,11 +4,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_balance_toggle_button.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_balance_toggle_button.dart index 9c890b223..aaaa306a7 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_balance_toggle_button.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_balance_toggle_button.dart @@ -1,10 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/providers/wallet/wallet_balance_toggle_state_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; class DesktopBalanceToggleButton extends ConsumerWidget { const DesktopBalanceToggleButton({ @@ -18,7 +19,7 @@ class DesktopBalanceToggleButton extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { return SizedBox( height: 22, - width: 22, + width: 80, child: MaterialButton( color: Theme.of(context).extension()!.buttonBackSecondary, splashColor: Theme.of(context).extension()!.highlight, @@ -43,10 +44,63 @@ class DesktopBalanceToggleButton extends ConsumerWidget { Constants.size.circularBorderRadius, ), ), + child: Center( + child: FittedBox( + child: Text( + ref.watch(walletBalanceToggleStateProvider.state).state == + WalletBalanceToggleState.available + ? "AVAILABLE" + : "FULL", + style: STextStyles.w500_10(context), + ), + ), + ), + ), + ); + } +} + +class DesktopPrivateBalanceToggleButton extends ConsumerWidget { + const DesktopPrivateBalanceToggleButton({ + Key? key, + this.onPressed, + }) : super(key: key); + + final VoidCallback? onPressed; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return SizedBox( + height: 22, + width: 22, + child: MaterialButton( + color: Theme.of(context).extension()!.buttonBackSecondary, + splashColor: Theme.of(context).extension()!.highlight, + onPressed: () { + if (ref.read(walletPrivateBalanceToggleStateProvider.state).state == + WalletBalanceToggleState.available) { + ref.read(walletPrivateBalanceToggleStateProvider.state).state = + WalletBalanceToggleState.full; + } else { + ref.read(walletPrivateBalanceToggleStateProvider.state).state = + WalletBalanceToggleState.available; + } + onPressed?.call(); + }, + elevation: 0, + highlightElevation: 0, + hoverElevation: 0, + padding: EdgeInsets.zero, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), child: Center( child: Image( image: AssetImage( - ref.watch(walletBalanceToggleStateProvider.state).state == + ref.watch(walletPrivateBalanceToggleStateProvider.state).state == WalletBalanceToggleState.available ? Assets.png.glassesHidden : Assets.png.glasses, diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart index 56d8a0889..bd9e1f03d 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart @@ -6,10 +6,10 @@ import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart'; import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart index 25e1f47ab..cabe94d81 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart @@ -1,31 +1,38 @@ import 'package:cw_core/monero_transaction_priority.dart'; -import 'package:decimal/decimal.dart'; import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/models/models.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart'; +import 'package:stackwallet/pages/token_view/token_view.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart'; import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; -import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/animated_text.dart'; +final tokenFeeSessionCacheProvider = + ChangeNotifierProvider((ref) { + return FeeSheetSessionCache(); +}); + class DesktopFeeDropDown extends ConsumerStatefulWidget { const DesktopFeeDropDown({ Key? key, required this.walletId, + this.isToken = false, }) : super(key: key); final String walletId; + final bool isToken; @override ConsumerState createState() => _DesktopFeeDropDownState(); @@ -44,102 +51,123 @@ class _DesktopFeeDropDownState extends ConsumerState { "Calculating...", ]; - Future feeFor({ - required int amount, + Future feeFor({ + required Amount amount, required FeeRateType feeRateType, required int feeRate, required Coin coin, }) async { switch (feeRateType) { case FeeRateType.fast: - if (ref.read(feeSheetSessionCacheProvider).fast[amount] == null) { - final manager = - ref.read(walletsChangeNotifierProvider).getManager(walletId); + if (ref + .read(widget.isToken + ? tokenFeeSessionCacheProvider + : feeSheetSessionCacheProvider) + .fast[amount] == + null) { + if (widget.isToken == false) { + final manager = + ref.read(walletsChangeNotifierProvider).getManager(walletId); - if (coin == Coin.monero || coin == Coin.wownero) { - final fee = await manager.estimateFeeFor( - amount, MoneroTransactionPriority.fast.raw!); - ref.read(feeSheetSessionCacheProvider).fast[amount] = - Format.satoshisToAmount( - fee, - coin: coin, - ); - } else if ((coin == Coin.firo || coin == Coin.firoTestNet) && - ref.read(publicPrivateBalanceStateProvider.state).state != - "Private") { - ref.read(feeSheetSessionCacheProvider).fast[amount] = - Format.satoshisToAmount( - await (manager.wallet as FiroWallet) - .estimateFeeForPublic(amount, feeRate), - coin: coin); + if (coin == Coin.monero || coin == Coin.wownero) { + final fee = await manager.estimateFeeFor( + amount, MoneroTransactionPriority.fast.raw!); + ref.read(feeSheetSessionCacheProvider).fast[amount] = fee; + } else if ((coin == Coin.firo || coin == Coin.firoTestNet) && + ref.read(publicPrivateBalanceStateProvider.state).state != + "Private") { + ref.read(feeSheetSessionCacheProvider).fast[amount] = + await (manager.wallet as FiroWallet) + .estimateFeeForPublic(amount, feeRate); + } else { + ref.read(feeSheetSessionCacheProvider).fast[amount] = + await manager.estimateFeeFor(amount, feeRate); + } } else { - ref.read(feeSheetSessionCacheProvider).fast[amount] = - Format.satoshisToAmount( - await manager.estimateFeeFor(amount, feeRate), - coin: coin); + final tokenWallet = ref.read(tokenServiceProvider)!; + final fee = tokenWallet.estimateFeeFor(feeRate); + ref.read(tokenFeeSessionCacheProvider).fast[amount] = fee; } } - return ref.read(feeSheetSessionCacheProvider).fast[amount]!; + return ref + .read(widget.isToken + ? tokenFeeSessionCacheProvider + : feeSheetSessionCacheProvider) + .fast[amount]!; case FeeRateType.average: - if (ref.read(feeSheetSessionCacheProvider).average[amount] == null) { - final manager = - ref.read(walletsChangeNotifierProvider).getManager(walletId); + if (ref + .read(widget.isToken + ? tokenFeeSessionCacheProvider + : feeSheetSessionCacheProvider) + .average[amount] == + null) { + if (widget.isToken == false) { + final manager = + ref.read(walletsChangeNotifierProvider).getManager(walletId); - if (coin == Coin.monero || coin == Coin.wownero) { - final fee = await manager.estimateFeeFor( - amount, MoneroTransactionPriority.regular.raw!); - ref.read(feeSheetSessionCacheProvider).average[amount] = - Format.satoshisToAmount( - fee, - coin: coin, - ); - } else if ((coin == Coin.firo || coin == Coin.firoTestNet) && - ref.read(publicPrivateBalanceStateProvider.state).state != - "Private") { - ref.read(feeSheetSessionCacheProvider).average[amount] = - Format.satoshisToAmount( - await (manager.wallet as FiroWallet) - .estimateFeeForPublic(amount, feeRate), - coin: coin); + if (coin == Coin.monero || coin == Coin.wownero) { + final fee = await manager.estimateFeeFor( + amount, MoneroTransactionPriority.regular.raw!); + ref.read(feeSheetSessionCacheProvider).average[amount] = fee; + } else if ((coin == Coin.firo || coin == Coin.firoTestNet) && + ref.read(publicPrivateBalanceStateProvider.state).state != + "Private") { + ref.read(feeSheetSessionCacheProvider).average[amount] = + await (manager.wallet as FiroWallet) + .estimateFeeForPublic(amount, feeRate); + } else { + ref.read(feeSheetSessionCacheProvider).average[amount] = + await manager.estimateFeeFor(amount, feeRate); + } } else { - ref.read(feeSheetSessionCacheProvider).average[amount] = - Format.satoshisToAmount( - await manager.estimateFeeFor(amount, feeRate), - coin: coin); + final tokenWallet = ref.read(tokenServiceProvider)!; + final fee = tokenWallet.estimateFeeFor(feeRate); + ref.read(tokenFeeSessionCacheProvider).average[amount] = fee; } } - return ref.read(feeSheetSessionCacheProvider).average[amount]!; + return ref + .read(widget.isToken + ? tokenFeeSessionCacheProvider + : feeSheetSessionCacheProvider) + .average[amount]!; case FeeRateType.slow: - if (ref.read(feeSheetSessionCacheProvider).slow[amount] == null) { - final manager = - ref.read(walletsChangeNotifierProvider).getManager(walletId); + if (ref + .read(widget.isToken + ? tokenFeeSessionCacheProvider + : feeSheetSessionCacheProvider) + .slow[amount] == + null) { + if (widget.isToken == false) { + final manager = + ref.read(walletsChangeNotifierProvider).getManager(walletId); - if (coin == Coin.monero || coin == Coin.wownero) { - final fee = await manager.estimateFeeFor( - amount, MoneroTransactionPriority.slow.raw!); - ref.read(feeSheetSessionCacheProvider).slow[amount] = - Format.satoshisToAmount( - fee, - coin: coin, - ); - } else if ((coin == Coin.firo || coin == Coin.firoTestNet) && - ref.read(publicPrivateBalanceStateProvider.state).state != - "Private") { - ref.read(feeSheetSessionCacheProvider).slow[amount] = - Format.satoshisToAmount( - await (manager.wallet as FiroWallet) - .estimateFeeForPublic(amount, feeRate), - coin: coin); + if (coin == Coin.monero || coin == Coin.wownero) { + final fee = await manager.estimateFeeFor( + amount, MoneroTransactionPriority.slow.raw!); + ref.read(feeSheetSessionCacheProvider).slow[amount] = fee; + } else if ((coin == Coin.firo || coin == Coin.firoTestNet) && + ref.read(publicPrivateBalanceStateProvider.state).state != + "Private") { + ref.read(feeSheetSessionCacheProvider).slow[amount] = + await (manager.wallet as FiroWallet) + .estimateFeeForPublic(amount, feeRate); + } else { + ref.read(feeSheetSessionCacheProvider).slow[amount] = + await manager.estimateFeeFor(amount, feeRate); + } } else { - ref.read(feeSheetSessionCacheProvider).slow[amount] = - Format.satoshisToAmount( - await manager.estimateFeeFor(amount, feeRate), - coin: coin); + final tokenWallet = ref.read(tokenServiceProvider)!; + final fee = tokenWallet.estimateFeeFor(feeRate); + ref.read(tokenFeeSessionCacheProvider).slow[amount] = fee; } } - return ref.read(feeSheetSessionCacheProvider).slow[amount]!; + return ref + .read(widget.isToken + ? tokenFeeSessionCacheProvider + : feeSheetSessionCacheProvider) + .slow[amount]!; } } @@ -242,7 +270,7 @@ class _DesktopFeeDropDownState extends ConsumerState { } final sendAmountProvider = - StateProvider.autoDispose((_) => Decimal.zero); + StateProvider.autoDispose((_) => Amount.zero); class FeeDropDownChild extends ConsumerWidget { const FeeDropDownChild({ @@ -257,8 +285,8 @@ class FeeDropDownChild extends ConsumerWidget { final FeeObject? feeObject; final FeeRateType feeRateType; final String walletId; - final Future Function({ - required int amount, + final Future Function({ + required Amount amount, required FeeRateType feeRateType, required int feeRate, required Coin coin, @@ -322,19 +350,20 @@ class FeeDropDownChild extends ConsumerWidget { : feeRateType == FeeRateType.slow ? feeObject!.slow : feeObject!.medium, - amount: Format.decimalAmountToSatoshis( - ref.watch(sendAmountProvider.state).state, - manager.coin, - ), + amount: ref.watch(sendAmountProvider.state).state, ), - builder: (_, AsyncSnapshot snapshot) { + builder: (_, AsyncSnapshot snapshot) { if (snapshot.connectionState == ConnectionState.done && snapshot.hasData) { return Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( - "${feeRateType.prettyName} (~${snapshot.data!} ${manager.coin.ticker})", + "${feeRateType.prettyName} " + "(~${snapshot.data!.decimal.toStringAsFixed( + manager.coin.decimals, + )} " + "${manager.coin.ticker})", style: STextStyles.desktopTextExtraExtraSmall(context).copyWith( color: Theme.of(context) @@ -345,14 +374,16 @@ class FeeDropDownChild extends ConsumerWidget { ), if (feeObject != null) Text( - estimatedTimeToBeIncludedInNextBlock( - Constants.targetBlockTimeInSeconds(manager.coin), - feeRateType == FeeRateType.fast - ? feeObject!.numberOfBlocksFast - : feeRateType == FeeRateType.slow - ? feeObject!.numberOfBlocksSlow - : feeObject!.numberOfBlocksAverage, - ), + manager.coin == Coin.ethereum + ? "" + : estimatedTimeToBeIncludedInNextBlock( + Constants.targetBlockTimeInSeconds(manager.coin), + feeRateType == FeeRateType.fast + ? feeObject!.numberOfBlocksFast + : feeRateType == FeeRateType.slow + ? feeObject!.numberOfBlocksSlow + : feeObject!.numberOfBlocksAverage, + ), style: STextStyles.desktopTextExtraExtraSmall(context) .copyWith( color: Theme.of(context) diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart index 064c31a08..11e74ce5a 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart @@ -7,15 +7,15 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/receive_view/generate_receiving_uri_qr_code_view.dart'; +import 'package:stackwallet/pages/token_view/token_view.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/route_generator.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/custom_loading_overlay.dart'; @@ -28,10 +28,12 @@ class DesktopReceive extends ConsumerStatefulWidget { const DesktopReceive({ Key? key, required this.walletId, + this.contractAddress, this.clipboard = const ClipboardWrapper(), }) : super(key: key); final String walletId; + final String? contractAddress; final ClipboardInterface clipboard; @override @@ -149,7 +151,11 @@ class _DesktopReceiveState extends ConsumerState { Row( children: [ Text( - "Your ${coin.ticker} address", + "Your ${widget.contractAddress == null ? coin.ticker : ref.watch( + tokenServiceProvider.select( + (value) => value!.tokenContract.symbol, + ), + )} address", style: STextStyles.itemSubtitle(context), ), const Spacer(), @@ -199,11 +205,11 @@ class _DesktopReceiveState extends ConsumerState { ), ), ), - if (coin != Coin.epicCash) + if (coin != Coin.epicCash && coin != Coin.ethereum) const SizedBox( height: 20, ), - if (coin != Coin.epicCash) + if (coin != Coin.epicCash && coin != Coin.ethereum) SecondaryButton( buttonHeight: ButtonHeight.l, onPressed: generateNewAddress, diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart index f1b967471..19854a017 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:bip47/bip47.dart'; import 'package:decimal/decimal.dart'; import 'package:dropdown_button2/dropdown_button2.dart'; import 'package:flutter/material.dart'; @@ -7,10 +8,12 @@ import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stackwallet/models/contact_address_entry.dart'; +import 'package:stackwallet/models/paynym/paynym_account_lite.dart'; import 'package:stackwallet/models/send_view_auto_fill_data.dart'; import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dialog.dart'; import 'package:stackwallet/pages/send_view/sub_widgets/transaction_fee_selection_sheet.dart'; +import 'package:stackwallet/pages_desktop_specific/coin_control/desktop_coin_control_use_dialog.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart'; @@ -20,17 +23,18 @@ import 'package:stackwallet/providers/ui/preview_tx_button_state_provider.dart'; import 'package:stackwallet/providers/wallet/public_private_balance_state_provider.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; import 'package:stackwallet/services/coins/manager.dart'; +import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/address_utils.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/barcode_scanner_interface.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/animated_text.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; @@ -41,6 +45,7 @@ import 'package:stackwallet/widgets/desktop/secondary_button.dart'; import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; @@ -51,12 +56,14 @@ class DesktopSend extends ConsumerStatefulWidget { this.autoFillData, this.clipboard = const ClipboardWrapper(), this.barcodeScanner = const BarcodeScannerWrapper(), + this.accountLite, }) : super(key: key); final String walletId; final SendViewAutoFillData? autoFillData; final ClipboardInterface clipboard; final BarcodeScannerInterface barcodeScanner; + final PaynymAccountLite? accountLite; @override ConsumerState createState() => _DesktopSendState(); @@ -81,8 +88,8 @@ class _DesktopSendState extends ConsumerState { String? _note; - Decimal? _amountToSend; - Decimal? _cachedAmountToSend; + Amount? _amountToSend; + Amount? _cachedAmountToSend; String? _address; String? _privateBalanceString; @@ -93,239 +100,234 @@ class _DesktopSendState extends ConsumerState { bool _cryptoAmountChangeLock = false; late VoidCallback onCryptoAmountChanged; + bool get isPaynymSend => widget.accountLite != null; + Future previewSend() async { final manager = ref.read(walletsChangeNotifierProvider).getManager(walletId); - // TODO: remove the need for this!! - final bool isOwnAddress = await manager.isOwnAddress(_address!); - if (isOwnAddress) { - await showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) { - return DesktopDialog( - maxWidth: 400, - maxHeight: double.infinity, - child: Padding( - padding: const EdgeInsets.only( - left: 32, - bottom: 32, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Transaction failed", - style: STextStyles.desktopH3(context), - ), - const DesktopDialogCloseButton(), - ], - ), - const SizedBox( - height: 12, - ), - Text( - "Sending to self is currently disabled", - textAlign: TextAlign.left, - style: STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - fontSize: 18, - ), - ), - const SizedBox( - height: 40, - ), - Padding( - padding: const EdgeInsets.only( - right: 32, - ), - child: SecondaryButton( - buttonHeight: ButtonHeight.l, - label: "Ok", - onPressed: () { - Navigator.of(context).pop(); - }, - ), - ), - ], - ), - ), - ); - }, - ); - return; - } - - final amount = Format.decimalAmountToSatoshis(_amountToSend!, coin); - int availableBalance; + final Amount amount = _amountToSend!; + final Amount availableBalance; if ((coin == Coin.firo || coin == Coin.firoTestNet)) { if (ref.read(publicPrivateBalanceStateProvider.state).state == "Private") { - availableBalance = Format.decimalAmountToSatoshis( - await (manager.wallet as FiroWallet).availablePrivateBalance(), - coin); + availableBalance = + (manager.wallet as FiroWallet).availablePrivateBalance(); } else { - availableBalance = Format.decimalAmountToSatoshis( - await (manager.wallet as FiroWallet).availablePublicBalance(), - coin); + availableBalance = + (manager.wallet as FiroWallet).availablePublicBalance(); } } else { - availableBalance = - Format.decimalAmountToSatoshis(await manager.availableBalance, coin); + availableBalance = manager.balance.spendable; } - // confirm send all - if (amount == availableBalance) { - final bool? shouldSendAll = await showDialog( - context: context, - useSafeArea: false, - barrierDismissible: true, - builder: (context) { - return DesktopDialog( - maxWidth: 450, - maxHeight: double.infinity, - child: Padding( - padding: const EdgeInsets.only( - left: 32, - bottom: 32, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Confirm send all", - style: STextStyles.desktopH3(context), - ), - const DesktopDialogCloseButton(), - ], - ), - const SizedBox( - height: 12, - ), - Padding( - padding: const EdgeInsets.only( - right: 32, - ), - child: Text( - "You are about to send your entire balance. Would you like to continue?", - textAlign: TextAlign.left, - style: STextStyles.desktopTextExtraExtraSmall(context) - .copyWith( - fontSize: 18, - ), - ), - ), - const SizedBox( - height: 40, - ), - Padding( - padding: const EdgeInsets.only( - right: 32, - ), - child: Row( - children: [ - Expanded( - child: SecondaryButton( - buttonHeight: ButtonHeight.l, - label: "Cancel", - onPressed: () { - Navigator.of(context).pop(false); - }, - ), - ), - const SizedBox( - width: 16, - ), - Expanded( - child: PrimaryButton( - buttonHeight: ButtonHeight.l, - label: "Yes", - onPressed: () { - Navigator.of(context).pop(true); + final coinControlEnabled = + ref.read(prefsChangeNotifierProvider).enableCoinControl; - setState(() { - sendToController.text = ""; - cryptoAmountController.text = ""; - baseAmountController.text = ""; - }); - }, - ), + if (!(manager.hasCoinControlSupport && coinControlEnabled) || + (manager.hasCoinControlSupport && + coinControlEnabled && + ref.read(desktopUseUTXOs).isEmpty)) { + // confirm send all + if (amount == availableBalance) { + final bool? shouldSendAll = await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return DesktopDialog( + maxWidth: 450, + maxHeight: double.infinity, + child: Padding( + padding: const EdgeInsets.only( + left: 32, + bottom: 32, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Confirm send all", + style: STextStyles.desktopH3(context), ), + const DesktopDialogCloseButton(), ], ), - ), - ], + const SizedBox( + height: 12, + ), + Padding( + padding: const EdgeInsets.only( + right: 32, + ), + child: Text( + "You are about to send your entire balance. Would you like to continue?", + textAlign: TextAlign.left, + style: STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + fontSize: 18, + ), + ), + ), + const SizedBox( + height: 40, + ), + Padding( + padding: const EdgeInsets.only( + right: 32, + ), + child: Row( + children: [ + Expanded( + child: SecondaryButton( + buttonHeight: ButtonHeight.l, + label: "Cancel", + onPressed: () { + Navigator.of(context).pop(false); + }, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + buttonHeight: ButtonHeight.l, + label: "Yes", + onPressed: () { + Navigator.of(context).pop(true); + }, + ), + ), + ], + ), + ), + ], + ), ), - ), - ); - }, - ); + ); + }, + ); - if (shouldSendAll == null || shouldSendAll == false) { - // cancel preview - return; + if (shouldSendAll == null || shouldSendAll == false) { + // cancel preview + return; + } } } try { bool wasCancelled = false; - unawaited(showDialog( - context: context, - useSafeArea: false, - barrierDismissible: false, - builder: (context) { - return DesktopDialog( - maxWidth: 400, - maxHeight: double.infinity, - child: Padding( - padding: const EdgeInsets.all(32), - child: BuildingTransactionDialog( - onCancel: () { - wasCancelled = true; + if (mounted) { + unawaited( + showDialog( + context: context, + useSafeArea: false, + barrierDismissible: false, + builder: (context) { + return DesktopDialog( + maxWidth: 400, + maxHeight: double.infinity, + child: Padding( + padding: const EdgeInsets.all(32), + child: BuildingTransactionDialog( + coin: manager.coin, + onCancel: () { + wasCancelled = true; - Navigator.of(context).pop(); - }, - ), - ), - ); - }, - )); - - Map txData; - - if ((coin == Coin.firo || coin == Coin.firoTestNet) && - ref.read(publicPrivateBalanceStateProvider.state).state != - "Private") { - txData = await (manager.wallet as FiroWallet).prepareSendPublic( - address: _address!, - satoshiAmount: amount, - args: {"feeRate": ref.read(feeRateTypeStateProvider)}, - ); - } else { - txData = await manager.prepareSend( - address: _address!, - satoshiAmount: amount, - args: {"feeRate": ref.read(feeRateTypeStateProvider)}, + Navigator.of(context).pop(); + }, + ), + ), + ); + }, + ), ); } + final time = Future.delayed( + const Duration( + milliseconds: 2500, + ), + ); + + Map txData; + Future> txDataFuture; + + if (isPaynymSend) { + final wallet = manager.wallet as PaynymWalletInterface; + final paymentCode = PaymentCode.fromPaymentCode( + widget.accountLite!.code, + networkType: wallet.networkType, + ); + final feeRate = ref.read(feeRateTypeStateProvider); + txDataFuture = wallet.preparePaymentCodeSend( + paymentCode: paymentCode, + isSegwit: widget.accountLite!.segwit, + amount: amount, + args: { + "feeRate": feeRate, + "UTXOs": (manager.hasCoinControlSupport && + coinControlEnabled && + ref.read(desktopUseUTXOs).isNotEmpty) + ? ref.read(desktopUseUTXOs) + : null, + }, + ); + } else if ((coin == Coin.firo || coin == Coin.firoTestNet) && + ref.read(publicPrivateBalanceStateProvider.state).state != + "Private") { + txDataFuture = (manager.wallet as FiroWallet).prepareSendPublic( + address: _address!, + amount: amount, + args: { + "feeRate": ref.read(feeRateTypeStateProvider), + "UTXOs": (manager.hasCoinControlSupport && + coinControlEnabled && + ref.read(desktopUseUTXOs).isNotEmpty) + ? ref.read(desktopUseUTXOs) + : null, + }, + ); + } else { + txDataFuture = manager.prepareSend( + address: _address!, + amount: amount, + args: { + "feeRate": ref.read(feeRateTypeStateProvider), + "UTXOs": (manager.hasCoinControlSupport && + coinControlEnabled && + ref.read(desktopUseUTXOs).isNotEmpty) + ? ref.read(desktopUseUTXOs) + : null, + }, + ); + } + + final results = await Future.wait([ + txDataFuture, + time, + ]); + + txData = results.first as Map; + if (!wasCancelled && mounted) { + if (isPaynymSend) { + txData["paynymAccountLite"] = widget.accountLite!; + txData["note"] = _note ?? "PayNym send"; + } else { + txData["address"] = _address; + txData["note"] = _note ?? ""; + } // pop building dialog Navigator.of( context, rootNavigator: true, ).pop(); - txData["note"] = _note; - txData["address"] = _address; unawaited( showDialog( @@ -336,6 +338,7 @@ class _DesktopSendState extends ConsumerState { child: ConfirmTransactionView( transactionInfo: txData, walletId: walletId, + isPaynymTransaction: isPaynymSend, routeOnSuccessName: DesktopHomeView.routeName, ), ), @@ -394,22 +397,24 @@ class _DesktopSendState extends ConsumerState { const SizedBox( height: 40, ), - Padding( - padding: const EdgeInsets.only( - right: 32, - ), - child: Expanded( - child: SecondaryButton( - buttonHeight: ButtonHeight.l, - label: "Yes", - onPressed: () { - Navigator.of( - context, - rootNavigator: true, - ).pop(); - }, + Row( + children: [ + Expanded( + child: SecondaryButton( + buttonHeight: ButtonHeight.l, + label: "Ok", + onPressed: () { + Navigator.of( + context, + rootNavigator: true, + ).pop(); + }, + ), ), - ), + const SizedBox( + width: 32, + ), + ], ), ], ), @@ -430,29 +435,32 @@ class _DesktopSendState extends ConsumerState { cryptoAmount != ",") { _amountToSend = cryptoAmount.contains(",") ? Decimal.parse(cryptoAmount.replaceFirst(",", ".")) - : Decimal.parse(cryptoAmount); + .toAmount(fractionDigits: coin.decimals) + : Decimal.parse(cryptoAmount) + .toAmount(fractionDigits: coin.decimals); if (_cachedAmountToSend != null && _cachedAmountToSend == _amountToSend) { return; } - _cachedAmountToSend = _amountToSend; Logging.instance.log("it changed $_amountToSend $_cachedAmountToSend", level: LogLevel.Info); + _cachedAmountToSend = _amountToSend; final price = ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin).item1; if (price > Decimal.zero) { - final String fiatAmountString = Format.localizedStringAsFixed( - value: _amountToSend! * price, - locale: ref.read(localeServiceChangeNotifierProvider).locale, - decimalPlaces: 2, - ); + final String fiatAmountString = (_amountToSend!.decimal * price) + .toAmount(fractionDigits: 2) + .localizedStringAsFixed( + locale: ref.read(localeServiceChangeNotifierProvider).locale, + ); baseAmountController.text = fiatAmountString; } } else { _amountToSend = null; + _cachedAmountToSend = null; baseAmountController.text = ""; } @@ -470,88 +478,20 @@ class _DesktopSendState extends ConsumerState { return null; } - void _updatePreviewButtonState(String? address, Decimal? amount) { - final isValidAddress = ref - .read(walletsChangeNotifierProvider) - .getManager(walletId) - .validateAddress(address ?? ""); - ref.read(previewTxButtonStateProvider.state).state = - (isValidAddress && amount != null && amount > Decimal.zero); + void _updatePreviewButtonState(String? address, Amount? amount) { + if (isPaynymSend) { + ref.read(previewTxButtonStateProvider.state).state = + (amount != null && amount > Amount.zero); + } else { + final isValidAddress = ref + .read(walletsChangeNotifierProvider) + .getManager(walletId) + .validateAddress(address ?? ""); + ref.read(previewTxButtonStateProvider.state).state = + (isValidAddress && amount != null && amount > Amount.zero); + } } - // late Future _calculateFeesFuture; - - // Map cachedFees = {}; - // Map cachedFiroPrivateFees = {}; - // Map cachedFiroPublicFees = {}; - - // Future calculateFees(int amount) async { - // if (amount <= 0) { - // return "0"; - // } - // - // if (coin == Coin.firo || coin == Coin.firoTestNet) { - // if (ref.read(publicPrivateBalanceStateProvider.state).state == - // "Private") { - // if (cachedFiroPrivateFees[amount] != null) { - // return cachedFiroPrivateFees[amount]!; - // } - // } else { - // if (cachedFiroPublicFees[amount] != null) { - // return cachedFiroPublicFees[amount]!; - // } - // } - // } else if (cachedFees[amount] != null) { - // return cachedFees[amount]!; - // } - // - // final manager = - // ref.read(walletsChangeNotifierProvider).getManager(walletId); - // final feeObject = await manager.fees; - // - // late final int feeRate; - // - // switch (ref.read(feeRateTypeStateProvider.state).state) { - // case FeeRateType.fast: - // feeRate = feeObject.fast; - // break; - // case FeeRateType.average: - // feeRate = feeObject.medium; - // break; - // case FeeRateType.slow: - // feeRate = feeObject.slow; - // break; - // } - // - // int fee; - // - // if (coin == Coin.firo || coin == Coin.firoTestNet) { - // if (ref.read(publicPrivateBalanceStateProvider.state).state == - // "Private") { - // fee = await manager.estimateFeeFor(amount, feeRate); - // - // cachedFiroPrivateFees[amount] = Format.satoshisToAmount(fee) - // .toStringAsFixed(Constants.decimalPlaces); - // - // return cachedFiroPrivateFees[amount]!; - // } else { - // fee = await (manager.wallet as FiroWallet) - // .estimateFeeForPublic(amount, feeRate); - // - // cachedFiroPublicFees[amount] = Format.satoshisToAmount(fee) - // .toStringAsFixed(Constants.decimalPlaces); - // - // return cachedFiroPublicFees[amount]!; - // } - // } else { - // fee = await manager.estimateFeeFor(amount, feeRate); - // cachedFees[amount] = - // Format.satoshisToAmount(fee).toStringAsFixed(Constants.decimalPlaces); - // - // return cachedFees[amount]!; - // } - // } - Future _firoBalanceFuture( ChangeNotifierProvider provider, String locale, @@ -560,15 +500,16 @@ class _DesktopSendState extends ConsumerState { final wallet = ref.read(provider).wallet as FiroWallet?; if (wallet != null) { - Decimal? balance; + Amount? balance; if (private) { - balance = await wallet.availablePrivateBalance(); + balance = wallet.availablePrivateBalance(); } else { - balance = await wallet.availablePublicBalance(); + balance = wallet.availablePublicBalance(); } - - return Format.localizedStringAsFixed( - value: balance, locale: locale, decimalPlaces: 8); + return balance.localizedStringAsFixed( + locale: locale, + decimalPlaces: coin.decimals, + ); } return null; @@ -639,9 +580,10 @@ class _DesktopSendState extends ConsumerState { // autofill amount field if (results["amount"] != null) { - final amount = Decimal.parse(results["amount"]!); - cryptoAmountController.text = Format.localizedStringAsFixed( - value: amount, + final amount = Decimal.parse(results["amount"]!).toAmount( + fractionDigits: coin.decimals, + ); + cryptoAmountController.text = amount.localizedStringAsFixed( locale: ref.read(localeServiceChangeNotifierProvider).locale, decimalPlaces: Constants.decimalPlacesForCoin(coin), ); @@ -684,6 +626,11 @@ class _DesktopSendState extends ConsumerState { content = content.substring(0, content.indexOf("\n")); } + if (coin == Coin.epicCash) { + // strip http:// and https:// if content contains @ + content = formatAddress(content); + } + sendToController.text = content; _address = content; @@ -700,18 +647,20 @@ class _DesktopSendState extends ConsumerState { baseAmountString != ",") { final baseAmount = baseAmountString.contains(",") ? Decimal.parse(baseAmountString.replaceFirst(",", ".")) - : Decimal.parse(baseAmountString); + .toAmount(fractionDigits: 2) + : Decimal.parse(baseAmountString).toAmount(fractionDigits: 2); var _price = ref.read(priceAnd24hChangeNotifierProvider).getPrice(coin).item1; if (_price == Decimal.zero) { - _amountToSend = Decimal.zero; + _amountToSend = Decimal.zero.toAmount(fractionDigits: coin.decimals); } else { - _amountToSend = baseAmount <= Decimal.zero - ? Decimal.zero - : (baseAmount / _price).toDecimal( - scaleOnInfinitePrecision: Constants.decimalPlacesForCoin(coin)); + _amountToSend = baseAmount <= Amount.zero + ? Decimal.zero.toAmount(fractionDigits: coin.decimals) + : (baseAmount.decimal / _price) + .toDecimal(scaleOnInfinitePrecision: coin.decimals) + .toAmount(fractionDigits: coin.decimals); } if (_cachedAmountToSend != null && _cachedAmountToSend == _amountToSend) { return; @@ -720,17 +669,16 @@ class _DesktopSendState extends ConsumerState { Logging.instance.log("it changed $_amountToSend $_cachedAmountToSend", level: LogLevel.Info); - final amountString = Format.localizedStringAsFixed( - value: _amountToSend!, + final amountString = _amountToSend!.localizedStringAsFixed( locale: ref.read(localeServiceChangeNotifierProvider).locale, - decimalPlaces: Constants.decimalPlacesForCoin(coin), + decimalPlaces: coin.decimals, ); _cryptoAmountChangeLock = true; cryptoAmountController.text = amountString; _cryptoAmountChangeLock = false; } else { - _amountToSend = Decimal.zero; + _amountToSend = Decimal.zero.toAmount(fractionDigits: coin.decimals); _cryptoAmountChangeLock = true; cryptoAmountController.text = ""; _cryptoAmountChangeLock = false; @@ -751,26 +699,43 @@ class _DesktopSendState extends ConsumerState { .wallet as FiroWallet; if (ref.read(publicPrivateBalanceStateProvider.state).state == "Private") { - cryptoAmountController.text = - (await firoWallet.availablePrivateBalance()) - .toStringAsFixed(Constants.decimalPlacesForCoin(coin)); + cryptoAmountController.text = firoWallet + .availablePrivateBalance() + .decimal + .toStringAsFixed(coin.decimals); } else { - cryptoAmountController.text = - (await firoWallet.availablePublicBalance()) - .toStringAsFixed(Constants.decimalPlacesForCoin(coin)); + cryptoAmountController.text = firoWallet + .availablePublicBalance() + .decimal + .toStringAsFixed(coin.decimals); } } else { - cryptoAmountController.text = (await ref - .read(walletsChangeNotifierProvider) - .getManager(walletId) - .availableBalance) - .toStringAsFixed(Constants.decimalPlacesForCoin(coin)); + cryptoAmountController.text = ref + .read(walletsChangeNotifierProvider) + .getManager(walletId) + .balance + .spendable + .decimal + .toStringAsFixed(coin.decimals); } } + void _showDesktopCoinControl() async { + await showDialog( + context: context, + builder: (context) => DesktopCoinControlUseDialog( + walletId: widget.walletId, + amountToSend: _amountToSend, + ), + ); + } + @override void initState() { - ref.refresh(feeSheetSessionCacheProvider); + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.refresh(feeSheetSessionCacheProvider); + ref.read(previewTxButtonStateProvider.state).state = false; + }); // _calculateFeesFuture = calculateFees(0); _data = widget.autoFillData; @@ -796,6 +761,10 @@ class _DesktopSendState extends ConsumerState { _addressToggleFlag = true; } + if (isPaynymSend) { + sendToController.text = widget.accountLite!.nymName; + } + _cryptoFocus.addListener(() { if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) { if (_amountToSend == null) { @@ -842,20 +811,32 @@ class _DesktopSendState extends ConsumerState { final String locale = ref.watch( localeServiceChangeNotifierProvider.select((value) => value.locale)); - // if (coin == Coin.firo || coin == Coin.firoTestNet) { - // ref.listen(publicPrivateBalanceStateProvider, (previous, next) { - // if (_amountToSend == null) { - // setState(() { - // _calculateFeesFuture = calculateFees(0); - // }); - // } else { - // setState(() { - // _calculateFeesFuture = - // calculateFees(Format.decimalAmountToSatoshis(_amountToSend!)); - // }); - // } - // }); - // } + // add listener for epic cash to strip http:// and https:// prefixes if the address also ocntains an @ symbol (indicating an epicbox address) + if (coin == Coin.epicCash) { + sendToController.addListener(() { + _address = sendToController.text; + + if (_address != null && _address!.isNotEmpty) { + _address = _address!.trim(); + if (_address!.contains("\n")) { + _address = _address!.substring(0, _address!.indexOf("\n")); + } + + sendToController.text = formatAddress(_address!); + } + }); + } + + final showCoinControl = ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.enableCoinControl, + ), + ) && + ref.watch( + provider.select( + (value) => value.hasCoinControlSupport, + ), + ); return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -972,6 +953,36 @@ class _DesktopSendState extends ConsumerState { const SizedBox( height: 20, ), + if (isPaynymSend) + Text( + "Send to PayNym address", + style: STextStyles.smallMed12(context), + textAlign: TextAlign.left, + ), + if (isPaynymSend) + const SizedBox( + height: 10, + ), + if (isPaynymSend) + TextField( + key: const Key("sendViewPaynymAddressFieldKey"), + controller: sendToController, + enabled: false, + readOnly: true, + style: STextStyles.desktopTextFieldLabel(context).copyWith( + fontSize: 16, + ), + decoration: const InputDecoration( + contentPadding: EdgeInsets.symmetric( + vertical: 18, + horizontal: 16, + ), + ), + ), + if (isPaynymSend) + const SizedBox( + height: 20, + ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -984,10 +995,11 @@ class _DesktopSendState extends ConsumerState { ), textAlign: TextAlign.left, ), - BlueTextButton( - text: "Send all ${coin.ticker}", - onTap: sendAllTapped, - ), + if (coin != Coin.ethereum) + CustomTextButton( + text: "Send all ${coin.ticker}", + onTap: sendAllTapped, + ), ], ), const SizedBox( @@ -1002,10 +1014,12 @@ class _DesktopSendState extends ConsumerState { key: const Key("amountInputFieldCryptoTextFieldKey"), controller: cryptoAmountController, focusNode: _cryptoFocus, - keyboardType: const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), + keyboardType: Util.isDesktop + ? null + : const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), textAlign: TextAlign.right, inputFormatters: [ // regex to validate a crypto amount with 8 decimal places @@ -1015,6 +1029,7 @@ class _DesktopSendState extends ConsumerState { ? newValue : oldValue), ], + onChanged: (newValue) {}, decoration: InputDecoration( contentPadding: const EdgeInsets.only( top: 22, @@ -1056,10 +1071,12 @@ class _DesktopSendState extends ConsumerState { key: const Key("amountInputFieldFiatTextFieldKey"), controller: baseAmountController, focusNode: _baseFocus, - keyboardType: const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), + keyboardType: Util.isDesktop + ? null + : const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), textAlign: TextAlign.right, inputFormatters: [ // regex to validate a fiat amount with 2 decimal places @@ -1098,202 +1115,232 @@ class _DesktopSendState extends ConsumerState { ), ), ), + if (showCoinControl) + const SizedBox( + height: 10, + ), + if (showCoinControl) + RoundedContainer( + color: Colors.transparent, + borderColor: + Theme.of(context).extension()!.textFieldDefaultBG, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Coin control", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + CustomTextButton( + text: ref.watch(desktopUseUTXOs.state).state.isEmpty + ? "Select coins" + : "Selected coins (${ref.watch(desktopUseUTXOs.state).state.length})", + onTap: _showDesktopCoinControl, + ), + ], + ), + ), const SizedBox( height: 20, ), - Text( - "Send to", - style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveSearchIconRight, - ), - textAlign: TextAlign.left, - ), - const SizedBox( - height: 10, - ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - minLines: 1, - maxLines: 5, - key: const Key("sendViewAddressFieldKey"), - controller: sendToController, - readOnly: false, - autocorrect: false, - enableSuggestions: false, - // inputFormatters: [ - // FilteringTextInputFormatter.allow( - // RegExp("[a-zA-Z0-9]{34}")), - // ], - toolbarOptions: const ToolbarOptions( - copy: false, - cut: false, - paste: true, - selectAll: false, - ), - onChanged: (newValue) { - _address = newValue; - _updatePreviewButtonState(_address, _amountToSend); - - setState(() { - _addressToggleFlag = newValue.isNotEmpty; - }); - }, - focusNode: _addressFocusNode, + if (!isPaynymSend) + Text( + "Send to", style: STextStyles.desktopTextExtraSmall(context).copyWith( color: Theme.of(context) .extension()! - .textFieldActiveText, - height: 1.8, + .textFieldActiveSearchIconRight, ), - decoration: standardInputDecoration( - "Enter ${coin.ticker} address", - _addressFocusNode, - context, - desktopMed: true, - ).copyWith( - contentPadding: const EdgeInsets.only( - left: 16, - top: 11, - bottom: 12, - right: 5, + textAlign: TextAlign.left, + ), + if (!isPaynymSend) + const SizedBox( + height: 10, + ), + if (!isPaynymSend) + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + minLines: 1, + maxLines: 5, + key: const Key("sendViewAddressFieldKey"), + controller: sendToController, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + // inputFormatters: [ + // FilteringTextInputFormatter.allow( + // RegExp("[a-zA-Z0-9]{34}")), + // ], + toolbarOptions: const ToolbarOptions( + copy: false, + cut: false, + paste: true, + selectAll: false, ), - suffixIcon: Padding( - padding: sendToController.text.isEmpty - ? const EdgeInsets.only(right: 8) - : const EdgeInsets.only(right: 0), - child: UnconstrainedBox( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - _addressToggleFlag - ? TextFieldIconButton( - key: const Key( - "sendViewClearAddressFieldButtonKey"), - onTap: () { - sendToController.text = ""; - _address = ""; - _updatePreviewButtonState( - _address, _amountToSend); - setState(() { - _addressToggleFlag = false; - }); - }, - child: const XIcon(), - ) - : TextFieldIconButton( - key: const Key( - "sendViewPasteAddressFieldButtonKey"), - onTap: pasteAddress, - child: sendToController.text.isEmpty - ? const ClipboardIcon() - : const XIcon(), - ), - if (sendToController.text.isEmpty) - TextFieldIconButton( - key: const Key("sendViewAddressBookButtonKey"), - onTap: () async { - final entry = - await showDialog( - context: context, - builder: (context) => DesktopDialog( - maxWidth: 696, - maxHeight: 600, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.only( - left: 32, - ), - child: Text( - "Address book", - style: - STextStyles.desktopH3(context), - ), - ), - const DesktopDialogCloseButton(), - ], - ), - Expanded( - child: AddressBookAddressChooser( - coin: coin, - ), - ), - ], - ), + onChanged: (newValue) { + _address = newValue; + _updatePreviewButtonState(_address, _amountToSend); + + setState(() { + _addressToggleFlag = newValue.isNotEmpty; + }); + }, + focusNode: _addressFocusNode, + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveText, + height: 1.8, + ), + decoration: standardInputDecoration( + "Enter ${coin.ticker} address", + _addressFocusNode, + context, + desktopMed: true, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 16, + top: 11, + bottom: 12, + right: 5, + ), + suffixIcon: Padding( + padding: sendToController.text.isEmpty + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _addressToggleFlag + ? TextFieldIconButton( + key: const Key( + "sendViewClearAddressFieldButtonKey"), + onTap: () { + sendToController.text = ""; + _address = ""; + _updatePreviewButtonState( + _address, _amountToSend); + setState(() { + _addressToggleFlag = false; + }); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + key: const Key( + "sendViewPasteAddressFieldButtonKey"), + onTap: pasteAddress, + child: sendToController.text.isEmpty + ? const ClipboardIcon() + : const XIcon(), ), - ); - - if (entry != null) { - sendToController.text = - entry.other ?? entry.label; - - _address = entry.address; - - _updatePreviewButtonState( - _address, - _amountToSend, + if (sendToController.text.isEmpty) + TextFieldIconButton( + key: const Key("sendViewAddressBookButtonKey"), + onTap: () async { + final entry = + await showDialog( + context: context, + builder: (context) => DesktopDialog( + maxWidth: 696, + maxHeight: 600, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Address book", + style: STextStyles.desktopH3( + context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: AddressBookAddressChooser( + coin: coin, + ), + ), + ], + ), + ), ); - setState(() { - _addressToggleFlag = true; - }); - } - }, - child: const AddressBookIcon(), - ), - // if (sendToController.text.isEmpty) - // TextFieldIconButton( - // key: const Key("sendViewScanQrButtonKey"), - // onTap: scanQr, - // child: const QrCodeIcon(), - // ) - ], + if (entry != null) { + sendToController.text = + entry.other ?? entry.label; + + _address = entry.address; + + _updatePreviewButtonState( + _address, + _amountToSend, + ); + + setState(() { + _addressToggleFlag = true; + }); + } + }, + child: const AddressBookIcon(), + ), + // if (sendToController.text.isEmpty) + // TextFieldIconButton( + // key: const Key("sendViewScanQrButtonKey"), + // onTap: scanQr, + // child: const QrCodeIcon(), + // ) + ], + ), ), ), ), ), ), - ), - Builder( - builder: (_) { - final error = _updateInvalidAddressText( - _address ?? "", - ref.read(walletsChangeNotifierProvider).getManager(walletId), - ); + if (!isPaynymSend) + Builder( + builder: (_) { + final error = _updateInvalidAddressText( + _address ?? "", + ref.read(walletsChangeNotifierProvider).getManager(walletId), + ); - if (error == null || error.isEmpty) { - return Container(); - } else { - return Align( - alignment: Alignment.topLeft, - child: Padding( - padding: const EdgeInsets.only( - left: 12.0, - top: 4.0, - ), - child: Text( - error, - textAlign: TextAlign.left, - style: STextStyles.label(context).copyWith( - color: - Theme.of(context).extension()!.textError, + if (error == null || error.isEmpty) { + return Container(); + } else { + return Align( + alignment: Alignment.topLeft, + child: Padding( + padding: const EdgeInsets.only( + left: 12.0, + top: 4.0, + ), + child: Text( + error, + textAlign: TextAlign.left, + style: STextStyles.label(context).copyWith( + color: Theme.of(context) + .extension()! + .textError, + ), ), ), - ), - ); - } - }, - ), + ); + } + }, + ), // const SizedBox( // height: 20, // ), @@ -1361,12 +1408,13 @@ class _DesktopSendState extends ConsumerState { // ), // ), // ), - const SizedBox( - height: 20, - ), + if (!isPaynymSend) + const SizedBox( + height: 20, + ), if (coin != Coin.epicCash) Text( - "Transaction fee (estimated)", + "Transaction fee (${coin == Coin.ethereum ? "max" : "estimated"})", style: STextStyles.desktopTextExtraSmall(context).copyWith( color: Theme.of(context) .extension()! @@ -1397,3 +1445,22 @@ class _DesktopSendState extends ConsumerState { ); } } + +String formatAddress(String epicAddress) { + // strip http:// or https:// prefixes if the address contains an @ symbol (and is thus an epicbox address) + if ((epicAddress.startsWith("http://") || + epicAddress.startsWith("https://")) && + epicAddress.contains("@")) { + epicAddress = epicAddress.replaceAll("http://", ""); + epicAddress = epicAddress.replaceAll("https://", ""); + } + // strip mailto: prefix + if (epicAddress.startsWith("mailto:")) { + epicAddress = epicAddress.replaceAll("mailto:", ""); + } + // strip / suffix if the address contains an @ symbol (and is thus an epicbox address) + if (epicAddress.endsWith("/") && epicAddress.contains("@")) { + epicAddress = epicAddress.substring(0, epicAddress.length - 1); + } + return epicAddress; +} diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_token_send.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_token_send.dart new file mode 100644 index 000000000..226de4300 --- /dev/null +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_token_send.dart @@ -0,0 +1,1078 @@ +import 'dart:async'; + +import 'package:decimal/decimal.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/contact_address_entry.dart'; +import 'package:stackwallet/models/paynym/paynym_account_lite.dart'; +import 'package:stackwallet/models/send_view_auto_fill_data.dart'; +import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart'; +import 'package:stackwallet/pages/send_view/sub_widgets/building_transaction_dialog.dart'; +import 'package:stackwallet/pages/token_view/token_view.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart'; +import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/address_book_address_chooser/address_book_address_chooser.dart'; +import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_fee_dropdown.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/providers/ui/fee_rate_type_state_provider.dart'; +import 'package:stackwallet/providers/ui/preview_tx_button_state_provider.dart'; +import 'package:stackwallet/services/coins/manager.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/address_utils.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/barcode_scanner_interface.dart'; +import 'package:stackwallet/utilities/clipboard_interface.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/prefs.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/clipboard_icon.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/stack_text_field.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; + +const _kCryptoAmountRegex = r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$'; + +class DesktopTokenSend extends ConsumerStatefulWidget { + const DesktopTokenSend({ + Key? key, + required this.walletId, + this.autoFillData, + this.clipboard = const ClipboardWrapper(), + this.barcodeScanner = const BarcodeScannerWrapper(), + this.accountLite, + }) : super(key: key); + + final String walletId; + final SendViewAutoFillData? autoFillData; + final ClipboardInterface clipboard; + final BarcodeScannerInterface barcodeScanner; + final PaynymAccountLite? accountLite; + + @override + ConsumerState createState() => _DesktopTokenSendState(); +} + +class _DesktopTokenSendState extends ConsumerState { + late final String walletId; + late final Coin coin; + late final ClipboardInterface clipboard; + late final BarcodeScannerInterface scanner; + + late TextEditingController sendToController; + late TextEditingController cryptoAmountController; + late TextEditingController baseAmountController; + late TextEditingController nonceController; + + late final SendViewAutoFillData? _data; + + final _addressFocusNode = FocusNode(); + final _cryptoFocus = FocusNode(); + final _baseFocus = FocusNode(); + final _nonceFocusNode = FocusNode(); + + String? _note; + + Amount? _amountToSend; + Amount? _cachedAmountToSend; + String? _address; + + bool _addressToggleFlag = false; + + bool _cryptoAmountChangeLock = false; + late VoidCallback onCryptoAmountChanged; + + Future previewSend() async { + final tokenWallet = ref.read(tokenServiceProvider)!; + + final Amount amount = _amountToSend!; + final Amount availableBalance = tokenWallet.balance.spendable; + + // confirm send all + if (amount == availableBalance) { + final bool? shouldSendAll = await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return DesktopDialog( + maxWidth: 450, + maxHeight: double.infinity, + child: Padding( + padding: const EdgeInsets.only( + left: 32, + bottom: 32, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Confirm send all", + style: STextStyles.desktopH3(context), + ), + const DesktopDialogCloseButton(), + ], + ), + const SizedBox( + height: 12, + ), + Padding( + padding: const EdgeInsets.only( + right: 32, + ), + child: Text( + "You are about to send your entire balance. Would you like to continue?", + textAlign: TextAlign.left, + style: STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + fontSize: 18, + ), + ), + ), + const SizedBox( + height: 40, + ), + Padding( + padding: const EdgeInsets.only( + right: 32, + ), + child: Row( + children: [ + Expanded( + child: SecondaryButton( + buttonHeight: ButtonHeight.l, + label: "Cancel", + onPressed: () { + Navigator.of(context).pop(false); + }, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + buttonHeight: ButtonHeight.l, + label: "Yes", + onPressed: () { + Navigator.of(context).pop(true); + }, + ), + ), + ], + ), + ), + ], + ), + ), + ); + }, + ); + + if (shouldSendAll == null || shouldSendAll == false) { + // cancel preview + return; + } + } + + try { + bool wasCancelled = false; + + if (mounted) { + unawaited( + showDialog( + context: context, + useSafeArea: false, + barrierDismissible: false, + builder: (context) { + return DesktopDialog( + maxWidth: 400, + maxHeight: double.infinity, + child: Padding( + padding: const EdgeInsets.all(32), + child: BuildingTransactionDialog( + coin: tokenWallet.coin, + onCancel: () { + wasCancelled = true; + + Navigator.of(context).pop(); + }, + ), + ), + ); + }, + ), + ); + } + + final time = Future.delayed( + const Duration( + milliseconds: 2500, + ), + ); + + Map txData; + Future> txDataFuture; + + txDataFuture = tokenWallet.prepareSend( + address: _address!, + amount: amount, + args: { + "feeRate": ref.read(feeRateTypeStateProvider), + "nonce": int.tryParse(nonceController.text), + }, + ); + + final results = await Future.wait([ + txDataFuture, + time, + ]); + + txData = results.first as Map; + + if (!wasCancelled && mounted) { + txData["address"] = _address; + txData["note"] = _note ?? ""; + + // pop building dialog + Navigator.of( + context, + rootNavigator: true, + ).pop(); + + unawaited( + showDialog( + context: context, + builder: (context) => DesktopDialog( + maxHeight: double.infinity, + maxWidth: 580, + child: ConfirmTransactionView( + transactionInfo: txData, + walletId: walletId, + isTokenTx: true, + routeOnSuccessName: DesktopHomeView.routeName, + ), + ), + ), + ); + } + } catch (e) { + if (mounted) { + // pop building dialog + Navigator.of( + context, + rootNavigator: true, + ).pop(); + + unawaited( + showDialog( + context: context, + builder: (context) { + return DesktopDialog( + maxWidth: 450, + maxHeight: double.infinity, + child: Padding( + padding: const EdgeInsets.only( + left: 32, + bottom: 32, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Transaction failed", + style: STextStyles.desktopH3(context), + ), + const DesktopDialogCloseButton(), + ], + ), + const SizedBox( + height: 12, + ), + Padding( + padding: const EdgeInsets.only( + right: 32, + ), + child: SelectableText( + e.toString(), + textAlign: TextAlign.left, + style: STextStyles.desktopTextExtraExtraSmall(context) + .copyWith( + fontSize: 18, + ), + ), + ), + const SizedBox( + height: 40, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + buttonHeight: ButtonHeight.l, + label: "Ok", + onPressed: () { + Navigator.of( + context, + rootNavigator: true, + ).pop(); + }, + ), + ), + const SizedBox( + width: 32, + ), + ], + ), + ], + ), + ), + ); + }, + ), + ); + } + } + } + + void _cryptoAmountChanged() async { + if (!_cryptoAmountChangeLock) { + final String cryptoAmount = cryptoAmountController.text; + if (cryptoAmount.isNotEmpty && + cryptoAmount != "." && + cryptoAmount != ",") { + _amountToSend = cryptoAmount.contains(",") + ? Decimal.parse(cryptoAmount.replaceFirst(",", ".")).toAmount( + fractionDigits: + ref.read(tokenServiceProvider)!.tokenContract.decimals, + ) + : Decimal.parse(cryptoAmount).toAmount( + fractionDigits: + ref.read(tokenServiceProvider)!.tokenContract.decimals, + ); + if (_cachedAmountToSend != null && + _cachedAmountToSend == _amountToSend) { + return; + } + Logging.instance.log("it changed $_amountToSend $_cachedAmountToSend", + level: LogLevel.Info); + _cachedAmountToSend = _amountToSend; + + final price = ref + .read(priceAnd24hChangeNotifierProvider) + .getTokenPrice( + ref.read(tokenServiceProvider)!.tokenContract.address, + ) + .item1; + + if (price > Decimal.zero) { + final String fiatAmountString = Amount.fromDecimal( + _amountToSend!.decimal * price, + fractionDigits: 2, + ).localizedStringAsFixed( + locale: ref.read(localeServiceChangeNotifierProvider).locale, + decimalPlaces: 2, + ); + + baseAmountController.text = fiatAmountString; + } + } else { + _amountToSend = null; + _cachedAmountToSend = null; + baseAmountController.text = ""; + } + + _updatePreviewButtonState(_address, _amountToSend); + } + } + + String? _updateInvalidAddressText(String address, Manager manager) { + if (_data != null && _data!.contactLabel == address) { + return null; + } + if (address.isNotEmpty && !manager.validateAddress(address)) { + return "Invalid address"; + } + return null; + } + + void _updatePreviewButtonState(String? address, Amount? amount) { + final isValidAddress = ref + .read(walletsChangeNotifierProvider) + .getManager(walletId) + .validateAddress(address ?? ""); + ref.read(previewTokenTxButtonStateProvider.state).state = + (isValidAddress && amount != null && amount > Amount.zero); + } + + Future scanQr() async { + try { + if (FocusScope.of(context).hasFocus) { + FocusScope.of(context).unfocus(); + await Future.delayed(const Duration(milliseconds: 75)); + } + + final qrResult = await scanner.scan(); + + Logging.instance.log("qrResult content: ${qrResult.rawContent}", + level: LogLevel.Info); + + final results = AddressUtils.parseUri(qrResult.rawContent); + + Logging.instance.log("qrResult parsed: $results", level: LogLevel.Info); + + if (results.isNotEmpty && results["scheme"] == coin.uriScheme) { + // auto fill address + _address = results["address"] ?? ""; + sendToController.text = _address!; + + // autofill notes field + if (results["message"] != null) { + _note = results["message"]!; + } else if (results["label"] != null) { + _note = results["label"]!; + } + + // autofill amount field + if (results["amount"] != null) { + final amount = Decimal.parse(results["amount"]!).toAmount( + fractionDigits: + ref.read(tokenServiceProvider)!.tokenContract.decimals, + ); + cryptoAmountController.text = amount.localizedStringAsFixed( + locale: ref.read(localeServiceChangeNotifierProvider).locale, + ); + + amount.toString(); + _amountToSend = amount; + } + + _updatePreviewButtonState(_address, _amountToSend); + setState(() { + _addressToggleFlag = sendToController.text.isNotEmpty; + }); + + // now check for non standard encoded basic address + } else if (ref + .read(walletsChangeNotifierProvider) + .getManager(walletId) + .validateAddress(qrResult.rawContent)) { + _address = qrResult.rawContent; + sendToController.text = _address ?? ""; + + _updatePreviewButtonState(_address, _amountToSend); + setState(() { + _addressToggleFlag = sendToController.text.isNotEmpty; + }); + } + } on PlatformException catch (e, s) { + // here we ignore the exception caused by not giving permission + // to use the camera to scan a qr code + Logging.instance.log( + "Failed to get camera permissions while trying to scan qr code in SendView: $e\n$s", + level: LogLevel.Warning); + } + } + + Future pasteAddress() async { + final ClipboardData? data = await clipboard.getData(Clipboard.kTextPlain); + if (data?.text != null && data!.text!.isNotEmpty) { + String content = data.text!.trim(); + if (content.contains("\n")) { + content = content.substring(0, content.indexOf("\n")); + } + + sendToController.text = content; + _address = content; + + _updatePreviewButtonState(_address, _amountToSend); + setState(() { + _addressToggleFlag = sendToController.text.isNotEmpty; + }); + } + } + + void fiatTextFieldOnChanged(String baseAmountString) { + final int tokenDecimals = + ref.read(tokenServiceProvider)!.tokenContract.decimals; + + if (baseAmountString.isNotEmpty && + baseAmountString != "." && + baseAmountString != ",") { + final baseAmount = baseAmountString.contains(",") + ? Decimal.parse(baseAmountString.replaceFirst(",", ".")) + .toAmount(fractionDigits: 2) + : Decimal.parse(baseAmountString).toAmount(fractionDigits: 2); + + final Decimal _price = ref + .read(priceAnd24hChangeNotifierProvider) + .getTokenPrice( + ref.read(tokenServiceProvider)!.tokenContract.address, + ) + .item1; + + if (_price == Decimal.zero) { + _amountToSend = Decimal.zero.toAmount(fractionDigits: tokenDecimals); + } else { + _amountToSend = baseAmount <= Amount.zero + ? Decimal.zero.toAmount(fractionDigits: tokenDecimals) + : (baseAmount.decimal / _price) + .toDecimal(scaleOnInfinitePrecision: tokenDecimals) + .toAmount(fractionDigits: tokenDecimals); + } + if (_cachedAmountToSend != null && _cachedAmountToSend == _amountToSend) { + return; + } + _cachedAmountToSend = _amountToSend; + Logging.instance.log("it changed $_amountToSend $_cachedAmountToSend", + level: LogLevel.Info); + + final amountString = _amountToSend!.localizedStringAsFixed( + locale: ref.read(localeServiceChangeNotifierProvider).locale, + decimalPlaces: tokenDecimals, + ); + + _cryptoAmountChangeLock = true; + cryptoAmountController.text = amountString; + _cryptoAmountChangeLock = false; + } else { + _amountToSend = Decimal.zero.toAmount(fractionDigits: tokenDecimals); + _cryptoAmountChangeLock = true; + cryptoAmountController.text = ""; + _cryptoAmountChangeLock = false; + } + + _updatePreviewButtonState(_address, _amountToSend); + } + + Future sendAllTapped() async { + cryptoAmountController.text = ref + .read(tokenServiceProvider)! + .balance + .spendable + .decimal + .toStringAsFixed( + ref.read(tokenServiceProvider)!.tokenContract.decimals, + ); + } + + @override + void initState() { + WidgetsBinding.instance.addPostFrameCallback((_) { + ref.refresh(tokenFeeSessionCacheProvider); + ref.read(previewTokenTxButtonStateProvider.state).state = false; + }); + + // _calculateFeesFuture = calculateFees(0); + _data = widget.autoFillData; + walletId = widget.walletId; + coin = ref.read(walletsChangeNotifierProvider).getManager(walletId).coin; + clipboard = widget.clipboard; + scanner = widget.barcodeScanner; + + sendToController = TextEditingController(); + cryptoAmountController = TextEditingController(); + baseAmountController = TextEditingController(); + nonceController = TextEditingController(); + // feeController = TextEditingController(); + + onCryptoAmountChanged = _cryptoAmountChanged; + cryptoAmountController.addListener(onCryptoAmountChanged); + + if (_data != null) { + if (_data!.amount != null) { + cryptoAmountController.text = _data!.amount!.toString(); + } + sendToController.text = _data!.contactLabel; + _address = _data!.address; + _addressToggleFlag = true; + } + + _cryptoFocus.addListener(() { + if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (_amountToSend == null) { + ref.refresh(sendAmountProvider); + } else { + ref.read(sendAmountProvider.state).state = _amountToSend!; + } + }); + } + }); + + _baseFocus.addListener(() { + if (!_cryptoFocus.hasFocus && !_baseFocus.hasFocus) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (_amountToSend == null) { + ref.refresh(sendAmountProvider); + } else { + ref.read(sendAmountProvider.state).state = _amountToSend!; + } + }); + } + }); + + super.initState(); + } + + @override + void dispose() { + cryptoAmountController.removeListener(onCryptoAmountChanged); + + sendToController.dispose(); + cryptoAmountController.dispose(); + baseAmountController.dispose(); + nonceController.dispose(); + // feeController.dispose(); + + _addressFocusNode.dispose(); + _cryptoFocus.dispose(); + _baseFocus.dispose(); + _nonceFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + + final tokenContract = ref.watch(tokenServiceProvider)!.tokenContract; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox( + height: 4, + ), + if (coin == Coin.firo) + Text( + "Send from", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconRight, + ), + textAlign: TextAlign.left, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Amount", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconRight, + ), + textAlign: TextAlign.left, + ), + CustomTextButton( + text: "Send all ${tokenContract.symbol}", + onTap: sendAllTapped, + ), + ], + ), + const SizedBox( + height: 10, + ), + TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + style: STextStyles.smallMed14(context).copyWith( + color: Theme.of(context).extension()!.textDark, + ), + key: const Key("amountInputFieldCryptoTextFieldKey"), + controller: cryptoAmountController, + focusNode: _cryptoFocus, + keyboardType: Util.isDesktop + ? null + : const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), + textAlign: TextAlign.right, + inputFormatters: [ + // regex to validate a crypto amount with 8 decimal places + TextInputFormatter.withFunction((oldValue, newValue) => RegExp( + _kCryptoAmountRegex.replaceAll( + "0,8", + "0,${tokenContract.decimals}", + ), + ).hasMatch(newValue.text) + ? newValue + : oldValue), + ], + onChanged: (newValue) {}, + decoration: InputDecoration( + contentPadding: const EdgeInsets.only( + top: 22, + right: 12, + bottom: 22, + ), + hintText: "0", + hintStyle: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldDefaultText, + ), + prefixIcon: FittedBox( + fit: BoxFit.scaleDown, + child: Padding( + padding: const EdgeInsets.all(12), + child: Text( + tokenContract.symbol, + style: STextStyles.smallMed14(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + ), + ), + ), + ), + if (Prefs.instance.externalCalls) + const SizedBox( + height: 10, + ), + if (Prefs.instance.externalCalls) + TextField( + autocorrect: Util.isDesktop ? false : true, + enableSuggestions: Util.isDesktop ? false : true, + style: STextStyles.smallMed14(context).copyWith( + color: Theme.of(context).extension()!.textDark, + ), + key: const Key("amountInputFieldFiatTextFieldKey"), + controller: baseAmountController, + focusNode: _baseFocus, + keyboardType: Util.isDesktop + ? null + : const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), + textAlign: TextAlign.right, + inputFormatters: [ + // regex to validate a fiat amount with 2 decimal places + TextInputFormatter.withFunction((oldValue, newValue) => + RegExp(r'^([0-9]*[,.]?[0-9]{0,2}|[,.][0-9]{0,2})$') + .hasMatch(newValue.text) + ? newValue + : oldValue), + ], + onChanged: fiatTextFieldOnChanged, + decoration: InputDecoration( + contentPadding: const EdgeInsets.only( + top: 22, + right: 12, + bottom: 22, + ), + hintText: "0", + hintStyle: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldDefaultText, + ), + prefixIcon: FittedBox( + fit: BoxFit.scaleDown, + child: Padding( + padding: const EdgeInsets.all(12), + child: Text( + ref.watch(prefsChangeNotifierProvider + .select((value) => value.currency)), + style: STextStyles.smallMed14(context).copyWith( + color: Theme.of(context) + .extension()! + .accentColorDark), + ), + ), + ), + ), + ), + const SizedBox( + height: 20, + ), + Text( + "Send to", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconRight, + ), + textAlign: TextAlign.left, + ), + const SizedBox( + height: 10, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + minLines: 1, + maxLines: 5, + key: const Key("sendViewAddressFieldKey"), + controller: sendToController, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + // inputFormatters: [ + // FilteringTextInputFormatter.allow( + // RegExp("[a-zA-Z0-9]{34}")), + // ], + toolbarOptions: const ToolbarOptions( + copy: false, + cut: false, + paste: true, + selectAll: false, + ), + onChanged: (newValue) { + _address = newValue; + _updatePreviewButtonState(_address, _amountToSend); + + setState(() { + _addressToggleFlag = newValue.isNotEmpty; + }); + }, + focusNode: _addressFocusNode, + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveText, + height: 1.8, + ), + decoration: standardInputDecoration( + "Enter ${tokenContract.symbol} address", + _addressFocusNode, + context, + desktopMed: true, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 16, + top: 11, + bottom: 12, + right: 5, + ), + suffixIcon: Padding( + padding: sendToController.text.isEmpty + ? const EdgeInsets.only(right: 8) + : const EdgeInsets.only(right: 0), + child: UnconstrainedBox( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _addressToggleFlag + ? TextFieldIconButton( + key: const Key( + "sendTokenViewClearAddressFieldButtonKey"), + onTap: () { + sendToController.text = ""; + _address = ""; + _updatePreviewButtonState( + _address, _amountToSend); + setState(() { + _addressToggleFlag = false; + }); + }, + child: const XIcon(), + ) + : TextFieldIconButton( + key: const Key( + "sendTokenViewPasteAddressFieldButtonKey"), + onTap: pasteAddress, + child: sendToController.text.isEmpty + ? const ClipboardIcon() + : const XIcon(), + ), + if (sendToController.text.isEmpty) + TextFieldIconButton( + key: const Key("sendTokenViewAddressBookButtonKey"), + onTap: () async { + final entry = + await showDialog( + context: context, + builder: (context) => DesktopDialog( + maxWidth: 696, + maxHeight: 600, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "Address book", + style: + STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: AddressBookAddressChooser( + coin: coin, + ), + ), + ], + ), + ), + ); + + if (entry != null) { + sendToController.text = + entry.other ?? entry.label; + + _address = entry.address; + + _updatePreviewButtonState( + _address, + _amountToSend, + ); + + setState(() { + _addressToggleFlag = true; + }); + } + }, + child: const AddressBookIcon(), + ), + ], + ), + ), + ), + ), + ), + ), + Builder( + builder: (_) { + final error = _updateInvalidAddressText( + _address ?? "", + ref.read(walletsChangeNotifierProvider).getManager(walletId), + ); + + if (error == null || error.isEmpty) { + return Container(); + } else { + return Align( + alignment: Alignment.topLeft, + child: Padding( + padding: const EdgeInsets.only( + left: 12.0, + top: 4.0, + ), + child: Text( + error, + textAlign: TextAlign.left, + style: STextStyles.label(context).copyWith( + color: + Theme.of(context).extension()!.textError, + ), + ), + ), + ); + } + }, + ), + const SizedBox( + height: 20, + ), + Text( + "Transaction fee (max)", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconRight, + ), + textAlign: TextAlign.left, + ), + const SizedBox( + height: 10, + ), + DesktopFeeDropDown( + walletId: walletId, + isToken: true, + ), + const SizedBox( + height: 20, + ), + Text( + "Nonce", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconRight, + ), + textAlign: TextAlign.left, + ), + const SizedBox( + height: 10, + ), + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + minLines: 1, + maxLines: 1, + key: const Key("sendViewNonceFieldKey"), + controller: nonceController, + readOnly: false, + autocorrect: false, + enableSuggestions: false, + keyboardType: const TextInputType.numberWithOptions(), + focusNode: _nonceFocusNode, + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textFieldActiveText, + height: 1.8, + ), + decoration: standardInputDecoration( + "Leave empty to auto select nonce", + _nonceFocusNode, + context, + desktopMed: true, + ).copyWith( + contentPadding: const EdgeInsets.only( + left: 16, + top: 11, + bottom: 12, + right: 5, + ), + ), + ), + ), + const SizedBox( + height: 36, + ), + PrimaryButton( + buttonHeight: ButtonHeight.l, + label: "Preview send", + enabled: ref.watch(previewTokenTxButtonStateProvider.state).state, + onPressed: ref.watch(previewTokenTxButtonStateProvider.state).state + ? previewSend + : null, + ) + ], + ); + } +} diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart new file mode 100644 index 000000000..f118a88e2 --- /dev/null +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_features.dart @@ -0,0 +1,385 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/pages/paynym/paynym_claim_view.dart'; +import 'package:stackwallet/pages/paynym/paynym_home_view.dart'; +import 'package:stackwallet/pages_desktop_specific/coin_control/desktop_coin_control_view.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_menu.dart'; +import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart'; +import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/more_features/more_features_dialog.dart'; +import 'package:stackwallet/providers/desktop/current_desktop_menu_item.dart'; +import 'package:stackwallet/providers/global/paynym_api_provider.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/providers/wallet/my_paynym_account_state_provider.dart'; +import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; +import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/custom_loading_overlay.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/loading_indicator.dart'; + +class DesktopWalletFeatures extends ConsumerStatefulWidget { + const DesktopWalletFeatures({ + Key? key, + required this.walletId, + }) : super(key: key); + + final String walletId; + + @override + ConsumerState createState() => + _DesktopWalletFeaturesState(); +} + +class _DesktopWalletFeaturesState extends ConsumerState { + static const double buttonWidth = 120; + + Future _onSwapPressed() async { + ref.read(currentDesktopMenuItemProvider.state).state = + DesktopMenuItemId.exchange; + ref.read(prevDesktopMenuItemProvider.state).state = + DesktopMenuItemId.exchange; + } + + Future _onBuyPressed() async { + ref.read(currentDesktopMenuItemProvider.state).state = + DesktopMenuItemId.buy; + ref.read(prevDesktopMenuItemProvider.state).state = DesktopMenuItemId.buy; + } + + Future _onMorePressed() async { + await showDialog( + context: context, + builder: (_) => MoreFeaturesDialog( + walletId: widget.walletId, + onPaynymPressed: _onPaynymPressed, + onCoinControlPressed: _onCoinControlPressed, + onAnonymizeAllPressed: _onAnonymizeAllPressed, + onWhirlpoolPressed: _onWhirlpoolPressed, + ), + ); + } + + void _onWhirlpoolPressed() { + Navigator.of(context, rootNavigator: true).pop(); + } + + void _onCoinControlPressed() { + Navigator.of(context, rootNavigator: true).pop(); + + Navigator.of(context).pushNamed( + DesktopCoinControlView.routeName, + arguments: widget.walletId, + ); + } + + Future _onAnonymizeAllPressed() async { + Navigator.of(context, rootNavigator: true).pop(); + await showDialog( + context: context, + barrierDismissible: false, + builder: (context) => DesktopDialog( + maxWidth: 500, + maxHeight: 210, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 20), + child: Column( + children: [ + Text( + "Attention!", + style: STextStyles.desktopH2(context), + ), + const SizedBox(height: 16), + Text( + "You're about to anonymize all of your public funds.", + style: STextStyles.desktopTextSmall(context), + ), + const SizedBox(height: 32), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SecondaryButton( + width: 200, + buttonHeight: ButtonHeight.l, + label: "Cancel", + onPressed: () { + Navigator.of(context).pop(); + }, + ), + const SizedBox(width: 20), + PrimaryButton( + width: 200, + buttonHeight: ButtonHeight.l, + label: "Continue", + onPressed: () { + Navigator.of(context).pop(); + + unawaited( + _attemptAnonymize(), + ); + }, + ) + ], + ), + ], + ), + ), + ), + ); + } + + Future _attemptAnonymize() async { + final managerProvider = ref + .read(walletsChangeNotifierProvider) + .getManagerProvider(widget.walletId); + + bool shouldPop = false; + unawaited( + showDialog( + context: context, + builder: (context) => WillPopScope( + child: const CustomLoadingOverlay( + message: "Anonymizing balance", + eventBus: null, + ), + onWillPop: () async => shouldPop, + ), + ), + ); + final firoWallet = ref.read(managerProvider).wallet as FiroWallet; + + final publicBalance = firoWallet.availablePublicBalance(); + if (publicBalance <= Amount.zero) { + shouldPop = true; + if (context.mounted) { + Navigator.of(context, rootNavigator: true).pop(); + Navigator.of(context).popUntil( + ModalRoute.withName(DesktopWalletView.routeName), + ); + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "No funds available to anonymize!", + context: context, + ), + ); + } + return; + } + + try { + await firoWallet.anonymizeAllPublicFunds(); + shouldPop = true; + if (context.mounted) { + Navigator.of(context, rootNavigator: true).pop(); + Navigator.of(context).popUntil( + ModalRoute.withName(DesktopWalletView.routeName), + ); + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "Anonymize transaction submitted", + context: context, + ), + ); + } + } catch (e) { + shouldPop = true; + if (context.mounted) { + Navigator.of(context, rootNavigator: true).pop(); + Navigator.of(context).popUntil( + ModalRoute.withName(DesktopWalletView.routeName), + ); + await showDialog( + context: context, + builder: (_) => DesktopDialog( + maxWidth: 400, + maxHeight: 300, + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Anonymize all failed", + style: STextStyles.desktopH3(context), + ), + const Spacer( + flex: 1, + ), + Text( + "Reason: $e", + style: STextStyles.desktopTextSmall(context), + ), + const Spacer( + flex: 2, + ), + Row( + children: [ + const Spacer(), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Ok", + buttonHeight: ButtonHeight.l, + onPressed: + Navigator.of(context, rootNavigator: true).pop, + ), + ), + ], + ) + ], + ), + ), + ), + ); + } + } + } + + Future _onPaynymPressed() async { + Navigator.of(context, rootNavigator: true).pop(); + + unawaited( + showDialog( + context: context, + builder: (context) { + return const LoadingIndicator( + width: 100, + ); + }, + ), + ); + + final manager = + ref.read(walletsChangeNotifierProvider).getManager(widget.walletId); + + final wallet = manager.wallet as PaynymWalletInterface; + + final code = await wallet.getPaymentCode(isSegwit: false); + + final account = await ref.read(paynymAPIProvider).nym(code.toString()); + + Logging.instance.log( + "my nym account: $account", + level: LogLevel.Info, + ); + + if (context.mounted) { + Navigator.of(context, rootNavigator: true).pop(); + + // check if account exists and for matching code to see if claimed + if (account.value != null && + account.value!.nonSegwitPaymentCode.claimed && + account.value!.segwit) { + ref.read(myPaynymAccountStateProvider.state).state = account.value!; + + await Navigator.of(context).pushNamed( + PaynymHomeView.routeName, + arguments: widget.walletId, + ); + } else { + await Navigator.of(context).pushNamed( + PaynymClaimView.routeName, + arguments: widget.walletId, + ); + } + } + } + + @override + Widget build(BuildContext context) { + final manager = ref.watch( + walletsChangeNotifierProvider.select( + (value) => value.getManager(widget.walletId), + ), + ); + + final showMore = manager.hasPaynymSupport || + (manager.hasCoinControlSupport && + ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.enableCoinControl, + ), + )) || + manager.coin == Coin.firo || + manager.coin == Coin.firoTestNet || + manager.hasWhirlpoolSupport; + + return Row( + children: [ + if (Constants.enableExchange) + SecondaryButton( + label: "Swap", + width: buttonWidth, + buttonHeight: ButtonHeight.l, + icon: SvgPicture.asset( + Assets.svg.arrowRotate, + height: 20, + width: 20, + color: Theme.of(context) + .extension()! + .buttonTextSecondary, + ), + onPressed: () => _onSwapPressed(), + ), + if (Constants.enableExchange) + const SizedBox( + width: 16, + ), + if (Constants.enableExchange) + SecondaryButton( + label: "Buy", + width: buttonWidth, + buttonHeight: ButtonHeight.l, + icon: SvgPicture.file( + File( + ref.watch(themeProvider.select((value) => value.assets.buy)), + ), + height: 20, + width: 20, + color: Theme.of(context) + .extension()! + .buttonTextSecondary, + ), + onPressed: () => _onBuyPressed(), + ), + if (showMore) + const SizedBox( + width: 16, + ), + if (showMore) + SecondaryButton( + label: "More", + width: buttonWidth, + buttonHeight: ButtonHeight.l, + icon: SvgPicture.asset( + Assets.svg.bars, + height: 20, + width: 20, + color: Theme.of(context) + .extension()! + .buttonTextSecondary, + ), + onPressed: () => _onMorePressed(), + ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart index 8291c35d3..c0422a686 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_wallet_summary.dart @@ -1,205 +1,170 @@ -import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/pages/token_view/token_view.dart'; import 'package:stackwallet/pages/wallet_view/sub_widgets/wallet_refresh_button.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_balance_toggle_button.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/wallet/wallet_balance_toggle_state_provider.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; -import 'package:stackwallet/services/coins/manager.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart'; -import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; -import 'package:stackwallet/widgets/animated_text.dart'; -class DesktopWalletSummary extends StatefulWidget { +class DesktopWalletSummary extends ConsumerStatefulWidget { const DesktopWalletSummary({ Key? key, required this.walletId, - required this.managerProvider, required this.initialSyncStatus, + this.isToken = false, }) : super(key: key); final String walletId; - final ChangeNotifierProvider managerProvider; final WalletSyncStatus initialSyncStatus; + final bool isToken; @override - State createState() => _WDesktopWalletSummaryState(); + ConsumerState createState() => + _WDesktopWalletSummaryState(); } -class _WDesktopWalletSummaryState extends State { +class _WDesktopWalletSummaryState extends ConsumerState { late final String walletId; - late final ChangeNotifierProvider managerProvider; - - Decimal? _balanceTotalCached; - Decimal? _balanceCached; @override void initState() { walletId = widget.walletId; - managerProvider = widget.managerProvider; super.initState(); } @override Widget build(BuildContext context) { debugPrint("BUILD: $runtimeType"); + + final externalCalls = ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.externalCalls, + ), + ); + final coin = ref.watch( + walletsChangeNotifierProvider.select( + (value) => value.getManager(widget.walletId).coin, + ), + ); + final locale = ref.watch( + localeServiceChangeNotifierProvider.select((value) => value.locale)); + + final baseCurrency = ref + .watch(prefsChangeNotifierProvider.select((value) => value.currency)); + + final priceTuple = widget.isToken + ? ref.watch(priceAnd24hChangeNotifierProvider.select((value) => + value.getTokenPrice(ref.watch(tokenServiceProvider + .select((value) => value!.tokenContract.address))))) + : ref.watch(priceAnd24hChangeNotifierProvider + .select((value) => value.getPrice(coin))); + + final _showAvailable = + ref.watch(walletBalanceToggleStateProvider.state).state == + WalletBalanceToggleState.available; + + final unit = widget.isToken + ? ref.watch( + tokenServiceProvider.select((value) => value!.tokenContract.symbol)) + : coin.ticker; + final decimalPlaces = widget.isToken + ? ref.watch(tokenServiceProvider + .select((value) => value!.tokenContract.decimals)) + : coin.decimals; + + Balance balance = widget.isToken + ? ref.watch(tokenServiceProvider.select((value) => value!.balance)) + : ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(walletId).balance)); + + Amount balanceToShow; + if (coin == Coin.firo || coin == Coin.firoTestNet) { + Balance? balanceSecondary = ref + .watch( + walletsChangeNotifierProvider.select( + (value) => + value.getManager(widget.walletId).wallet as FiroWallet?, + ), + ) + ?.balancePrivate; + final showPrivate = + ref.watch(walletPrivateBalanceToggleStateProvider.state).state == + WalletBalanceToggleState.available; + + if (_showAvailable) { + balanceToShow = + showPrivate ? balanceSecondary!.spendable : balance.spendable; + } else { + balanceToShow = showPrivate ? balanceSecondary!.total : balance.total; + } + } else { + if (_showAvailable) { + balanceToShow = balance.spendable; + } else { + balanceToShow = balance.total; + } + } + return Consumer( builder: (context, ref, __) { - final Coin coin = - ref.watch(managerProvider.select((value) => value.coin)); return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Consumer( - builder: (_, ref, __) { - final externalCalls = ref.watch(prefsChangeNotifierProvider - .select((value) => value.externalCalls)); - - Future? totalBalanceFuture; - Future? availableBalanceFuture; - if (coin == Coin.firo || coin == Coin.firoTestNet) { - final firoWallet = ref.watch( - managerProvider.select((value) => value.wallet)) - as FiroWallet; - totalBalanceFuture = firoWallet.availablePublicBalance(); - availableBalanceFuture = - firoWallet.availablePrivateBalance(); - } else { - totalBalanceFuture = ref.watch(managerProvider - .select((value) => value.totalBalance)); - - availableBalanceFuture = ref.watch(managerProvider - .select((value) => value.availableBalance)); - } - - final locale = ref.watch(localeServiceChangeNotifierProvider - .select((value) => value.locale)); - - final baseCurrency = ref.watch(prefsChangeNotifierProvider - .select((value) => value.currency)); - - final priceTuple = ref.watch( - priceAnd24hChangeNotifierProvider - .select((value) => value.getPrice(coin))); - - final _showAvailable = ref - .watch(walletBalanceToggleStateProvider.state) - .state == - WalletBalanceToggleState.available; - - return FutureBuilder( - future: _showAvailable - ? availableBalanceFuture - : totalBalanceFuture, - builder: (fbContext, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done && - snapshot.hasData && - snapshot.data != null) { - if (_showAvailable) { - _balanceCached = snapshot.data!; - } else { - _balanceTotalCached = snapshot.data!; - } - } - Decimal? balanceToShow = _showAvailable - ? _balanceCached - : _balanceTotalCached; - - if (balanceToShow != null) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - FittedBox( - fit: BoxFit.scaleDown, - child: Text( - "${Format.localizedStringAsFixed( - value: balanceToShow, - locale: locale, - decimalPlaces: 8, - )} ${coin.ticker}", - style: STextStyles.desktopH3(context), - ), - ), - if (externalCalls) - Text( - "${Format.localizedStringAsFixed( - value: priceTuple.item1 * balanceToShow, - locale: locale, - decimalPlaces: 2, - )} $baseCurrency", - style: - STextStyles.desktopTextExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), - ], - ); - } else { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AnimatedText( - stringsToLoopThrough: const [ - "Loading balance ", - "Loading balance. ", - "Loading balance.. ", - "Loading balance..." - ], - style: STextStyles.desktopH3(context).copyWith( - fontSize: 24, - color: Theme.of(context) - .extension()! - .textDark, - ), - ), - if (externalCalls) - AnimatedText( - stringsToLoopThrough: const [ - "Loading balance ", - "Loading balance. ", - "Loading balance.. ", - "Loading balance..." - ], - style: - STextStyles.desktopTextExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), - ], - ); - } - }, - ); - }, + FittedBox( + fit: BoxFit.scaleDown, + child: Text( + "${balanceToShow.localizedStringAsFixed( + locale: locale, + decimalPlaces: decimalPlaces, + )} $unit", + style: STextStyles.desktopH3(context), + ), ), + if (externalCalls) + Text( + "${Amount.fromDecimal( + priceTuple.item1 * balanceToShow.decimal, + fractionDigits: 2, + ).localizedStringAsFixed( + locale: locale, + decimalPlaces: 2, + )} $baseCurrency", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), ], ), - if (coin == Coin.firo || coin == Coin.firoTestNet) - const SizedBox( - width: 8, - ), - if (coin == Coin.firo || coin == Coin.firoTestNet) - const DesktopBalanceToggleButton(), const SizedBox( width: 8, ), WalletRefreshButton( walletId: walletId, initialSyncStatus: widget.initialSyncStatus, - ) + ), + if (coin == Coin.firo || coin == Coin.firoTestNet) + const SizedBox( + width: 8, + ), + if (coin == Coin.firo || coin == Coin.firoTestNet) + const DesktopPrivateBalanceToggleButton(), + const SizedBox( + width: 8, + ), + const DesktopBalanceToggleButton(), ], ); }, diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/more_features/more_features_dialog.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/more_features/more_features_dialog.dart new file mode 100644 index 000000000..e2dbfa015 --- /dev/null +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/more_features/more_features_dialog.dart @@ -0,0 +1,175 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; + +class MoreFeaturesDialog extends ConsumerStatefulWidget { + const MoreFeaturesDialog({ + Key? key, + required this.walletId, + required this.onPaynymPressed, + required this.onCoinControlPressed, + required this.onAnonymizeAllPressed, + required this.onWhirlpoolPressed, + }) : super(key: key); + + final String walletId; + final VoidCallback? onPaynymPressed; + final VoidCallback? onCoinControlPressed; + final VoidCallback? onAnonymizeAllPressed; + final VoidCallback? onWhirlpoolPressed; + + @override + ConsumerState createState() => _MoreFeaturesDialogState(); +} + +class _MoreFeaturesDialogState extends ConsumerState { + @override + Widget build(BuildContext context) { + final manager = ref.watch( + walletsChangeNotifierProvider.select( + (value) => value.getManager(widget.walletId), + ), + ); + + final coinControlPrefEnabled = ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.enableCoinControl, + ), + ); + + return DesktopDialog( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only( + left: 32, + ), + child: Text( + "More features", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + if (manager.coin == Coin.firo || manager.coin == Coin.firoTestNet) + _MoreFeaturesItem( + label: "Anonymize funds", + detail: "Anonymize funds", + iconAsset: Assets.svg.anonymize, + onPressed: () => widget.onAnonymizeAllPressed?.call(), + ), + if (manager.hasWhirlpoolSupport) + _MoreFeaturesItem( + label: "Whirlpool", + detail: "Powerful Bitcoin privacy enhancer", + iconAsset: Assets.svg.whirlPool, + onPressed: () => widget.onWhirlpoolPressed?.call(), + ), + if (manager.hasCoinControlSupport && coinControlPrefEnabled) + _MoreFeaturesItem( + label: "Coin control", + detail: "Control, freeze, and utilize outputs at your discretion", + iconAsset: Assets.svg.coinControl.gamePad, + onPressed: () => widget.onCoinControlPressed?.call(), + ), + if (manager.hasPaynymSupport) + _MoreFeaturesItem( + label: "PayNym", + detail: "Increased address privacy using BIP47", + iconAsset: Assets.svg.robotHead, + onPressed: () => widget.onPaynymPressed?.call(), + ), + const SizedBox( + height: 28, + ), + ], + ), + ); + } +} + +class _MoreFeaturesItem extends StatelessWidget { + const _MoreFeaturesItem({ + Key? key, + required this.label, + required this.detail, + required this.iconAsset, + this.onPressed, + }) : super(key: key); + + static const double iconSizeBG = 46; + static const double iconSize = 24; + + final String label; + final String detail; + final String iconAsset; + final VoidCallback? onPressed; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric( + vertical: 6, + horizontal: 32, + ), + child: RoundedContainer( + color: Colors.transparent, + borderColor: + Theme.of(context).extension()!.textFieldDefaultBG, + onPressed: onPressed, + child: Row( + children: [ + RoundedContainer( + padding: const EdgeInsets.all(0), + color: + Theme.of(context).extension()!.settingsIconBack, + width: iconSizeBG, + height: iconSizeBG, + radiusMultiplier: iconSizeBG, + child: Center( + child: SvgPicture.asset( + iconAsset, + width: iconSize, + height: iconSize, + color: Theme.of(context) + .extension()! + .settingsIconIcon, + ), + ), + ), + const SizedBox( + width: 16, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + label, + style: STextStyles.w600_20(context), + ), + Text( + detail, + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + ], + ) + ], + ), + ), + ); + } +} diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart index 6813d42cc..f57ad8c2b 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/my_wallet.dart @@ -1,87 +1,101 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/pages/wallet_view/sub_widgets/transactions_list.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_receive.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_send.dart'; -import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/send_receive_tab_menu.dart'; -import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_token_send.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/widgets/custom_tab_view.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; -class MyWallet extends StatefulWidget { +class MyWallet extends ConsumerStatefulWidget { const MyWallet({ Key? key, required this.walletId, + this.contractAddress, }) : super(key: key); final String walletId; + final String? contractAddress; @override - State createState() => _MyWalletState(); + ConsumerState createState() => _MyWalletState(); } -class _MyWalletState extends State { - int _selectedIndex = 0; +class _MyWalletState extends ConsumerState { + final titles = [ + "Send", + "Receive", + ]; + + late final bool isEth; + + @override + void initState() { + isEth = ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId) + .coin == + Coin.ethereum; + + if (isEth && widget.contractAddress == null) { + titles.add("Transactions"); + } + + super.initState(); + } @override Widget build(BuildContext context) { return ListView( primary: false, children: [ - Text( - "My wallet", - style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveSearchIconLeft, - ), - ), - const SizedBox( - height: 16, - ), - Container( - decoration: BoxDecoration( - color: Theme.of(context).extension()!.popupBG, - borderRadius: BorderRadius.vertical( - top: Radius.circular( - Constants.size.circularBorderRadius, + RoundedWhiteContainer( + padding: EdgeInsets.zero, + child: CustomTabView( + titles: titles, + children: [ + widget.contractAddress == null + ? Padding( + padding: const EdgeInsets.all(20), + child: DesktopSend( + walletId: widget.walletId, + ), + ) + : Padding( + padding: const EdgeInsets.all(20), + child: DesktopTokenSend( + walletId: widget.walletId, + ), + ), + Padding( + padding: const EdgeInsets.all(20), + child: DesktopReceive( + walletId: widget.walletId, + contractAddress: widget.contractAddress, + ), ), - ), - ), - child: SendReceiveTabMenu( - onChanged: (index) { - setState(() { - _selectedIndex = index; - }); - }, - ), - ), - Container( - decoration: BoxDecoration( - color: Theme.of(context).extension()!.popupBG, - borderRadius: BorderRadius.vertical( - bottom: Radius.circular( - Constants.size.circularBorderRadius, - ), - ), - ), - child: AnimatedCrossFade( - firstChild: Padding( - key: const Key("desktopSendViewPortKey"), - padding: const EdgeInsets.all(20), - child: DesktopSend( - walletId: widget.walletId, - ), - ), - secondChild: Padding( - key: const Key("desktopReceiveViewPortKey"), - padding: const EdgeInsets.all(20), - child: DesktopReceive( - walletId: widget.walletId, - ), - ), - crossFadeState: _selectedIndex == 0 - ? CrossFadeState.showFirst - : CrossFadeState.showSecond, - duration: const Duration(milliseconds: 250), + if (isEth && widget.contractAddress == null) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: ConstrainedBox( + constraints: BoxConstraints( + maxHeight: MediaQuery.of(context).size.height - 362, + ), + child: TransactionsList( + walletId: widget.walletId, + managerProvider: ref.watch( + walletsChangeNotifierProvider.select( + (value) => value.getManagerProvider( + widget.walletId, + ), + ), + ), + ), + ), + ), + ], ), ), ], diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/network_info_button.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/network_info_button.dart index 5195a4b9b..b44efa09b 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/network_info_button.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/network_info_button.dart @@ -10,9 +10,9 @@ import 'package:stackwallet/route_generator.dart'; import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/qr_code_desktop_popup_content.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/qr_code_desktop_popup_content.dart index 3a7a94885..3e5d23ab1 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/qr_code_desktop_popup_content.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/qr_code_desktop_popup_content.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:qr_flutter/qr_flutter.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/recent_desktop_transactions.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/recent_desktop_transactions.dart deleted file mode 100644 index 59e01f0b7..000000000 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/recent_desktop_transactions.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/pages/wallet_view/sub_widgets/transactions_list.dart'; -import 'package:stackwallet/pages/wallet_view/transaction_views/all_transactions_view.dart'; -import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; -import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; - -class RecentDesktopTransactions extends ConsumerStatefulWidget { - const RecentDesktopTransactions({ - Key? key, - required this.walletId, - }) : super(key: key); - - final String walletId; - - @override - ConsumerState createState() => - _RecentDesktopTransactionsState(); -} - -class _RecentDesktopTransactionsState - extends ConsumerState { - @override - Widget build(BuildContext context) { - return Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Recent transactions", - style: STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textFieldActiveSearchIconLeft, - ), - ), - BlueTextButton( - text: "See all", - onTap: () { - Navigator.of(context).pushNamed( - AllTransactionsView.routeName, - arguments: widget.walletId, - ); - }, - ), - ], - ), - const SizedBox( - height: 16, - ), - Expanded( - child: TransactionsList( - managerProvider: ref.watch(walletsChangeNotifierProvider - .select((value) => value.getManagerProvider(widget.walletId))), - walletId: widget.walletId, - ), - ), - ], - ); - } -} diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/send_receive_tab_menu.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/send_receive_tab_menu.dart deleted file mode 100644 index f42ed297d..000000000 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/send_receive_tab_menu.dart +++ /dev/null @@ -1,165 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; - -class SendReceiveTabMenu extends StatefulWidget { - const SendReceiveTabMenu({ - Key? key, - this.initialIndex = 0, - this.onChanged, - }) : super(key: key); - - final int initialIndex; - final void Function(int)? onChanged; - - @override - State createState() => _SendReceiveTabMenuState(); -} - -class _SendReceiveTabMenuState extends State { - late int _selectedIndex; - - void _onChanged(int newIndex) { - if (_selectedIndex != newIndex) { - setState(() { - _selectedIndex = newIndex; - }); - widget.onChanged?.call(_selectedIndex); - } - } - - @override - void initState() { - _selectedIndex = widget.initialIndex; - super.initState(); - } - - @override - Widget build(BuildContext context) { - return Row( - children: [ - Expanded( - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: () => _onChanged(0), - child: Container( - color: Colors.transparent, - child: Column( - children: [ - const SizedBox( - height: 16, - ), - AnimatedCrossFade( - firstChild: Text( - "Send", - style: - STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorBlue, - ), - ), - secondChild: Text( - "Send", - style: - STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), - crossFadeState: _selectedIndex == 0 - ? CrossFadeState.showFirst - : CrossFadeState.showSecond, - duration: const Duration(milliseconds: 250), - ), - const SizedBox( - height: 19, - ), - Container( - height: 2, - decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .backgroundAppBar, - ), - ), - ], - ), - ), - ), - ), - ), - Expanded( - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: () => _onChanged(1), - child: Container( - color: Colors.transparent, - child: Column( - children: [ - const SizedBox( - height: 16, - ), - AnimatedCrossFade( - firstChild: Text( - "Receive", - style: - STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .accentColorBlue, - ), - ), - secondChild: Text( - "Receive", - style: - STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ), - ), - crossFadeState: _selectedIndex == 1 - ? CrossFadeState.showFirst - : CrossFadeState.showSecond, - duration: const Duration(milliseconds: 250), - ), - const SizedBox( - height: 19, - ), - Stack( - children: [ - Container( - height: 2, - decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .backgroundAppBar, - ), - ), - AnimatedSlide( - offset: Offset(_selectedIndex == 0 ? -1 : 0, 0), - duration: const Duration(milliseconds: 250), - child: Container( - height: 2, - decoration: BoxDecoration( - color: Theme.of(context) - .extension()! - .accentColorBlue), - ), - ), - ], - ), - ], - ), - ), - ), - ), - ), - ], - ); - } -} diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart index f86268646..e6f01f8fb 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart @@ -7,10 +7,10 @@ import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart'; import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_button.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_button.dart index 2ce006851..85cb2ef6c 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_button.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_button.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/unlock_wallet_keys_desktop.dart'; import 'package:stackwallet/route_generator.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; class WalletKeysButton extends StatelessWidget { const WalletKeysButton({ diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart index be3732a95..7ee97045b 100644 --- a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_keys_desktop_popup.dart @@ -5,11 +5,11 @@ import 'package:flutter/services.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/qr_code_desktop_popup_content.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/address_utils.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/clipboard_interface.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; diff --git a/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart new file mode 100644 index 000000000..9a91bb039 --- /dev/null +++ b/lib/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/wallet_options_button.dart @@ -0,0 +1,339 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/xpub_view.dart'; +import 'package:stackwallet/pages_desktop_specific/addresses/desktop_wallet_addresses_view.dart'; +import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_delete_wallet_dialog.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/route_generator.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; + +enum _WalletOptions { + addressList, + deleteWallet, + showXpub; + + String get prettyName { + switch (this) { + case _WalletOptions.addressList: + return "Address list"; + case _WalletOptions.deleteWallet: + return "Delete wallet"; + case _WalletOptions.showXpub: + return "Show xPub"; + } + } +} + +class WalletOptionsButton extends StatelessWidget { + const WalletOptionsButton({ + Key? key, + required this.walletId, + }) : super(key: key); + + final String walletId; + + @override + Widget build(BuildContext context) { + return RawMaterialButton( + constraints: const BoxConstraints( + minHeight: 32, + minWidth: 32, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(1000), + ), + onPressed: () async { + final func = await showDialog<_WalletOptions?>( + context: context, + barrierColor: Colors.transparent, + builder: (context) { + return WalletOptionsPopupMenu( + onDeletePressed: () async { + Navigator.of(context).pop(_WalletOptions.deleteWallet); + }, + onAddressListPressed: () async { + Navigator.of(context).pop(_WalletOptions.addressList); + }, + onShowXpubPressed: () async { + Navigator.of(context).pop(_WalletOptions.showXpub); + }, + walletId: walletId, + ); + }, + ); + + if (context.mounted && func != null) { + switch (func) { + case _WalletOptions.addressList: + unawaited( + Navigator.of(context).pushNamed( + DesktopWalletAddressesView.routeName, + arguments: walletId, + ), + ); + break; + case _WalletOptions.deleteWallet: + final result = await showDialog( + context: context, + barrierDismissible: false, + builder: (context) => Navigator( + initialRoute: DesktopDeleteWalletDialog.routeName, + onGenerateRoute: RouteGenerator.generateRoute, + onGenerateInitialRoutes: (_, __) { + return [ + RouteGenerator.generateRoute( + RouteSettings( + name: DesktopDeleteWalletDialog.routeName, + arguments: walletId, + ), + ), + ]; + }, + ), + ); + + if (result == true) { + if (context.mounted) { + Navigator.of(context).pop(); + } + } + break; + case _WalletOptions.showXpub: + final result = await showDialog( + context: context, + barrierDismissible: false, + builder: (context) => Navigator( + initialRoute: XPubView.routeName, + onGenerateRoute: RouteGenerator.generateRoute, + onGenerateInitialRoutes: (_, __) { + return [ + RouteGenerator.generateRoute( + RouteSettings( + name: XPubView.routeName, + arguments: walletId, + ), + ), + ]; + }, + ), + ); + + if (result == true) { + if (context.mounted) { + Navigator.of(context).pop(); + } + } + break; + } + } + }, + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 19, + horizontal: 32, + ), + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.ellipsis, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .buttonTextSecondary, + ), + ], + ), + ), + ); + } +} + +class WalletOptionsPopupMenu extends ConsumerWidget { + const WalletOptionsPopupMenu({ + Key? key, + required this.onDeletePressed, + required this.onAddressListPressed, + required this.onShowXpubPressed, + required this.walletId, + }) : super(key: key); + + final VoidCallback onDeletePressed; + final VoidCallback onAddressListPressed; + final VoidCallback onShowXpubPressed; + final String walletId; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final bool xpubEnabled = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(walletId).hasXPub)); + + return Stack( + children: [ + Positioned( + top: 24, + left: MediaQuery.of(context).size.width - 234, + child: Container( + width: 220, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius * 2, + ), + color: Theme.of(context).extension()!.popupBG, + boxShadow: [ + Theme.of(context).extension()!.standardBoxShadow, + ], + ), + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TransparentButton( + onPressed: onAddressListPressed, + child: Padding( + padding: const EdgeInsets.all(8), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SvgPicture.asset( + Assets.svg.list, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconLeft, + ), + const SizedBox(width: 14), + Expanded( + child: Text( + _WalletOptions.addressList.prettyName, + style: STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + ), + ], + ), + ), + ), + if (xpubEnabled) + const SizedBox( + height: 8, + ), + if (xpubEnabled) + TransparentButton( + onPressed: onShowXpubPressed, + child: Padding( + padding: const EdgeInsets.all(8), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SvgPicture.asset( + Assets.svg.eye, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconLeft, + ), + const SizedBox(width: 14), + Expanded( + child: Text( + _WalletOptions.showXpub.prettyName, + style: STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + ), + ], + ), + ), + ), + const SizedBox( + height: 8, + ), + TransparentButton( + onPressed: onDeletePressed, + child: Padding( + padding: const EdgeInsets.all(8), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SvgPicture.asset( + Assets.svg.trash, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .textFieldActiveSearchIconLeft, + ), + const SizedBox(width: 14), + Expanded( + child: Text( + _WalletOptions.deleteWallet.prettyName, + style: STextStyles.desktopTextExtraExtraSmall( + context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + ), + ], + ), + ), + ), + ], + ), + ), + ), + ), + ], + ); + } +} + +class TransparentButton extends StatelessWidget { + const TransparentButton({ + Key? key, + required this.child, + this.onPressed, + }) : super(key: key); + + final Widget child; + final VoidCallback? onPressed; + + @override + Widget build(BuildContext context) { + return RawMaterialButton( + constraints: const BoxConstraints( + minHeight: 32, + minWidth: 32, + ), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: onPressed, + child: child, + ); + } +} diff --git a/lib/pages_desktop_specific/notifications/desktop_notifications_view.dart b/lib/pages_desktop_specific/notifications/desktop_notifications_view.dart index b9c03ff19..a73bd5238 100644 --- a/lib/pages_desktop_specific/notifications/desktop_notifications_view.dart +++ b/lib/pages_desktop_specific/notifications/desktop_notifications_view.dart @@ -3,8 +3,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/notifications/notification_card.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/providers/ui/unread_notifications_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; diff --git a/lib/pages_desktop_specific/password/create_password_view.dart b/lib/pages_desktop_specific/password/create_password_view.dart index 2f440ec92..0cf9c0710 100644 --- a/lib/pages_desktop_specific/password/create_password_view.dart +++ b/lib/pages_desktop_specific/password/create_password_view.dart @@ -9,11 +9,11 @@ import 'package:stackwallet/pages_desktop_specific/password/forgotten_passphrase import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; @@ -425,10 +425,10 @@ class _CreatePasswordViewState extends ConsumerState { style: nextEnabled ? Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context) + .getPrimaryEnabledButtonStyle(context) : Theme.of(context) .extension()! - .getPrimaryDisabledButtonColor(context), + .getPrimaryDisabledButtonStyle(context), onPressed: nextEnabled ? onNextPressed : null, child: Text( "Next", diff --git a/lib/pages_desktop_specific/password/delete_password_warning_view.dart b/lib/pages_desktop_specific/password/delete_password_warning_view.dart index dc2322f05..74e2aca19 100644 --- a/lib/pages_desktop_specific/password/delete_password_warning_view.dart +++ b/lib/pages_desktop_specific/password/delete_password_warning_view.dart @@ -5,11 +5,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:hive/hive.dart'; -import 'package:isar/isar.dart'; -import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/intro_view.dart'; -import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/stack_file_system.dart'; import 'package:stackwallet/utilities/text_styles.dart'; @@ -109,8 +108,14 @@ class _ForgotPasswordDesktopViewState child: Column( mainAxisSize: MainAxisSize.min, children: [ - SvgPicture.asset( - Assets.svg.stackIcon(context), + SvgPicture.file( + File( + ref.watch( + themeProvider.select( + (value) => value.assets.stackIcon, + ), + ), + ), width: 100, ), const SizedBox( diff --git a/lib/pages_desktop_specific/password/desktop_login_view.dart b/lib/pages_desktop_specific/password/desktop_login_view.dart index 713cb52c2..6ec7e0115 100644 --- a/lib/pages_desktop_specific/password/desktop_login_view.dart +++ b/lib/pages_desktop_specific/password/desktop_login_view.dart @@ -1,22 +1,29 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart'; import 'package:stackwallet/pages_desktop_specific/password/forgot_password_desktop_view.dart'; import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/db_version_migration.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; class DesktopLoginView extends ConsumerStatefulWidget { @@ -43,6 +50,25 @@ class _DesktopLoginViewState extends ConsumerState { bool hidePassword = true; bool _continueEnabled = false; + Future _checkDesktopMigrate() async { + if (Util.isDesktop) { + int dbVersion = DB.instance.get( + boxName: DB.boxNameDBInfo, key: "hive_data_version") as int? ?? + 0; + if (dbVersion < Constants.currentDataVersion) { + try { + await DbVersionMigrator().migrate( + dbVersion, + secureStore: ref.read(secureStoreProvider), + ); + } catch (e, s) { + Logging.instance.log("Cannot migrate desktop database\n$e $s", + level: LogLevel.Error, printFullLength: true); + } + } + } + } + Future login() async { try { unawaited( @@ -63,12 +89,18 @@ class _DesktopLoginViewState extends ConsumerState { await Future.delayed(const Duration(seconds: 1)); + // init security context await ref .read(storageCryptoHandlerProvider) .initFromExisting(passwordController.text); + // init desktop secure storage await (ref.read(secureStoreProvider).store as DesktopSecureStore).init(); + // check and migrate if needed + await _checkDesktopMigrate(); + + // load data await widget.load?.call(); // if no errors passphrase is correct @@ -89,14 +121,24 @@ class _DesktopLoginViewState extends ConsumerState { await Future.delayed(const Duration(seconds: 1)); - await showFloatingFlushBar( - type: FlushBarType.warning, - message: e.toString(), - context: context, - ); + if (mounted) { + await showFloatingFlushBar( + type: FlushBarType.warning, + message: e.toString(), + context: context, + ); + } } } + @override + void didChangeDependencies() { + // TODO: check if we still need to do this with new theming + // unawaited(Assets.precache(context)); + + super.didChangeDependencies(); + } + @override void initState() { passwordController = TextEditingController(); @@ -125,8 +167,14 @@ class _DesktopLoginViewState extends ConsumerState { child: Column( mainAxisSize: MainAxisSize.min, children: [ - SvgPicture.asset( - Assets.svg.stackIcon(context), + SvgPicture.file( + File( + ref.watch( + themeProvider.select( + (value) => value.assets.stackIcon, + ), + ), + ), width: 100, ), const SizedBox( @@ -150,74 +198,104 @@ class _DesktopLoginViewState extends ConsumerState { const SizedBox( height: 24, ), - ClipRRect( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - child: TextField( - key: const Key("desktopLoginPasswordFieldKey"), - focusNode: passwordFocusNode, - controller: passwordController, - style: STextStyles.desktopTextMedium(context).copyWith( - height: 2, + RoundedContainer( + padding: EdgeInsets.zero, + height: 74, + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + child: ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, ), - obscureText: hidePassword, - enableSuggestions: false, - autocorrect: false, - autofocus: true, - onSubmitted: (_) { - if (_continueEnabled) { - login(); - } - }, - decoration: standardInputDecoration( - "Enter password", - passwordFocusNode, - context, - ).copyWith( - suffixIcon: UnconstrainedBox( - child: SizedBox( - height: 70, - child: Row( - children: [ - const SizedBox( - width: 24, - ), - GestureDetector( - key: const Key( - "restoreFromFilePasswordFieldShowPasswordButtonKey"), - onTap: () async { - setState(() { - hidePassword = !hidePassword; - }); - }, - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: SvgPicture.asset( - hidePassword - ? Assets.svg.eye - : Assets.svg.eyeSlash, - color: Theme.of(context) - .extension()! - .textDark3, - width: 24, - height: 24, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: TextField( + key: const Key("desktopLoginPasswordFieldKey"), + focusNode: passwordFocusNode, + controller: passwordController, + style: STextStyles.desktopTextMedium(context), + obscureText: hidePassword, + enableSuggestions: false, + textAlignVertical: TextAlignVertical.bottom, + autocorrect: false, + autofocus: true, + onSubmitted: (_) { + if (_continueEnabled) { + login(); + } + }, + decoration: standardInputDecoration( + "Enter password", + passwordFocusNode, + context, + ).copyWith( + isDense: true, + fillColor: Colors.transparent, + focusColor: Colors.transparent, + hoverColor: Colors.transparent, + enabledBorder: OutlineInputBorder( + borderSide: const BorderSide( + color: Colors.transparent, width: 1), + borderRadius: BorderRadius.circular(10), + ), + focusedBorder: OutlineInputBorder( + borderSide: const BorderSide( + color: Colors.transparent, width: 1), + borderRadius: BorderRadius.circular(10), + ), + contentPadding: const EdgeInsets.only( + top: 0, + left: 16, + right: 16, + bottom: 0, + ), + suffixIcon: UnconstrainedBox( + child: SizedBox( + height: 40, + child: Row( + children: [ + const SizedBox( + width: 20, ), - ), + GestureDetector( + key: const Key( + "restoreFromFilePasswordFieldShowPasswordButtonKey"), + onTap: () async { + setState(() { + hidePassword = !hidePassword; + }); + }, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: SvgPicture.asset( + hidePassword + ? Assets.svg.eye + : Assets.svg.eyeSlash, + color: Theme.of(context) + .extension()! + .textDark3, + width: 20, + height: 20, + ), + ), + ), + const SizedBox( + width: 12, + ), + ], ), - const SizedBox( - width: 12, - ), - ], + ), ), ), + onChanged: (newValue) { + setState(() { + _continueEnabled = + passwordController.text.isNotEmpty; + }); + }, ), ), - onChanged: (newValue) { - setState(() { - _continueEnabled = passwordController.text.isNotEmpty; - }); - }, ), ), const SizedBox( @@ -231,7 +309,7 @@ class _DesktopLoginViewState extends ConsumerState { const SizedBox( height: 60, ), - BlueTextButton( + CustomTextButton( text: "Forgot password?", textSize: 20, onTap: () { diff --git a/lib/pages_desktop_specific/password/forgot_password_desktop_view.dart b/lib/pages_desktop_specific/password/forgot_password_desktop_view.dart index db3e40a16..381247639 100644 --- a/lib/pages_desktop_specific/password/forgot_password_desktop_view.dart +++ b/lib/pages_desktop_specific/password/forgot_password_desktop_view.dart @@ -1,16 +1,19 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages_desktop_specific/password/delete_password_warning_view.dart'; -import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; -class ForgotPasswordDesktopView extends StatefulWidget { +class ForgotPasswordDesktopView extends ConsumerStatefulWidget { const ForgotPasswordDesktopView({ Key? key, }) : super(key: key); @@ -18,11 +21,12 @@ class ForgotPasswordDesktopView extends StatefulWidget { static const String routeName = "/forgotPasswordDesktop"; @override - State createState() => + ConsumerState createState() => _ForgotPasswordDesktopViewState(); } -class _ForgotPasswordDesktopViewState extends State { +class _ForgotPasswordDesktopViewState + extends ConsumerState { @override Widget build(BuildContext context) { return DesktopScaffold( @@ -45,8 +49,14 @@ class _ForgotPasswordDesktopViewState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - SvgPicture.asset( - Assets.svg.stackIcon(context), + SvgPicture.file( + File( + ref.watch( + themeProvider.select( + (value) => value.assets.stackIcon, + ), + ), + ), width: 100, ), const SizedBox( diff --git a/lib/pages_desktop_specific/password/forgotten_passphrase_restore_from_swb.dart b/lib/pages_desktop_specific/password/forgotten_passphrase_restore_from_swb.dart index 60dad82f1..475f82b6c 100644 --- a/lib/pages_desktop_specific/password/forgotten_passphrase_restore_from_swb.dart +++ b/lib/pages_desktop_specific/password/forgotten_passphrase_restore_from_swb.dart @@ -6,7 +6,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:hive_flutter/hive_flutter.dart'; -import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/restore_create_backup.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/swb_file_system.dart'; @@ -15,12 +15,12 @@ import 'package:stackwallet/pages_desktop_specific/password/create_password_view import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; diff --git a/lib/pages_desktop_specific/settings/desktop_settings_view.dart b/lib/pages_desktop_specific/settings/desktop_settings_view.dart index 0c5f1c4e3..b30018370 100644 --- a/lib/pages_desktop_specific/settings/desktop_settings_view.dart +++ b/lib/pages_desktop_specific/settings/desktop_settings_view.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/pages_desktop_specific/settings/settings_menu.dart'; import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/advanced_settings/advanced_settings.dart'; -import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/appearance_settings.dart'; +import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart'; import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/backup_and_restore/backup_and_restore_settings.dart'; import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/currency_settings/currency_settings.dart'; import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/language_settings/language_settings.dart'; @@ -10,8 +10,8 @@ import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/nodes_ import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/security_settings.dart'; import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/syncing_preferences_settings.dart'; import 'package:stackwallet/route_generator.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; diff --git a/lib/pages_desktop_specific/settings/settings_menu.dart b/lib/pages_desktop_specific/settings/settings_menu.dart index 79881c1c8..743f633d0 100644 --- a/lib/pages_desktop_specific/settings/settings_menu.dart +++ b/lib/pages_desktop_specific/settings/settings_menu.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages_desktop_specific/settings/settings_menu_item.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; final selectedSettingsMenuItemStateProvider = StateProvider((_) => 0); diff --git a/lib/pages_desktop_specific/settings/settings_menu/advanced_settings/advanced_settings.dart b/lib/pages_desktop_specific/settings/settings_menu/advanced_settings/advanced_settings.dart index e041bcb21..08eba6c0f 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/advanced_settings/advanced_settings.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/advanced_settings/advanced_settings.dart @@ -2,11 +2,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/advanced_settings/debug_info_dialog.dart'; +import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/advanced_settings/desktop_manage_block_explorers_dialog.dart'; import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/advanced_settings/stack_privacy_dialog.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -58,7 +59,7 @@ class _AdvancedSettings extends ConsumerState { ), TextSpan( text: - "\n\nConfigurate these settings only if you know what you are doing!", + "\n\nConfigure these settings only if you know what you are doing!", style: STextStyles.desktopTextExtraExtraSmall( context), ), @@ -110,6 +111,44 @@ class _AdvancedSettings extends ConsumerState { thickness: 0.5, ), ), + Padding( + padding: const EdgeInsets.all(10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Enable coin control", + style: STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark), + textAlign: TextAlign.left, + ), + SizedBox( + height: 20, + width: 40, + child: DraggableSwitchButton( + isOn: ref.watch( + prefsChangeNotifierProvider + .select((value) => value.enableCoinControl), + ), + onValueChanged: (newValue) { + ref + .read(prefsChangeNotifierProvider) + .enableCoinControl = newValue; + }, + ), + ), + ], + ), + ), + const Padding( + padding: EdgeInsets.all(10.0), + child: Divider( + thickness: 0.5, + ), + ), /// TODO: Make a dialog popup Consumer(builder: (_, ref, __) { @@ -145,7 +184,7 @@ class _AdvancedSettings extends ConsumerState { PrimaryButton( label: "Change", buttonHeight: ButtonHeight.xs, - width: 86, + width: 101, onPressed: () async { await showDialog( context: context, @@ -169,8 +208,6 @@ class _AdvancedSettings extends ConsumerState { thickness: 0.5, ), ), - - /// TODO: Make a dialog popup Padding( padding: const EdgeInsets.all(10), child: Row( @@ -203,6 +240,44 @@ class _AdvancedSettings extends ConsumerState { ], ), ), + const Padding( + padding: EdgeInsets.all(10.0), + child: Divider( + thickness: 0.5, + ), + ), + Padding( + padding: const EdgeInsets.all(10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Block explorers", + style: STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark), + textAlign: TextAlign.left, + ), + PrimaryButton( + buttonHeight: ButtonHeight.xs, + label: "Edit", + width: 101, + onPressed: () async { + await showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return const DesktopManageBlockExplorersDialog(); + }, + ); + }, + ), + ], + ), + ), ], ), ), diff --git a/lib/pages_desktop_specific/settings/settings_menu/advanced_settings/debug_info_dialog.dart b/lib/pages_desktop_specific/settings/settings_menu/advanced_settings/debug_info_dialog.dart index 3235a5549..8ccd3abf4 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/advanced_settings/debug_info_dialog.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/advanced_settings/debug_info_dialog.dart @@ -6,12 +6,11 @@ import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/models/isar/models/log.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/providers/global/debug_service_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/enums/log_level_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; @@ -73,7 +72,6 @@ class _DebugInfoDialog extends ConsumerState { searchDebugController = TextEditingController(); searchDebugFocusNode = FocusNode(); - ref.read(debugServiceProvider).updateRecentLogs(); super.initState(); } @@ -109,7 +107,7 @@ class _DebugInfoDialog extends ConsumerState { ], ), Padding( - padding: EdgeInsets.symmetric(horizontal: 32), + padding: const EdgeInsets.symmetric(horizontal: 32), child: ClipRRect( borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, @@ -319,8 +317,8 @@ class _DebugInfoDialog extends ConsumerState { child: SecondaryButton( label: "Clear logs", onPressed: () async { - await ref.read(debugServiceProvider).deleteAllMessages(); - await ref.read(debugServiceProvider).updateRecentLogs(); + await ref.read(debugServiceProvider).deleteAllLogs(); + setState(() {}); if (mounted) { Navigator.pop(context); @@ -338,7 +336,9 @@ class _DebugInfoDialog extends ConsumerState { Expanded( child: PrimaryButton( label: "Save logs to file", - onPressed: () {}, + onPressed: () { + // TODO: save file dialog + }, ), ) ], diff --git a/lib/pages_desktop_specific/settings/settings_menu/advanced_settings/desktop_manage_block_explorers_dialog.dart b/lib/pages_desktop_specific/settings/settings_menu/advanced_settings/desktop_manage_block_explorers_dialog.dart new file mode 100644 index 000000000..9042f41d4 --- /dev/null +++ b/lib/pages_desktop_specific/settings/settings_menu/advanced_settings/desktop_manage_block_explorers_dialog.dart @@ -0,0 +1,266 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/block_explorers.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class DesktopManageBlockExplorersDialog extends ConsumerWidget { + const DesktopManageBlockExplorersDialog({ + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + bool showTestNet = ref.watch( + prefsChangeNotifierProvider.select((value) => value.showTestNetCoins), + ); + + final List coins = showTestNet + ? Coin.values + : Coin.values.sublist(0, Coin.values.length - kTestNetCoinCount); + + return DesktopDialog( + maxHeight: 850, + maxWidth: 600, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.all(32), + child: Text( + "Manage block explorers", + style: STextStyles.desktopH3(context), + textAlign: TextAlign.center, + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: ListView.separated( + itemCount: coins.length, + separatorBuilder: (_, __) => const SizedBox( + height: 12, + ), + itemBuilder: (_, index) { + final coin = coins[index]; + + return RoundedWhiteContainer( + padding: const EdgeInsets.symmetric( + vertical: 16, + horizontal: 14, + ), + borderColor: Theme.of(context) + .extension()! + .textSubtitle6, + child: Row( + children: [ + SvgPicture.file( + File( + ref.watch(coinIconProvider(coin)), + ), + width: 24, + height: 24, + ), + const SizedBox( + width: 12, + ), + Expanded( + child: Text( + "${coin.prettyName} block explorer", + style: STextStyles.desktopTextSmall(context), + ), + ), + const SizedBox( + width: 12, + ), + SvgPicture.asset( + Assets.svg.chevronRight, + width: 20, + height: 20, + ), + ], + ), + onPressed: () { + showDialog( + context: context, + useSafeArea: false, + barrierDismissible: true, + builder: (context) { + return _DesktopEditBlockExplorerDialog( + coin: coin, + ); + }, + ); + }, + ); + }, + ), + ), + ), + ], + ), + ); + } +} + +class _DesktopEditBlockExplorerDialog extends ConsumerStatefulWidget { + const _DesktopEditBlockExplorerDialog({Key? key, required this.coin}) + : super(key: key); + + final Coin coin; + + @override + ConsumerState<_DesktopEditBlockExplorerDialog> createState() => + _DesktopEditBlockExplorerDialogState(); +} + +class _DesktopEditBlockExplorerDialogState + extends ConsumerState<_DesktopEditBlockExplorerDialog> { + late final TextEditingController _textEditingController; + late final FocusNode _focusNode; + + @override + void initState() { + _textEditingController = TextEditingController( + text: + getBlockExplorerTransactionUrlFor(coin: widget.coin, txid: "[TXID]") + .toString() + .replaceAll("%5BTXID%5D", "[TXID]")); + _focusNode = FocusNode(); + super.initState(); + } + + @override + void dispose() { + _textEditingController.dispose(); + _focusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return DesktopDialog( + maxHeight: double.infinity, + maxWidth: 600, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.all(32), + child: Text( + "${widget.coin.prettyName} block explorer", + style: STextStyles.desktopH3(context), + textAlign: TextAlign.center, + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Padding( + padding: const EdgeInsets.only( + left: 32, + right: 32, + bottom: 32, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ClipRRect( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + child: TextField( + autocorrect: false, + enableSuggestions: false, + key: const Key("addCustomNodeNodeAddressFieldKey"), + controller: _textEditingController, + focusNode: _focusNode, + style: STextStyles.field(context), + ), + ), + const SizedBox( + height: 16, + ), + RoundedWhiteContainer( + borderColor: + Theme.of(context).extension()!.textSubtitle6, + child: Text( + "Edit your block explorer above. Keep in mind that" + " every block explorer has a slightly different URL scheme." + "\n\n" + "Paste in your block explorer of choice, then edit in" + " [TXID] where the transaction ID should go, and Stack" + " Wallet will auto fill the transaction ID in that place" + " of the URL.", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + ), + const SizedBox( + height: 16, + ), + Row( + children: [ + Expanded( + child: SecondaryButton( + label: "Cancel", + buttonHeight: ButtonHeight.l, + onPressed: Navigator.of(context).pop, + ), + ), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Save", + buttonHeight: ButtonHeight.l, + onPressed: () async { + _textEditingController.text = + _textEditingController.text.trim(); + await setBlockExplorerForCoin( + coin: widget.coin, + url: Uri.parse( + _textEditingController.text, + ), + ); + + if (mounted) { + Navigator.of(context).pop(); + } + }, + ), + ), + ], + ) + ], + ), + ) + ], + ), + ); + } +} diff --git a/lib/pages_desktop_specific/settings/settings_menu/advanced_settings/stack_privacy_dialog.dart b/lib/pages_desktop_specific/settings/settings_menu/advanced_settings/stack_privacy_dialog.dart index 32c20c010..d99de9ee6 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/advanced_settings/stack_privacy_dialog.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/advanced_settings/stack_privacy_dialog.dart @@ -1,16 +1,18 @@ import 'dart:async'; +import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/providers/global/price_provider.dart'; import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; @@ -96,9 +98,10 @@ class _StackPrivacyDialog extends ConsumerState { ), children: infoToggle ? [ - const TextSpan( - text: - "Exchange data preloaded for a seamless experience."), + if (Constants.enableExchange) + const TextSpan( + text: + "Exchange data preloaded for a seamless experience."), const TextSpan( text: "\n\nCoinGecko enabled: (24 hour price change shown in-app, total wallet value shown in USD or other currency)."), @@ -149,7 +152,9 @@ class _StackPrivacyDialog extends ConsumerState { Expanded( child: SecondaryButton( label: "Cancel", - onPressed: () {}, + onPressed: () { + Navigator.of(context).pop(); + }, ), ), const SizedBox( @@ -169,7 +174,9 @@ class _StackPrivacyDialog extends ConsumerState { value: isEasy) .then((_) { if (isEasy) { - unawaited(ExchangeDataLoadingService().loadAll(ref)); + unawaited( + ExchangeDataLoadingService.instance.loadAll(), + ); ref .read(priceAnd24hChangeNotifierProvider) .start(true); @@ -190,7 +197,7 @@ class _StackPrivacyDialog extends ConsumerState { } } -class PrivacyToggle extends StatefulWidget { +class PrivacyToggle extends ConsumerStatefulWidget { const PrivacyToggle({ Key? key, required this.externalCallsEnabled, @@ -201,10 +208,10 @@ class PrivacyToggle extends StatefulWidget { final void Function(bool)? onChanged; @override - State createState() => _PrivacyToggleState(); + ConsumerState createState() => _PrivacyToggleState(); } -class _PrivacyToggleState extends State { +class _PrivacyToggleState extends ConsumerState { late bool externalCallsEnabled; late final bool isDesktop; @@ -219,6 +226,11 @@ class _PrivacyToggleState extends State { @override Widget build(BuildContext context) { + final easyFile = + ref.watch(themeProvider.select((value) => value.assets.personaEasy)); + final incognitoFile = ref + .watch(themeProvider.select((value) => value.assets.personaIncognito)); + return Row( children: [ Expanded( @@ -260,11 +272,22 @@ class _PrivacyToggleState extends State { const SizedBox( height: 10, ), - SvgPicture.asset( - Assets.svg.personaEasy, - width: 120, - height: 120, - ), + // + (easyFile.endsWith(".png")) + ? Image.file( + File( + easyFile, + ), + width: 120, + height: 120, + ) + : SvgPicture.file( + File( + easyFile, + ), + width: 120, + height: 120, + ), if (isDesktop) const SizedBox( height: 12, @@ -366,11 +389,26 @@ class _PrivacyToggleState extends State { const SizedBox( height: 10, ), - SvgPicture.asset( - Assets.svg.personaIncognito, - width: 120, - height: 120, - ), + (incognitoFile.endsWith(".png")) + ? Image.file( + File( + incognitoFile, + ), + width: 120, + height: 120, + ) + : SvgPicture.file( + File( + incognitoFile, + ), + width: 120, + height: 120, + ), + // SvgPicture.asset( + // Assets.svg.personaIncognito(context), + // width: 120, + // height: 120, + // ), if (isDesktop) const SizedBox( height: 12, diff --git a/lib/pages_desktop_specific/settings/settings_menu/appearance_settings.dart b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings.dart deleted file mode 100644 index 8486e64f6..000000000 --- a/lib/pages_desktop_specific/settings/settings_menu/appearance_settings.dart +++ /dev/null @@ -1,271 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/hive/db.dart'; -import 'package:stackwallet/providers/global/prefs_provider.dart'; -import 'package:stackwallet/providers/ui/color_theme_provider.dart'; -import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/color_theme.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; -import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; -import 'package:stackwallet/widgets/rounded_white_container.dart'; - -class AppearanceOptionSettings extends ConsumerStatefulWidget { - const AppearanceOptionSettings({Key? key}) : super(key: key); - - static const String routeName = "/settingsMenuAppearance"; - - @override - ConsumerState createState() => - _AppearanceOptionSettings(); -} - -class _AppearanceOptionSettings - extends ConsumerState { - @override - Widget build(BuildContext context) { - debugPrint("BUILD: $runtimeType"); - return Column( - children: [ - Padding( - padding: const EdgeInsets.only( - right: 30, - ), - child: RoundedWhiteContainer( - radiusMultiplier: 2, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: SvgPicture.asset( - Assets.svg.circleSun, - width: 48, - height: 48, - ), - ), - Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Padding( - padding: const EdgeInsets.all(10), - child: RichText( - textAlign: TextAlign.left, - text: TextSpan( - children: [ - TextSpan( - text: "Appearances", - style: STextStyles.desktopTextSmall(context), - ), - TextSpan( - text: - "\n\nCustomize how your Stack Wallet looks according to your preferences.", - style: STextStyles.desktopTextExtraExtraSmall( - context), - ), - ], - ), - ), - ), - ], - ), - const Padding( - padding: EdgeInsets.all(10.0), - child: Divider( - thickness: 0.5, - ), - ), - Padding( - padding: const EdgeInsets.all(10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Display favorite wallets", - style: STextStyles.desktopTextExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark), - textAlign: TextAlign.left, - ), - SizedBox( - height: 20, - width: 40, - child: DraggableSwitchButton( - isOn: ref.watch( - prefsChangeNotifierProvider - .select((value) => value.showFavoriteWallets), - ), - onValueChanged: (newValue) { - ref - .read(prefsChangeNotifierProvider) - .showFavoriteWallets = newValue; - }, - ), - ) - ], - ), - ), - const Padding( - padding: EdgeInsets.all(10.0), - child: Divider( - thickness: 0.5, - ), - ), - Padding( - padding: const EdgeInsets.all(10.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - "Choose theme", - style: STextStyles.desktopTextExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark), - textAlign: TextAlign.left, - ), - ], - ), - ), - const Padding( - padding: EdgeInsets.all(10), - child: ThemeToggle(), - ), - ], - ), - ), - ), - ], - ); - } -} - -class ThemeToggle extends ConsumerStatefulWidget { - const ThemeToggle({ - Key? key, - }) : super(key: key); - - @override - ConsumerState createState() => _ThemeToggle(); -} - -class _ThemeToggle extends ConsumerState { - String assetNameFor(ThemeType type) { - switch (type) { - case ThemeType.light: - return Assets.svg.themeLight; - case ThemeType.dark: - return Assets.svg.themeDark; - case ThemeType.oceanBreeze: - return Assets.svg.themeOcean; - } - } - - @override - Widget build(BuildContext context) { - return Row( - children: [ - for (int i = 0; i < ThemeType.values.length; i++) - Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (i > 0) - const SizedBox( - width: 10, - ), - MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: () { - if (ref.read(colorThemeProvider.state).state.themeType != - ThemeType.values[i]) { - DB.instance.put( - boxName: DB.boxNameTheme, - key: "colorScheme", - value: ThemeType.values[i].name, - ); - ref.read(colorThemeProvider.state).state = - StackColors.fromStackColorTheme( - ThemeType.values[i].colorTheme); - } - }, - child: Container( - width: 200, - color: Colors.transparent, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - decoration: BoxDecoration( - border: Border.all( - width: 2.5, - color: ref - .read(colorThemeProvider.state) - .state - .themeType == - ThemeType.values[i] - ? Theme.of(context) - .extension()! - .infoItemIcons - : Theme.of(context) - .extension()! - .popupBG, - ), - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, - ), - ), - child: SvgPicture.asset( - assetNameFor(ThemeType.values[i]), - ), - ), - const SizedBox( - height: 12, - ), - Row( - children: [ - SizedBox( - width: 20, - height: 20, - child: Radio( - activeColor: Theme.of(context) - .extension()! - .radioButtonIconEnabled, - value: ThemeType.values[i], - groupValue: ref - .read(colorThemeProvider.state) - .state - .themeType, - onChanged: (_) {}, - ), - ), - const SizedBox( - width: 14, - ), - Text( - ThemeType.values[i].prettyName, - style: STextStyles.desktopTextExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, - ), - ), - ], - ), - ], - ), - ), - ), - ) - ], - ), - ], - ); - } -} diff --git a/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart new file mode 100644 index 000000000..a005028b1 --- /dev/null +++ b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart @@ -0,0 +1,424 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_manage_themes.dart'; +import 'package:stackwallet/providers/db/main_db_provider.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; +import 'package:stackwallet/themes/theme_service.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:tuple/tuple.dart'; + +class AppearanceOptionSettings extends ConsumerStatefulWidget { + const AppearanceOptionSettings({Key? key}) : super(key: key); + + static const String routeName = "/settingsMenuAppearance"; + + @override + ConsumerState createState() => + _AppearanceOptionSettings(); +} + +class _AppearanceOptionSettings + extends ConsumerState { + @override + Widget build(BuildContext context) { + debugPrint("BUILD: $runtimeType"); + return SingleChildScrollView( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only( + right: 30, + ), + child: RoundedWhiteContainer( + radiusMultiplier: 2, + child: Wrap( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: SvgPicture.asset( + Assets.svg.circleSun, + width: 48, + height: 48, + ), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Padding( + padding: const EdgeInsets.all(10), + child: RichText( + textAlign: TextAlign.left, + text: TextSpan( + children: [ + TextSpan( + text: "Appearances", + style: STextStyles.desktopTextSmall(context), + ), + TextSpan( + text: + "\n\nCustomize how your Stack Wallet looks according to your preferences.", + style: STextStyles.desktopTextExtraExtraSmall( + context), + ), + ], + ), + ), + ), + ], + ), + const Padding( + padding: EdgeInsets.all(10.0), + child: Divider( + thickness: 0.5, + ), + ), + Padding( + padding: const EdgeInsets.all(10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Display favorite wallets", + style: STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark), + textAlign: TextAlign.left, + ), + SizedBox( + height: 20, + width: 40, + child: DraggableSwitchButton( + isOn: ref.watch( + prefsChangeNotifierProvider.select( + (value) => value.showFavoriteWallets), + ), + onValueChanged: (newValue) { + ref + .read(prefsChangeNotifierProvider) + .showFavoriteWallets = newValue; + }, + ), + ) + ], + ), + ), + const Padding( + padding: EdgeInsets.all(10.0), + child: Divider( + thickness: 0.5, + ), + ), + Padding( + padding: const EdgeInsets.all(10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Choose theme", + style: STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark), + textAlign: TextAlign.left, + ), + ], + ), + ), + const Padding( + padding: EdgeInsets.all(2), + child: ThemeToggle(), + ), + ], + ), + ], + ), + ), + ), + ], + )); + } +} + +class ThemeToggle extends ConsumerStatefulWidget { + const ThemeToggle({ + Key? key, + }) : super(key: key); + + @override + ConsumerState createState() => _ThemeToggle(); +} + +class _ThemeToggle extends ConsumerState { + late final StreamSubscription _subscription; + late int _current; + + List> installedThemeIdNames = []; + + int get systemDefault => installedThemeIdNames.length; + + void setTheme(int index) { + if (index == _current) { + return; + } + + if (index == systemDefault) { + // update current index + _current = index; + + // enable system brightness setting + ref.read(prefsChangeNotifierProvider).enableSystemBrightness = true; + + // get theme + final String themeId; + switch (MediaQuery.of(context).platformBrightness) { + case Brightness.dark: + themeId = ref + .read(prefsChangeNotifierProvider.notifier) + .systemBrightnessDarkThemeId; + break; + case Brightness.light: + themeId = ref + .read(prefsChangeNotifierProvider.notifier) + .systemBrightnessLightThemeId; + break; + } + + // apply theme + ref.read(themeProvider.notifier).state = + ref.read(pThemeService).getTheme(themeId: themeId)!; + + // Assets.precache(context); + } else { + if (_current == systemDefault) { + // disable system brightness setting + ref.read(prefsChangeNotifierProvider).enableSystemBrightness = false; + } + + // update current index + _current = index; + + // get theme + final themeId = installedThemeIdNames[index].item1; + + // save theme setting + ref.read(prefsChangeNotifierProvider.notifier).themeId = themeId; + + // apply theme + ref.read(themeProvider.notifier).state = + ref.read(pThemeService).getTheme(themeId: themeId)!; + + // Assets.precache(context); + } + } + + void _updateInstalledList() { + installedThemeIdNames = ref + .read(pThemeService) + .installedThemes + .map((e) => Tuple3(e.themeId, e.name, e.assets.themeSelector)) + .toList(); + + if (ref.read(prefsChangeNotifierProvider).enableSystemBrightness) { + _current = installedThemeIdNames.length; + } else { + final themeId = ref.read(prefsChangeNotifierProvider).themeId; + + for (int i = 0; i < installedThemeIdNames.length; i++) { + if (installedThemeIdNames[i].item1 == themeId) { + _current = i; + break; + } + } + } + } + + void _manageThemesPressed() { + showDialog( + context: context, + builder: (_) => const DesktopManageThemesDialog(), + ); + } + + @override + void initState() { + _updateInstalledList(); + + _subscription = + ref.read(mainDBProvider).isar.stackThemes.watchLazy().listen((_) { + if (mounted) { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + _updateInstalledList(); + }); + }); + } + }); + + super.initState(); + } + + @override + void dispose() { + _subscription.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Wrap( + spacing: 16, + runSpacing: 16, + children: [ + for (int i = 0; i < installedThemeIdNames.length; i++) + Padding( + key: Key("installedTheme_${installedThemeIdNames[i].item1}"), + padding: const EdgeInsets.all(8.0), + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () { + if (_current != i) { + setTheme(i); + } + }, + child: Container( + width: 200, + color: Colors.transparent, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + decoration: BoxDecoration( + border: Border.all( + width: 2.5, + color: _current == i + ? Theme.of(context) + .extension()! + .infoItemIcons + : Theme.of(context) + .extension()! + .popupBG, + ), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: SvgPicture.file( + File( + installedThemeIdNames[i].item3, + ), + height: 160, + width: 200, + ), + ), + const SizedBox( + height: 12, + ), + Row( + children: [ + SizedBox( + width: 20, + height: 20, + child: Radio( + activeColor: Theme.of(context) + .extension()! + .radioButtonIconEnabled, + value: i, + groupValue: _current, + onChanged: (newValue) { + if (newValue is int) { + setTheme(newValue); + } + }, + ), + ), + const SizedBox( + width: 14, + ), + Text( + installedThemeIdNames[i].item2, + style: STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + ], + ), + ], + ), + ), + ), + ), + ), + Container( + decoration: BoxDecoration( + border: Border.all( + width: 2.5, + color: Theme.of(context).extension()!.popupBG, + ), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Semantics( + label: "Manage themes", + button: true, + excludeSemantics: true, + child: RawMaterialButton( + onPressed: _manageThemesPressed, + elevation: 0, + focusElevation: 0, + hoverElevation: 0, + highlightElevation: 0, + fillColor: Theme.of(context) + .extension()! + .textFieldActiveBG, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + child: Container( + color: Colors.transparent, + height: 160, + width: 200, + child: Center( + child: SvgPicture.asset( + Assets.svg.circlePlusFilled, + color: Theme.of(context) + .extension()! + .textSubtitle2, + width: 20, + height: 20, + ), + ), + ), + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_install_theme.dart b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_install_theme.dart new file mode 100644 index 000000000..fcfb87471 --- /dev/null +++ b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_install_theme.dart @@ -0,0 +1,327 @@ +import 'package:desktop_drop/desktop_drop.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:share_plus/share_plus.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_service.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/show_loading.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/desktop/outline_blue_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; + +class DesktopInstallTheme extends ConsumerStatefulWidget { + const DesktopInstallTheme({Key? key}) : super(key: key); + + @override + ConsumerState createState() => + _DesktopInstallThemeState(); +} + +class _DesktopInstallThemeState extends ConsumerState { + final _boxKey = GlobalKey(debugLabel: "selectThemeFileBoxKey"); + + XFile? _selectedFile; + bool? _installedState; + Size? _size; + bool _dragging = false; + + Future _install() async { + try { + final timedFuture = Future.delayed(const Duration(seconds: 2)); + final installFuture = _selectedFile!.readAsBytes().then( + (fileBytes) => ref.read(pThemeService).install( + themeArchiveData: fileBytes, + ), + ); + + // wait for at least 2 seconds to prevent annoying screen flashing + await Future.wait([ + installFuture, + timedFuture, + ]); + return true; + } catch (e, s) { + Logging.instance.log( + "Failed to install theme: $e\n$s", + level: LogLevel.Warning, + ); + return false; + } + } + + Future _chooseFile() async { + try { + final result = await FilePicker.platform.pickFiles( + dialogTitle: "Choose theme file", + type: FileType.custom, + allowedExtensions: ["zip"], + lockParentWindow: true, // windows only + ); + + if (result != null && mounted) { + if (result.paths.isNotEmpty && result.paths.first != null) { + setState(() { + _selectedFile = XFile(result.paths.first!); + }); + } + } + } catch (e, s) { + Logging.instance.log("$e\n$s", level: LogLevel.Error); + } + } + + void setBoxSize() { + if (_size == null) { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + setState(() { + _size = _boxKey.currentContext?.size; + }); + }); + } + } + + @override + void initState() { + setBoxSize(); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + children: [ + Text( + "Install theme file", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + ], + ), + const SizedBox( + height: 12, + ), + DropTarget( + onDragDone: (detail) { + setState(() { + if (detail.files.isNotEmpty) { + _selectedFile = detail.files.first; + } + }); + }, + onDragEntered: (detail) { + setState(() { + _dragging = true; + }); + }, + onDragExited: (detail) { + setState(() { + _dragging = false; + }); + }, + child: RoundedContainer( + key: _boxKey, + height: _size?.height, + color: _dragging + ? Theme.of(context).extension()!.textSubtitle6 + : Theme.of(context).extension()!.popupBG, + borderColor: + Theme.of(context).extension()!.textSubtitle6, + child: _selectedFile == null + ? Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: 12, + ), + SvgPicture.asset( + Assets.svg.fileUpload, + color: Theme.of(context) + .extension()! + .textSubtitle2, + width: 24, + height: 24, + ), + const SizedBox( + height: 12, + ), + Text( + "Drag and drop your file here", + style: STextStyles.fieldLabel(context), + ), + const SizedBox( + height: 12, + ), + OutlineBlueButton( + label: "Browse", + buttonHeight: ButtonHeight.s, + width: 140, + onPressed: _chooseFile, + ), + const SizedBox( + height: 12, + ), + ], + ) + : Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + RoundedContainer( + padding: EdgeInsets.zero, + color: Theme.of(context) + .extension()! + .textFieldActiveBG, + width: 300, + child: Row( + children: [ + const SizedBox( + width: 10, + ), + SvgPicture.asset( + Assets.svg.file, + color: Theme.of(context) + .extension()! + .textDark, + width: 16, + height: 16, + ), + const SizedBox( + width: 8, + ), + Expanded( + child: Text( + _selectedFile!.name, + style: STextStyles.w500_14(context), + ), + ), + IconButton( + onPressed: () { + setState(() { + _selectedFile = null; + }); + }, + icon: SvgPicture.asset( + Assets.svg.circleX, + color: Theme.of(context) + .extension()! + .textSubtitle2, + width: 16, + height: 16, + ), + ), + ], + ), + ) + ], + ), + ), + ), + if (_selectedFile != null) + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(top: 16, bottom: 20), + child: PrimaryButton( + label: "Install", + buttonHeight: ButtonHeight.s, + width: 140, + enabled: _installedState == null, + onPressed: () async { + final result = await showLoading( + whileFuture: _install(), + context: context, + message: "Installing ${_selectedFile!.name}...", + ); + if (mounted) { + setState(() { + _installedState = result; + }); + + await Future.delayed( + const Duration(milliseconds: 2000), + ).then((_) { + if (mounted) { + setState(() { + _selectedFile = null; + _installedState = null; + }); + } + }); + } + }, + ), + ), + ], + ), + if (_installedState == true) + RoundedContainer( + color: + Theme.of(context).extension()!.snackBarBackSuccess, + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.circleX, + color: Theme.of(context) + .extension()! + .snackBarTextSuccess, + width: 16, + height: 16, + ), + const SizedBox( + width: 10, + ), + Text( + "${_selectedFile?.name} theme installed", + style: + STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .snackBarTextSuccess, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + if (_installedState == false) + RoundedContainer( + color: + Theme.of(context).extension()!.snackBarBackError, + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.circleX, + color: Theme.of(context) + .extension()! + .snackBarTextError, + width: 16, + height: 16, + ), + const SizedBox( + width: 10, + ), + Text( + "Failed to install ${_selectedFile?.name} theme", + style: + STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .snackBarTextError, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_manage_themes.dart b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_manage_themes.dart new file mode 100644 index 000000000..277bc0c9e --- /dev/null +++ b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_manage_themes.dart @@ -0,0 +1,112 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_install_theme.dart'; +import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_themes_gallery.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/toggle.dart'; + +class DesktopManageThemesDialog extends ConsumerStatefulWidget { + const DesktopManageThemesDialog({Key? key}) : super(key: key); + + @override + ConsumerState createState() => + _DesktopManageThemesDialogState(); +} + +class _DesktopManageThemesDialogState + extends ConsumerState { + static const width = 580.0; + bool _isInstallFromFile = false; + + @override + Widget build(BuildContext context) { + return DesktopDialog( + maxWidth: width, + maxHeight: 708, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Add more themes", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: SizedBox( + height: 56, + child: Toggle( + isOn: _isInstallFromFile, + onValueChanged: (value) { + if (value != _isInstallFromFile) { + setState(() { + _isInstallFromFile = value; + }); + } + }, + onColor: Theme.of(context) + .extension()! + .rateTypeToggleDesktopColorOn, + offColor: Theme.of(context) + .extension()! + .rateTypeToggleDesktopColorOff, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onText: "Theme gallery", + offText: "Install file", + ), + ), + ), + const SizedBox( + height: 16, + ), + Expanded( + child: LayoutBuilder( + builder: (context, constraints) { + return AnimatedCrossFade( + crossFadeState: _isInstallFromFile + ? CrossFadeState.showSecond + : CrossFadeState.showFirst, + duration: const Duration( + milliseconds: 300, + ), + firstChild: SizedBox( + height: constraints.maxHeight, + child: const DesktopThemeGallery( + dialogWidth: width, + ), + ), + secondChild: SizedBox( + height: constraints.maxHeight, + child: const Padding( + padding: EdgeInsets.symmetric(horizontal: 32), + child: DesktopInstallTheme(), + ), + ), + ); + }, + ), + ), + const SizedBox( + height: 32, + ), + ], + ), + ); + } +} diff --git a/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_themes_gallery.dart b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_themes_gallery.dart new file mode 100644 index 000000000..3e0bf3ec3 --- /dev/null +++ b/lib/pages_desktop_specific/settings/settings_menu/appearance_settings/sub_widgets/desktop_themes_gallery.dart @@ -0,0 +1,153 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/manage_themes.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/sub_widgets/stack_theme_card.dart'; +import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_service.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/loading_indicator.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class DesktopThemeGallery extends ConsumerStatefulWidget { + const DesktopThemeGallery({ + Key? key, + required this.dialogWidth, + }) : super(key: key); + + final double dialogWidth; + + @override + ConsumerState createState() => + _DesktopThemeGalleryState(); +} + +class _DesktopThemeGalleryState extends ConsumerState { + late bool _showThemes; + Future> Function() future = () async => []; + + @override + void initState() { + _showThemes = ref.read(prefsChangeNotifierProvider).externalCalls; + if (_showThemes) { + future = ref.read(pThemeService).fetchThemes; + } + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Row( + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Theme Gallery", + style: STextStyles.desktopTextExtraExtraSmall(context), + ), + ), + ], + ), + const SizedBox( + height: 12, + ), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: SingleChildScrollView( + child: _showThemes + ? Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FutureBuilder( + future: future(), + builder: ( + context, + AsyncSnapshot> snapshot, + ) { + if (snapshot.connectionState == + ConnectionState.done && + snapshot.hasData) { + return Wrap( + spacing: 16, + runSpacing: 16, + children: snapshot.data! + .map( + (e) => SizedBox( + key: Key( + "_DesktopThemeGalleryState_card_${e.id}_key"), + width: + (widget.dialogWidth - 64 - 32) / 3, + child: StackThemeCard( + data: e, + ), + ), + ) + .toList(), + ); + } else { + return const Center( + child: LoadingIndicator( + width: 200, + ), + ); + } + }, + ), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RoundedWhiteContainer( + borderColor: Theme.of(context) + .extension()! + .textSubtitle6, + child: Text( + "You are using Incognito Mode." + " Please press the button below to load " + "available themes from our server or install a " + "theme file manually from your computer.", + style: + STextStyles.desktopTextExtraExtraSmall(context), + ), + ), + const SizedBox( + height: 16, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + PrimaryButton( + label: "Load themes", + width: 140, + buttonHeight: ButtonHeight.s, + onPressed: () { + setState(() { + _showThemes = true; + future = ref.read(pThemeService).fetchThemes; + }); + }, + ), + ], + ), + const SizedBox( + height: 20, + ), + IncognitoInstalledThemes( + cardWidth: (widget.dialogWidth - 64 - 32) / 3, + ), + ], + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/pages_desktop_specific/settings/settings_menu/backup_and_restore/backup_and_restore_settings.dart b/lib/pages_desktop_specific/settings/settings_menu/backup_and_restore/backup_and_restore_settings.dart index de9a68418..15198449e 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/backup_and_restore/backup_and_restore_settings.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/backup_and_restore/backup_and_restore_settings.dart @@ -10,11 +10,11 @@ import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/backup import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/backup_and_restore/enable_backup_dialog.dart'; import 'package:stackwallet/providers/global/auto_swb_service_provider.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; @@ -159,7 +159,7 @@ class _BackupRestoreSettings extends ConsumerState { leftButton: TextButton( style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), child: Text( "Back", style: STextStyles.button(context).copyWith( @@ -175,7 +175,7 @@ class _BackupRestoreSettings extends ConsumerState { rightButton: TextButton( style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), child: Text( "Disable", style: STextStyles.button(context), @@ -450,7 +450,7 @@ class _BackupRestoreSettings extends ConsumerState { STextStyles.itemSubtitle( context), ), - BlueTextButton( + CustomTextButton( text: "Back up now", onTap: () { ref diff --git a/lib/pages_desktop_specific/settings/settings_menu/backup_and_restore/create_auto_backup.dart b/lib/pages_desktop_specific/settings/settings_menu/backup_and_restore/create_auto_backup.dart index df80da732..14e35d78c 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/backup_and_restore/create_auto_backup.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/backup_and_restore/create_auto_backup.dart @@ -13,15 +13,14 @@ import 'package:stackwallet/pages/settings_views/global_settings_view/stack_back import 'package:stackwallet/pages/settings_views/global_settings_view/stack_backup_views/helpers/swb_file_system.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; import 'package:stackwallet/providers/global/secure_store_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; @@ -722,7 +721,7 @@ class _CreateAutoBackup extends ConsumerState { fileToSave, adkString, jsonEncode(backup), - adkVersion: adkVersion, + adkVersion, ); // this future should already be complete unless there was an error encrypting diff --git a/lib/pages_desktop_specific/settings/settings_menu/backup_and_restore/enable_backup_dialog.dart b/lib/pages_desktop_specific/settings/settings_menu/backup_and_restore/enable_backup_dialog.dart index aebbdf592..e6056e83d 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/backup_and_restore/enable_backup_dialog.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/backup_and_restore/enable_backup_dialog.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/backup_and_restore/create_auto_backup.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; diff --git a/lib/pages_desktop_specific/settings/settings_menu/desktop_about_view.dart b/lib/pages_desktop_specific/settings/settings_menu/desktop_about_view.dart index 99cfe1cc6..89d66306b 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/desktop_about_view.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/desktop_about_view.dart @@ -8,9 +8,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:http/http.dart'; // import 'package:lelantus/git_versions.dart' as FIRO_VERSIONS; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; @@ -678,7 +678,7 @@ class DesktopAboutView extends ConsumerWidget { const SizedBox( height: 2, ), - BlueTextButton( + CustomTextButton( text: "https://stackwallet.com", onTap: () { diff --git a/lib/pages_desktop_specific/settings/settings_menu/desktop_support_view.dart b/lib/pages_desktop_specific/settings/settings_menu/desktop_support_view.dart index ce3e3f3cc..1971cfcd2 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/desktop_support_view.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/desktop_support_view.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/support_view.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; diff --git a/lib/pages_desktop_specific/settings/settings_menu/language_settings/language_dialog.dart b/lib/pages_desktop_specific/settings/settings_menu/language_settings/language_dialog.dart index d07c9729f..4d8510353 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/language_settings/language_dialog.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/language_settings/language_dialog.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/languages_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; @@ -9,13 +11,10 @@ import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/stack_text_field.dart'; - -import '../../../../utilities/assets.dart'; -import '../../../../utilities/theme/stack_colors.dart'; -import '../../../../widgets/icon_widgets/x_icon.dart'; -import '../../../../widgets/rounded_container.dart'; -import '../../../../widgets/textfield_icon_button.dart'; +import 'package:stackwallet/widgets/textfield_icon_button.dart'; class LanguageDialog extends ConsumerStatefulWidget { const LanguageDialog({Key? key}) : super(key: key); diff --git a/lib/pages_desktop_specific/settings/settings_menu/nodes_settings.dart b/lib/pages_desktop_specific/settings/settings_menu/nodes_settings.dart index a7e95d33a..7c39b6c91 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/nodes_settings.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/nodes_settings.dart @@ -1,14 +1,17 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/coin_nodes_view.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/route_generator.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -46,10 +49,16 @@ class _NodesSettings extends ConsumerState { .toList(); } + final bool isDesktop = Util.isDesktop; + @override void initState() { _coins = _coins.toList(); _coins.remove(Coin.firoTestNet); + if (Platform.isWindows) { + _coins.remove(Coin.monero); + _coins.remove(Coin.wownero); + } searchNodeController = TextEditingController(); searchNodeFocusNode = FocusNode(); @@ -128,8 +137,8 @@ class _NodesSettings extends ConsumerState { Constants.size.circularBorderRadius, ), child: TextField( - autocorrect: Util.isDesktop ? false : true, - enableSuggestions: Util.isDesktop ? false : true, + autocorrect: !isDesktop, + enableSuggestions: !isDesktop, controller: searchNodeController, focusNode: searchNodeFocusNode, onChanged: (newString) { @@ -241,8 +250,10 @@ class _NodesSettings extends ConsumerState { children: [ Row( children: [ - SvgPicture.asset( - Assets.svg.iconFor(coin: coin), + SvgPicture.file( + File( + ref.watch(coinIconProvider(coin)), + ), width: 24, height: 24, ), diff --git a/lib/pages_desktop_specific/settings/settings_menu/security_settings.dart b/lib/pages_desktop_specific/settings/settings_menu/security_settings.dart index ff7537126..9ac684156 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/security_settings.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/security_settings.dart @@ -5,11 +5,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/providers/desktop/storage_crypto_handler_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/progress_bar.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; diff --git a/lib/pages_desktop_specific/settings/settings_menu/syncing_preferences_settings.dart b/lib/pages_desktop_specific/settings/settings_menu/syncing_preferences_settings.dart index 720d77b8b..f8d1b8850 100644 --- a/lib/pages_desktop_specific/settings/settings_menu/syncing_preferences_settings.dart +++ b/lib/pages_desktop_specific/settings/settings_menu/syncing_preferences_settings.dart @@ -1,13 +1,12 @@ import 'package:flutter/material.dart'; -import 'package:flutter/src/widgets/framework.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_options_view.dart'; import 'package:stackwallet/providers/global/prefs_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/enums/sync_type_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/primary_button.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; diff --git a/lib/pages_desktop_specific/settings/settings_menu_item.dart b/lib/pages_desktop_specific/settings/settings_menu_item.dart index 28ddffb50..95845d282 100644 --- a/lib/pages_desktop_specific/settings/settings_menu_item.dart +++ b/lib/pages_desktop_specific/settings/settings_menu_item.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; class SettingsMenuItem extends StatelessWidget { const SettingsMenuItem({ @@ -25,10 +25,10 @@ class SettingsMenuItem extends StatelessWidget { style: value == group ? Theme.of(context) .extension()! - .getDesktopSettingsButtonColor(context) + .getDesktopSettingsButtonStyle(context) : Theme.of(context) .extension()! - .getDesktopSettingsButtonColor(context), + .getDesktopSettingsButtonStyle(context), onPressed: () { onChanged(value); }, diff --git a/lib/providers/buy/buy_form_state_provider.dart b/lib/providers/buy/buy_form_state_provider.dart new file mode 100644 index 000000000..2a0dc719c --- /dev/null +++ b/lib/providers/buy/buy_form_state_provider.dart @@ -0,0 +1,6 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/buy/buy_form_state.dart'; + +final buyFormStateProvider = ChangeNotifierProvider( + (ref) => BuyFormState(), +); diff --git a/lib/providers/buy/simplex_initial_load_status.dart b/lib/providers/buy/simplex_initial_load_status.dart new file mode 100644 index 000000000..9571f5b64 --- /dev/null +++ b/lib/providers/buy/simplex_initial_load_status.dart @@ -0,0 +1,11 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +enum SimplexLoadStatus { + waiting, + loading, + success, + failed, +} + +final simplexLoadStatusStateProvider = + StateProvider((ref) => SimplexLoadStatus.waiting); diff --git a/lib/providers/buy/simplex_provider.dart b/lib/providers/buy/simplex_provider.dart new file mode 100644 index 000000000..d15d335a5 --- /dev/null +++ b/lib/providers/buy/simplex_provider.dart @@ -0,0 +1,6 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/buy/simplex/simplex.dart'; + +final simplexProvider = Provider( + (ref) => Simplex(), +); diff --git a/lib/providers/db/main_db_provider.dart b/lib/providers/db/main_db_provider.dart new file mode 100644 index 000000000..2f3b6479c --- /dev/null +++ b/lib/providers/db/main_db_provider.dart @@ -0,0 +1,4 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; + +final mainDBProvider = Provider((ref) => MainDB.instance); diff --git a/lib/providers/desktop/current_desktop_menu_item.dart b/lib/providers/desktop/current_desktop_menu_item.dart index 85ac7c46b..a9d3b17e1 100644 --- a/lib/providers/desktop/current_desktop_menu_item.dart +++ b/lib/providers/desktop/current_desktop_menu_item.dart @@ -3,3 +3,6 @@ import 'package:stackwallet/pages_desktop_specific/desktop_menu.dart'; final currentDesktopMenuItemProvider = StateProvider((ref) => DesktopMenuItemId.myStack); + +final prevDesktopMenuItemProvider = + StateProvider((ref) => DesktopMenuItemId.myStack); diff --git a/lib/providers/exchange/available_changenow_currencies_provider.dart b/lib/providers/exchange/available_changenow_currencies_provider.dart deleted file mode 100644 index 04bf04007..000000000 --- a/lib/providers/exchange/available_changenow_currencies_provider.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/models/exchange/change_now/cn_available_currencies.dart'; - -final availableChangeNowCurrenciesProvider = Provider( - (ref) => CNAvailableCurrencies(), -); diff --git a/lib/providers/exchange/available_simpleswap_currencies_provider.dart b/lib/providers/exchange/available_simpleswap_currencies_provider.dart deleted file mode 100644 index ae1bd5fa0..000000000 --- a/lib/providers/exchange/available_simpleswap_currencies_provider.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/models/exchange/simpleswap/sp_available_currencies.dart'; - -final availableSimpleswapCurrenciesProvider = Provider( - (ref) => SPAvailableCurrencies(), -); diff --git a/lib/providers/exchange/current_exchange_name_state_provider.dart b/lib/providers/exchange/current_exchange_name_state_provider.dart deleted file mode 100644 index 67b630ad7..000000000 --- a/lib/providers/exchange/current_exchange_name_state_provider.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; - -final currentExchangeNameStateProvider = StateProvider( - (ref) => ChangeNowExchange.exchangeName, -); diff --git a/lib/providers/exchange/exchange_form_state_provider.dart b/lib/providers/exchange/exchange_form_state_provider.dart index 568660d48..afac49de8 100644 --- a/lib/providers/exchange/exchange_form_state_provider.dart +++ b/lib/providers/exchange/exchange_form_state_provider.dart @@ -1,6 +1,91 @@ +import 'package:decimal/decimal.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/models/exchange/exchange_form_state.dart'; +import 'package:stackwallet/models/exchange/active_pair.dart'; +import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; +import 'package:stackwallet/models/exchange/response_objects/range.dart'; +import 'package:stackwallet/services/exchange/exchange.dart'; +import 'package:stackwallet/services/exchange/exchange_response.dart'; +import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart'; +import 'package:tuple/tuple.dart'; -final exchangeFormStateProvider = ChangeNotifierProvider( - (ref) => ExchangeFormState(), +final efEstimatesListProvider = StateProvider.family< + Tuple2>, Range?>?, + String>((ref, exchangeName) => null); + +final efRateTypeProvider = + StateProvider((ref) => ExchangeRateType.estimated); + +final efExchangeProvider = + StateProvider((ref) => Exchange.defaultExchange); +final efExchangeProviderNameProvider = + StateProvider((ref) => Exchange.defaultExchange.name); + +final currentCombinedExchangeIdProvider = Provider((ref) { + return "${ref.watch(efExchangeProvider).name}" + " (${ref.watch(efExchangeProviderNameProvider)})"; +}); + +final efSendAmountProvider = StateProvider((ref) => null); +final efReceiveAmountProvider = StateProvider((ref) => null); + +final efSendAmountStringProvider = StateProvider((ref) { + final refreshing = ref.watch(efRefreshingProvider); + final reversed = ref.watch(efReversedProvider); + if (refreshing && reversed) { + return "-"; + } else { + return ref.watch(efSendAmountProvider)?.toString() ?? ""; + } +}); +final efReceiveAmountStringProvider = StateProvider((ref) { + final refreshing = ref.watch(efRefreshingProvider); + final reversed = ref.watch(efReversedProvider); + + if (refreshing && reversed == false) { + return "-"; + } else { + return ref.watch(efReceiveAmountProvider)?.toString() ?? ""; + } +}); + +final efReversedProvider = StateProvider((ref) => false); + +final efCurrencyPairProvider = ChangeNotifierProvider( + (ref) => ActivePair(), ); + +final efEstimateProvider = StateProvider((ref) { + final exchange = ref.watch(efExchangeProvider); + final provider = ref.watch(efExchangeProviderNameProvider); + final reversed = ref.watch(efReversedProvider); + final fixedRate = ref.watch(efRateTypeProvider) == ExchangeRateType.fixed; + + final matches = ref + .watch(efEstimatesListProvider(exchange.name)) + ?.item1 + .value + ?.where((e) { + return e.exchangeProvider == provider && + e.fixedRate == fixedRate && + e.reversed == reversed; + }); + + Estimate? result; + + if (matches != null && matches.isNotEmpty) { + result = matches.first; + } else { + result = null; + } + + return result; +}); + +final efCanExchangeProvider = StateProvider((ref) { + final Estimate? estimate = ref.watch(efEstimateProvider); + final refreshing = ref.watch(efRefreshingProvider); + + return !refreshing && estimate != null; +}); + +final efRefreshingProvider = StateProvider((ref) => false); diff --git a/lib/providers/exchange/exchange_provider.dart b/lib/providers/exchange/exchange_provider.dart deleted file mode 100644 index e65847a18..000000000 --- a/lib/providers/exchange/exchange_provider.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/providers/exchange/current_exchange_name_state_provider.dart'; -import 'package:stackwallet/services/exchange/exchange.dart'; - -final exchangeProvider = Provider( - (ref) => Exchange.fromName( - ref.watch(currentExchangeNameStateProvider.state).state, - ), -); diff --git a/lib/providers/global/paynym_api_provider.dart b/lib/providers/global/paynym_api_provider.dart new file mode 100644 index 000000000..482b4c2ba --- /dev/null +++ b/lib/providers/global/paynym_api_provider.dart @@ -0,0 +1,4 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/utilities/paynym_is_api.dart'; + +final paynymAPIProvider = Provider((_) => PaynymIsApi()); diff --git a/lib/providers/providers.dart b/lib/providers/providers.dart index 5cef38830..2c027b169 100644 --- a/lib/providers/providers.dart +++ b/lib/providers/providers.dart @@ -1,10 +1,9 @@ -export './exchange/available_changenow_currencies_provider.dart'; -export './exchange/available_simpleswap_currencies_provider.dart'; +export './buy/buy_form_state_provider.dart'; +export './buy/simplex_initial_load_status.dart'; +export './buy/simplex_provider.dart'; export './exchange/changenow_initial_load_status.dart'; -export './exchange/current_exchange_name_state_provider.dart'; export './exchange/exchange_flow_is_active_state_provider.dart'; export './exchange/exchange_form_state_provider.dart'; -export './exchange/exchange_provider.dart'; export './exchange/exchange_send_from_wallet_id_provider.dart'; export './exchange/trade_note_service_provider.dart'; export './exchange/trade_sent_from_stack_lookup_provider.dart'; diff --git a/lib/providers/ui/add_wallet_selected_coin_provider.dart b/lib/providers/ui/add_wallet_selected_coin_provider.dart index 4e2f77cdd..6acf51db8 100644 --- a/lib/providers/ui/add_wallet_selected_coin_provider.dart +++ b/lib/providers/ui/add_wallet_selected_coin_provider.dart @@ -1,14 +1,5 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/models/add_wallet_list_entity/add_wallet_list_entity.dart'; -int _count = 0; - -final addWalletSelectedCoinStateProvider = - StateProvider.autoDispose((_) { - if (kDebugMode) { - _count++; - } - - return null; -}); +final addWalletSelectedEntityStateProvider = + StateProvider.autoDispose((_) => null); diff --git a/lib/providers/ui/color_theme_provider.dart b/lib/providers/ui/color_theme_provider.dart deleted file mode 100644 index 9843f0f9c..000000000 --- a/lib/providers/ui/color_theme_provider.dart +++ /dev/null @@ -1,6 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; - -final colorThemeProvider = StateProvider( - (ref) => StackColors.fromStackColorTheme(LightColors())); diff --git a/lib/providers/ui/preview_tx_button_state_provider.dart b/lib/providers/ui/preview_tx_button_state_provider.dart index 740356a20..68d4fc531 100644 --- a/lib/providers/ui/preview_tx_button_state_provider.dart +++ b/lib/providers/ui/preview_tx_button_state_provider.dart @@ -1,12 +1,9 @@ -import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -int _count = 0; - final previewTxButtonStateProvider = StateProvider.autoDispose((_) { - if (kDebugMode) { - _count++; - } - + return false; +}); + +final previewTokenTxButtonStateProvider = StateProvider.autoDispose((_) { return false; }); diff --git a/lib/providers/ui/selected_paynym_details_item_Provider.dart b/lib/providers/ui/selected_paynym_details_item_Provider.dart new file mode 100644 index 000000000..153145710 --- /dev/null +++ b/lib/providers/ui/selected_paynym_details_item_Provider.dart @@ -0,0 +1,5 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/paynym/paynym_account_lite.dart'; + +final selectedPaynymDetailsItemProvider = + StateProvider.autoDispose((_) => null); diff --git a/lib/providers/wallet/my_paynym_account_state_provider.dart b/lib/providers/wallet/my_paynym_account_state_provider.dart new file mode 100644 index 000000000..1919e2ace --- /dev/null +++ b/lib/providers/wallet/my_paynym_account_state_provider.dart @@ -0,0 +1,5 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/paynym/paynym_account.dart'; + +final myPaynymAccountStateProvider = + StateProvider((ref) => null); diff --git a/lib/providers/wallet/wallet_balance_toggle_state_provider.dart b/lib/providers/wallet/wallet_balance_toggle_state_provider.dart index 35d85a6cf..477f4fb03 100644 --- a/lib/providers/wallet/wallet_balance_toggle_state_provider.dart +++ b/lib/providers/wallet/wallet_balance_toggle_state_provider.dart @@ -4,3 +4,7 @@ import 'package:stackwallet/utilities/enums/wallet_balance_toggle_state.dart'; final walletBalanceToggleStateProvider = StateProvider.autoDispose( (ref) => WalletBalanceToggleState.full); + +final walletPrivateBalanceToggleStateProvider = + StateProvider.autoDispose( + (ref) => WalletBalanceToggleState.full); diff --git a/lib/providers/wallet_provider.dart b/lib/providers/wallet_provider.dart new file mode 100644 index 000000000..15c7952b6 --- /dev/null +++ b/lib/providers/wallet_provider.dart @@ -0,0 +1,60 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/providers/db/main_db_provider.dart'; +import 'package:stackwallet/providers/global/secure_store_provider.dart'; +import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart'; +import 'package:stackwallet/services/coins/manager.dart'; +import 'package:stackwallet/services/ethereum/ethereum_token_service.dart'; +import 'package:stackwallet/services/transaction_notification_tracker.dart'; +import 'package:stackwallet/utilities/logger.dart'; + +class ContractWalletId implements Equatable { + final String walletId; + final String tokenContractAddress; + + ContractWalletId({ + required this.walletId, + required this.tokenContractAddress, + }); + + @override + List get props => [walletId, tokenContractAddress]; + + @override + bool? get stringify => true; +} + +/// provide the wallet for a given wallet id +final walletProvider = + ChangeNotifierProvider.family((ref, arg) => null); + +/// provide the token wallet given a contract address and eth wallet id +final tokenWalletProvider = + ChangeNotifierProvider.family( + (ref, arg) { + final ethWallet = + ref.watch(walletProvider(arg.walletId).select((value) => value?.wallet)) + as EthereumWallet?; + final contract = + ref.read(mainDBProvider).getEthContractSync(arg.tokenContractAddress); + + if (ethWallet == null || contract == null) { + Logging.instance.log( + "Attempted to access a token wallet with walletId=${arg.walletId} where" + " contractAddress=${arg.tokenContractAddress}", + level: LogLevel.Warning, + ); + return null; + } + + final secureStore = ref.watch(secureStoreProvider); + + return EthTokenWallet( + token: contract, + ethWallet: ethWallet, + secureStore: secureStore, + tracker: TransactionNotificationTracker( + walletId: arg.walletId, + ), + ); +}); diff --git a/lib/route_generator.dart b/lib/route_generator.dart index ed676c437..2791974e6 100644 --- a/lib/route_generator.dart +++ b/lib/route_generator.dart @@ -1,12 +1,17 @@ -import 'package:decimal/decimal.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/models/contact_address_entry.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/models/add_wallet_list_entity/add_wallet_list_entity.dart'; +import 'package:stackwallet/models/add_wallet_list_entity/sub_classes/eth_token_entity.dart'; +import 'package:stackwallet/models/buy/response_objects/quote.dart'; import 'package:stackwallet/models/exchange/incomplete_exchange.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; -import 'package:stackwallet/models/paymint/transactions_model.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/models/paynym/paynym_account_lite.dart'; import 'package:stackwallet/models/send_view_auto_fill_data.dart'; +import 'package:stackwallet/pages/add_wallet_views/add_token_view/add_custom_token_view.dart'; +import 'package:stackwallet/pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart'; import 'package:stackwallet/pages/add_wallet_views/add_wallet_view/add_wallet_view.dart'; import 'package:stackwallet/pages/add_wallet_views/create_or_restore_wallet_view/create_or_restore_wallet_view.dart'; import 'package:stackwallet/pages/add_wallet_views/name_your_wallet_view/name_your_wallet_view.dart'; @@ -14,6 +19,7 @@ import 'package:stackwallet/pages/add_wallet_views/new_wallet_recovery_phrase_vi import 'package:stackwallet/pages/add_wallet_views/new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_warning_view.dart'; import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/restore_options_view/restore_options_view.dart'; import 'package:stackwallet/pages/add_wallet_views/restore_wallet_view/restore_wallet_view.dart'; +import 'package:stackwallet/pages/add_wallet_views/select_wallet_for_token_view.dart'; import 'package:stackwallet/pages/add_wallet_views/verify_recovery_phrase_view/verify_recovery_phrase_view.dart'; import 'package:stackwallet/pages/address_book_views/address_book_view.dart'; import 'package:stackwallet/pages/address_book_views/subviews/add_address_book_entry_view.dart'; @@ -22,9 +28,13 @@ import 'package:stackwallet/pages/address_book_views/subviews/address_book_filte import 'package:stackwallet/pages/address_book_views/subviews/contact_details_view.dart'; import 'package:stackwallet/pages/address_book_views/subviews/edit_contact_address_view.dart'; import 'package:stackwallet/pages/address_book_views/subviews/edit_contact_name_emoji_view.dart'; +import 'package:stackwallet/pages/buy_view/buy_in_wallet_view.dart'; +import 'package:stackwallet/pages/buy_view/buy_quote_preview.dart'; +import 'package:stackwallet/pages/buy_view/buy_view.dart'; +import 'package:stackwallet/pages/coin_control/coin_control_view.dart'; +import 'package:stackwallet/pages/coin_control/utxo_details_view.dart'; import 'package:stackwallet/pages/exchange_view/choose_from_stack_view.dart'; import 'package:stackwallet/pages/exchange_view/edit_trade_note_view.dart'; -import 'package:stackwallet/pages/exchange_view/exchange_loading_overlay.dart'; import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_1_view.dart'; import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_2_view.dart'; import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_3_view.dart'; @@ -32,19 +42,30 @@ import 'package:stackwallet/pages/exchange_view/exchange_step_views/step_4_view. import 'package:stackwallet/pages/exchange_view/send_from_view.dart'; import 'package:stackwallet/pages/exchange_view/trade_details_view.dart'; import 'package:stackwallet/pages/exchange_view/wallet_initiated_exchange_view.dart'; +import 'package:stackwallet/pages/generic/single_field_edit_view.dart'; import 'package:stackwallet/pages/home_view/home_view.dart'; import 'package:stackwallet/pages/intro_view.dart'; import 'package:stackwallet/pages/manage_favorites_view/manage_favorites_view.dart'; import 'package:stackwallet/pages/notification_views/notifications_view.dart'; +import 'package:stackwallet/pages/paynym/add_new_paynym_follow_view.dart'; +import 'package:stackwallet/pages/paynym/paynym_claim_view.dart'; +import 'package:stackwallet/pages/paynym/paynym_home_view.dart'; import 'package:stackwallet/pages/pinpad_views/create_pin_view.dart'; +import 'package:stackwallet/pages/receive_view/addresses/address_details_view.dart'; +import 'package:stackwallet/pages/receive_view/addresses/edit_address_label_view.dart'; +import 'package:stackwallet/pages/receive_view/addresses/wallet_addresses_view.dart'; import 'package:stackwallet/pages/receive_view/generate_receiving_uri_qr_code_view.dart'; import 'package:stackwallet/pages/receive_view/receive_view.dart'; import 'package:stackwallet/pages/send_view/confirm_transaction_view.dart'; import 'package:stackwallet/pages/send_view/send_view.dart'; +import 'package:stackwallet/pages/send_view/token_send_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/about_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/advanced_settings_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/debug_view.dart'; -import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings_view.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/advanced_views/manage_explorer_view.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/appearance_settings_view.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/manage_themes.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/appearance_settings/system_brightness_theme_selection_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/currency_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/delete_account_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/global_settings_view.dart'; @@ -70,6 +91,7 @@ import 'package:stackwallet/pages/settings_views/global_settings_view/support_vi import 'package:stackwallet/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_options_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/syncing_preferences_views/syncing_preferences_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/syncing_preferences_views/wallet_syncing_options_view.dart'; +import 'package:stackwallet/pages/settings_views/global_settings_view/xpub_view.dart'; import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_backup_views/wallet_backup_view.dart'; import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_network_settings_view/wallet_network_settings_view.dart'; import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_view.dart'; @@ -78,17 +100,26 @@ import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_set import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/rename_wallet_view.dart'; import 'package:stackwallet/pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/wallet_settings_wallet_settings_view.dart'; import 'package:stackwallet/pages/stack_privacy_calls.dart'; +import 'package:stackwallet/pages/token_view/my_tokens_view.dart'; +import 'package:stackwallet/pages/token_view/token_contract_details_view.dart'; +import 'package:stackwallet/pages/token_view/token_view.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/all_transactions_view.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/edit_note_view.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_search_filter_view.dart'; import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; +import 'package:stackwallet/pages/wallets_view/wallets_overview.dart'; import 'package:stackwallet/pages/wallets_view/wallets_view.dart'; import 'package:stackwallet/pages_desktop_specific/address_book_view/desktop_address_book.dart'; +import 'package:stackwallet/pages_desktop_specific/addresses/desktop_wallet_addresses_view.dart'; +import 'package:stackwallet/pages_desktop_specific/coin_control/desktop_coin_control_view.dart'; +// import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_all_buys_view.dart'; +import 'package:stackwallet/pages_desktop_specific/desktop_buy/desktop_buy_view.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_all_trades_view.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_exchange/desktop_exchange_view.dart'; import 'package:stackwallet/pages_desktop_specific/desktop_home_view.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/my_stack_view.dart'; +import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_token_view.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/desktop_attention_delete_wallet.dart'; @@ -103,7 +134,7 @@ import 'package:stackwallet/pages_desktop_specific/password/forgot_password_desk import 'package:stackwallet/pages_desktop_specific/password/forgotten_passphrase_restore_from_swb.dart'; import 'package:stackwallet/pages_desktop_specific/settings/desktop_settings_view.dart'; import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/advanced_settings/advanced_settings.dart'; -import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/appearance_settings.dart'; +import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/appearance_settings/appearance_settings.dart'; import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/backup_and_restore/backup_and_restore_settings.dart'; import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/currency_settings/currency_settings.dart'; import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/desktop_about_view.dart'; @@ -115,10 +146,14 @@ import 'package:stackwallet/pages_desktop_specific/settings/settings_menu/syncin import 'package:stackwallet/services/coins/manager.dart'; import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/enums/add_wallet_type_enum.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/widgets/choose_coin_view.dart'; import 'package:tuple/tuple.dart'; +import 'models/isar/models/contact_entry.dart'; + class RouteGenerator { static const bool useMaterialPageRoute = true; @@ -172,9 +207,39 @@ class RouteGenerator { } return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, - builder: (_) => StackPrivacyCalls(isSettings: false), + builder: (_) => const StackPrivacyCalls(isSettings: false), settings: RouteSettings(name: settings.name)); + case ChooseCoinView.routeName: + if (args is Tuple3) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => ChooseCoinView( + title: args.item1, + coinAdditional: args.item2, + nextRouteName: args.item3, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case ManageExplorerView.routeName: + if (args is Coin) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => ManageExplorerView( + coin: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + case WalletsView.routeName: return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, @@ -187,6 +252,198 @@ class RouteGenerator { builder: (_) => const AddWalletView(), settings: RouteSettings(name: settings.name)); + case EditWalletTokensView.routeName: + if (args is String) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => EditWalletTokensView( + walletId: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } else if (args is Tuple2>) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => EditWalletTokensView( + walletId: args.item1, + contractsToMarkSelected: args.item2, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case DesktopTokenView.routeName: + if (args is String) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => DesktopTokenView( + walletId: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case SelectWalletForTokenView.routeName: + if (args is EthTokenEntity) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => SelectWalletForTokenView( + entity: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case AddCustomTokenView.routeName: + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const AddCustomTokenView(), + settings: RouteSettings( + name: settings.name, + ), + ); + + case WalletsOverview.routeName: + if (args is Coin) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => WalletsOverview( + coin: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case TokenContractDetailsView.routeName: + if (args is Tuple2) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => TokenContractDetailsView( + contractAddress: args.item1, + walletId: args.item2, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case SingleFieldEditView.routeName: + if (args is Tuple2) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => SingleFieldEditView( + initialValue: args.item1, + label: args.item2, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case CoinControlView.routeName: + if (args is Tuple2) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => CoinControlView( + walletId: args.item1, + type: args.item2, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } else if (args + is Tuple4?>) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => CoinControlView( + walletId: args.item1, + type: args.item2, + requestedTotal: args.item3, + selectedUTXOs: args.item4, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case UtxoDetailsView.routeName: + if (args is Tuple2) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => UtxoDetailsView( + walletId: args.item2, + utxoId: args.item1, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case PaynymClaimView.routeName: + if (args is String) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => PaynymClaimView( + walletId: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case PaynymHomeView.routeName: + if (args is String) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => PaynymHomeView( + walletId: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case AddNewPaynymFollowView.routeName: + if (args is String) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => AddNewPaynymFollowView( + walletId: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + case GlobalSettingsView.routeName: return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, @@ -276,6 +533,20 @@ class RouteGenerator { builder: (_) => const DebugView(), settings: RouteSettings(name: settings.name)); + case XPubView.routeName: + if (args is String) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => XPubView( + walletId: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + case AppearanceSettingsView.routeName: return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, @@ -420,6 +691,20 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); + case EditAddressLabelView.routeName: + if (args is int) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => EditAddressLabelView( + addressLabelId: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + case EditTradeNoteView.routeName: if (args is Tuple2) { return getRoute( @@ -509,6 +794,15 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); + case SystemBrightnessThemeSelectionView.routeName: + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const SystemBrightnessThemeSelectionView(), + settings: RouteSettings( + name: settings.name, + ), + ); + case WalletNetworkSettingsView.routeName: if (args is Tuple3) { return getRoute( @@ -583,11 +877,11 @@ class RouteGenerator { return _routeError("${settings.name} invalid args: ${args.toString()}"); case CreateOrRestoreWalletView.routeName: - if (args is Coin) { + if (args is AddWalletListEntity) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, builder: (_) => CreateOrRestoreWalletView( - coin: args, + entity: args, ), settings: RouteSettings( name: settings.name, @@ -642,15 +936,15 @@ class RouteGenerator { return _routeError("${settings.name} invalid args: ${args.toString()}"); case RestoreWalletView.routeName: - if (args is Tuple4) { + if (args is Tuple5) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, builder: (_) => RestoreWalletView( - walletName: args.item1, - coin: args.item2, - seedWordsLength: args.item3, - restoreFromDate: args.item4, - ), + walletName: args.item1, + coin: args.item2, + seedWordsLength: args.item3, + restoreFromDate: args.item4, + mnemonicPassphrase: args.item5), settings: RouteSettings( name: settings.name, ), @@ -753,12 +1047,51 @@ class RouteGenerator { return _routeError("${settings.name} invalid args: ${args.toString()}"); case ReceiveView.routeName: - if (args is Tuple2) { + if (args is String) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => ReceiveView( + walletId: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } else if (args is Tuple2) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, builder: (_) => ReceiveView( walletId: args.item1, - coin: args.item2, + tokenContract: args.item2, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case WalletAddressesView.routeName: + if (args is String) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => WalletAddressesView( + walletId: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case AddressDetailsView.routeName: + if (args is Tuple2) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => AddressDetailsView( + walletId: args.item2, + addressId: args.item1, ), settings: RouteSettings( name: settings.name, @@ -791,6 +1124,34 @@ class RouteGenerator { name: settings.name, ), ); + } else if (args is Tuple3) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => SendView( + walletId: args.item1, + coin: args.item2, + accountLite: args.item3, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case TokenSendView.routeName: + if (args is Tuple3) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => TokenSendView( + walletId: args.item1, + coin: args.item2, + tokenContract: args.item3, + ), + settings: RouteSettings( + name: settings.name, + ), + ); } return _routeError("${settings.name} invalid args: ${args.toString()}"); @@ -810,7 +1171,7 @@ class RouteGenerator { return _routeError("${settings.name} invalid args: ${args.toString()}"); case WalletInitiatedExchangeView.routeName: - if (args is Tuple3) { + if (args is Tuple2) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, builder: (_) => Stack( @@ -819,9 +1180,29 @@ class RouteGenerator { walletId: args.item1, coin: args.item2, ), - ExchangeLoadingOverlayView( - unawaitedLoad: args.item3, + // ExchangeLoadingOverlayView( + // unawaitedLoad: args.item3, + // ), + ], + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + if (args is Tuple3) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => Stack( + children: [ + WalletInitiatedExchangeView( + walletId: args.item1, + coin: args.item2, + contract: args.item3, ), + // ExchangeLoadingOverlayView( + // unawaitedLoad: args.item3, + // ), ], ), settings: RouteSettings( @@ -968,7 +1349,7 @@ class RouteGenerator { return _routeError("${settings.name} invalid args: ${args.toString()}"); case SendFromView.routeName: - if (args is Tuple4) { + if (args is Tuple4) { return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, builder: (_) => SendFromView( @@ -999,6 +1380,20 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); + case BuyQuotePreviewView.routeName: + if (args is SimplexQuote) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => BuyQuotePreviewView( + quote: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + // == Desktop specific routes ============================================ case CreatePasswordView.routeName: if (args is bool) { @@ -1061,6 +1456,42 @@ class RouteGenerator { builder: (_) => const DesktopExchangeView(), settings: RouteSettings(name: settings.name)); + case BuyView.routeName: + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const BuyView(), + settings: RouteSettings(name: settings.name)); + + case BuyInWalletView.routeName: + if (args is Coin) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => BuyInWalletView(coin: args), + settings: RouteSettings( + name: settings.name, + ), + ); + } + if (args is Tuple2) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => BuyInWalletView( + coin: args.item1, + contract: args.item2, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case DesktopBuyView.routeName: + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const DesktopBuyView(), + settings: RouteSettings(name: settings.name)); + case DesktopAllTradesView.routeName: return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, @@ -1093,6 +1524,34 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); + case DesktopWalletAddressesView.routeName: + if (args is String) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => DesktopWalletAddressesView( + walletId: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + case DesktopCoinControlView.routeName: + if (args is String) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => DesktopCoinControlView( + walletId: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + case BackupRestoreSettings.routeName: return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, @@ -1135,6 +1594,12 @@ class RouteGenerator { builder: (_) => const AppearanceOptionSettings(), settings: RouteSettings(name: settings.name)); + case ManageThemesView.routeName: + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => const ManageThemesView(), + settings: RouteSettings(name: settings.name)); + case AdvancedSettings.routeName: return getRoute( shouldUseMaterialRoute: useMaterialPageRoute, @@ -1292,6 +1757,48 @@ class RouteGenerator { } return _routeError("${settings.name} invalid args: ${args.toString()}"); + case MyTokensView.routeName: + if (args is String) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => MyTokensView( + walletId: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + + // case WalletView.routeName: + // if (args is Tuple2>) { + // return getRoute( + // shouldUseMaterialRoute: useMaterialPageRoute, + // builder: (_) => WalletView( + // walletId: args.item1, + // managerProvider: args.item2, + // ), + // settings: RouteSettings( + // name: settings.name, + // ), + // ); + // } + + case TokenView.routeName: + if (args is String) { + return getRoute( + shouldUseMaterialRoute: useMaterialPageRoute, + builder: (_) => TokenView( + walletId: args, + ), + settings: RouteSettings( + name: settings.name, + ), + ); + } + return _routeError("${settings.name} invalid args: ${args.toString()}"); + // == End of desktop specific routes ===================================== default: diff --git a/lib/services/address_book_service.dart b/lib/services/address_book_service.dart index 80c60e4b8..793dccb8b 100644 --- a/lib/services/address_book_service.dart +++ b/lib/services/address_book_service.dart @@ -1,55 +1,33 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; -import 'package:stackwallet/hive/db.dart'; -import 'package:stackwallet/models/contact.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/isar/models/contact_entry.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/logger.dart'; class AddressBookService extends ChangeNotifier { - Contact getContactById(String id) { - final json = DB.instance - .get(boxName: DB.boxNameAddressBook, key: id) as Map?; - if (json == null) { - Logging.instance - .log("Attempted to get non existing contact", level: LogLevel.Fatal); + ContactEntry getContactById(String id) { + ContactEntry? contactEntry = MainDB.instance.getContactEntry(id: id); + if (contactEntry == null) { throw Exception('Contact ID "$id" not found!'); + } else { + return contactEntry; } - return Contact.fromJson(Map.from(json)); } - List get contacts { - final keys = List.from( - DB.instance.keys(boxName: DB.boxNameAddressBook)); - final _contacts = keys - .map((id) => Contact.fromJson(Map.from(DB.instance - .get(boxName: DB.boxNameAddressBook, key: id) as Map))) - .toList(growable: false); - _contacts - .sort((a, b) => a.name.toLowerCase().compareTo(b.name.toLowerCase())); - return _contacts; - } - - Future>? _addressBookEntries; - Future> get addressBookEntries => - _addressBookEntries ??= _fetchAddressBookEntries(); - - // Load address book contact entries - Future> _fetchAddressBookEntries() async { - return contacts; - } + List get contacts => MainDB.instance.getContactEntries(); /// search address book entries - //TODO optimize address book search? - Future> search(String text) async { - if (text.isEmpty) return addressBookEntries; - var results = (await addressBookEntries).toList(); + //TODO search using isar queries + Future> search(String text) async { + if (text.isEmpty) return contacts; + var results = contacts.toList(); results.retainWhere((contact) => matches(text, contact)); return results; } - bool matches(String term, Contact contact) { + bool matches(String term, ContactEntry contact) { if (term.isEmpty) { return true; } @@ -73,44 +51,27 @@ class AddressBookService extends ChangeNotifier { /// /// returns false if it provided [contact]'s id already exists in the database /// other true if the [contact] was saved - Future addContact(Contact contact) async { - if (DB.instance.containsKey( - boxName: DB.boxNameAddressBook, key: contact.id)) { + Future addContact(ContactEntry contact) async { + if (await MainDB.instance.isContactEntryExists(id: contact.customId)) { return false; + } else { + await MainDB.instance.putContactEntry(contactEntry: contact); + notifyListeners(); + return true; } - - await DB.instance.put( - boxName: DB.boxNameAddressBook, - key: contact.id, - value: contact.toMap()); - - Logging.instance.log("add address book entry saved", level: LogLevel.Info); - await _refreshAddressBookEntries(); - return true; } /// Edit contact - Future editContact(Contact editedContact) async { + Future editContact(ContactEntry editedContact) async { // over write the contact with edited version - await DB.instance.put( - boxName: DB.boxNameAddressBook, - key: editedContact.id, - value: editedContact.toMap()); - - Logging.instance.log("edit address book entry saved", level: LogLevel.Info); - await _refreshAddressBookEntries(); + await MainDB.instance.putContactEntry(contactEntry: editedContact); + notifyListeners(); return true; } /// Remove address book contact entry from db if it exists Future removeContact(String id) async { - await DB.instance.delete(key: id, boxName: DB.boxNameAddressBook); - await _refreshAddressBookEntries(); - } - - Future _refreshAddressBookEntries() async { - final newAddressBookEntries = await _fetchAddressBookEntries(); - _addressBookEntries = Future(() => newAddressBookEntries); + await MainDB.instance.deleteContactEntry(id: id); notifyListeners(); } } diff --git a/lib/services/auto_swb_service.dart b/lib/services/auto_swb_service.dart index f7efc994e..15b4b9f77 100644 --- a/lib/services/auto_swb_service.dart +++ b/lib/services/auto_swb_service.dart @@ -75,8 +75,7 @@ class AutoSWBService extends ChangeNotifier { createAutoBackupFilename(autoBackupDirectoryPath, now); final result = await SWB.encryptStackWalletWithADK( - fileToSave, adkString!, jsonString, - adkVersion: adkVersion); + fileToSave, adkString!, jsonString, adkVersion); if (!result) { throw Exception("stack auto backup service failed to create a backup"); diff --git a/lib/services/buy/buy.dart b/lib/services/buy/buy.dart new file mode 100644 index 000000000..84e38487c --- /dev/null +++ b/lib/services/buy/buy.dart @@ -0,0 +1,3 @@ +abstract class Buy { + String get name; +} diff --git a/lib/services/buy/buy_data_loading_service.dart b/lib/services/buy/buy_data_loading_service.dart new file mode 100644 index 000000000..93999c221 --- /dev/null +++ b/lib/services/buy/buy_data_loading_service.dart @@ -0,0 +1,62 @@ +// import 'package:flutter_riverpod/flutter_riverpod.dart'; +// import 'package:stackwallet/providers/providers.dart'; +// import 'package:stackwallet/services/buy/simplex/simplex_api.dart'; +// import 'package:stackwallet/utilities/logger.dart'; +// +// class BuyDataLoadingService { +// Future loadAll(WidgetRef ref) async { +// try { +// await Future.wait([ +// _loadSimplexCurrencies(ref), +// ]); +// } catch (e, s) { +// Logging.instance.log("BuyDataLoadingService.loadAll failed: $e\n$s", +// level: LogLevel.Error); +// } +// } +// +// Future _loadSimplexCurrencies(WidgetRef ref) async { +// bool error = false; +// // if (ref.read(simplexLoadStatusStateProvider.state).state == +// // SimplexLoadStatus.loading) { +// // // already in progress so just +// // return; +// // } +// +// ref.read(simplexLoadStatusStateProvider.state).state = +// SimplexLoadStatus.loading; +// +// final response = await SimplexAPI.instance.getSupported(); +// +// if (response.value != null) { +// ref +// .read(supportedSimplexCurrenciesProvider) +// .updateSupportedCryptos(response.value!.item1); +// } else { +// error = true; +// Logging.instance.log( +// "_loadSimplexCurrencies: $response", +// level: LogLevel.Warning, +// ); +// } +// +// if (response.value != null) { +// ref +// .read(supportedSimplexCurrenciesProvider) +// .updateSupportedFiats(response.value!.item2); +// } else { +// error = true; +// Logging.instance.log( +// "_loadSimplexCurrencies: $response", +// level: LogLevel.Warning, +// ); +// } +// +// if (error) { +// // _loadSimplexCurrencies() again? +// } else { +// ref.read(simplexLoadStatusStateProvider.state).state = +// SimplexLoadStatus.success; +// } +// } +// } diff --git a/lib/services/buy/buy_response.dart b/lib/services/buy/buy_response.dart new file mode 100644 index 000000000..6510ece4c --- /dev/null +++ b/lib/services/buy/buy_response.dart @@ -0,0 +1,28 @@ +enum BuyExceptionType { + generic, + serializeResponseError, + cryptoAmountOutOfRange, +} + +class BuyException implements Exception { + String errorMessage; + BuyExceptionType type; + BuyException(this.errorMessage, this.type); + + @override + String toString() { + return errorMessage; + } +} + +class BuyResponse { + final T? value; + final BuyException? exception; + + BuyResponse({this.value, this.exception}); + + @override + String toString() { + return "{error: $exception, value: $value}"; + } +} diff --git a/lib/services/buy/simplex/simplex_api.dart b/lib/services/buy/simplex/simplex_api.dart new file mode 100644 index 000000000..d1a110128 --- /dev/null +++ b/lib/services/buy/simplex/simplex_api.dart @@ -0,0 +1,367 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:decimal/decimal.dart'; +import 'package:http/http.dart' as http; +import 'package:stackwallet/models/buy/response_objects/crypto.dart'; +import 'package:stackwallet/models/buy/response_objects/fiat.dart'; +import 'package:stackwallet/models/buy/response_objects/order.dart'; +import 'package:stackwallet/models/buy/response_objects/quote.dart'; +import 'package:stackwallet/services/buy/buy_response.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/fiat_enum.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/prefs.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class SimplexAPI { + static const String authority = "buycrypto.stackwallet.com"; + // static const String authority = "localhost"; // For development purposes + static const String scheme = authority == "localhost" ? "http" : "https"; + + final _prefs = Prefs.instance; + + SimplexAPI._(); + static final SimplexAPI _instance = SimplexAPI._(); + static SimplexAPI get instance => _instance; + + /// set this to override using standard http client. Useful for testing + http.Client? client; + + Uri _buildUri(String path, Map? params) { + if (scheme == "http") { + return Uri.http(authority, path, params); + } + return Uri.https(authority, path, params); + } + + Future>> getSupportedCryptos() async { + try { + Map headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + Map data = { + 'ROUTE': 'supported_cryptos', + }; + Uri url = _buildUri('api.php', data); + + var res = await http.post(url, headers: headers); + if (res.statusCode != 200) { + throw Exception( + 'getAvailableCurrencies exception: statusCode= ${res.statusCode}'); + } + final jsonArray = jsonDecode(res.body); // TODO handle if invalid json + + return _parseSupportedCryptos(jsonArray); + } catch (e, s) { + Logging.instance.log("getAvailableCurrencies exception: $e\n$s", + level: LogLevel.Error); + return BuyResponse( + exception: BuyException( + e.toString(), + BuyExceptionType.generic, + ), + ); + } + } + + BuyResponse> _parseSupportedCryptos(dynamic jsonArray) { + try { + List cryptos = []; + List fiats = []; + + for (final crypto in jsonArray as List) { + // TODO validate jsonArray + if (isStackCoin("${crypto['ticker_symbol']}")) { + cryptos.add(Crypto.fromJson({ + 'ticker': "${crypto['ticker_symbol']}", + 'name': crypto['name'], + 'network': "${crypto['network']}", + 'contractAddress': "${crypto['contractAddress']}", + 'image': "", + })); + } + } + + return BuyResponse(value: cryptos); + } catch (e, s) { + Logging.instance + .log("_parseSupported exception: $e\n$s", level: LogLevel.Error); + return BuyResponse( + exception: BuyException( + e.toString(), + BuyExceptionType.generic, + ), + ); + } + } + + Future>> getSupportedFiats() async { + try { + Map headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + Map data = { + 'ROUTE': 'supported_fiats', + }; + Uri url = _buildUri('api.php', data); + + var res = await http.post(url, headers: headers); + if (res.statusCode != 200) { + throw Exception( + 'getAvailableCurrencies exception: statusCode= ${res.statusCode}'); + } + final jsonArray = jsonDecode(res.body); // TODO validate json + + return _parseSupportedFiats(jsonArray); + } catch (e, s) { + Logging.instance.log("getAvailableCurrencies exception: $e\n$s", + level: LogLevel.Error); + return BuyResponse( + exception: BuyException( + e.toString(), + BuyExceptionType.generic, + ), + ); + } + } + + BuyResponse> _parseSupportedFiats(dynamic jsonArray) { + try { + List cryptos = []; + List fiats = []; + + for (final fiat in jsonArray as List) { + if (isSimplexFiat("${fiat['ticker_symbol']}")) { + // TODO validate list + fiats.add(Fiat.fromJson({ + 'ticker': "${fiat['ticker_symbol']}", + 'name': fiatFromTickerCaseInsensitive("${fiat['ticker_symbol']}") + .prettyName, + 'minAmount': "${fiat['min_amount']}", + 'maxAmount': "${fiat['max_amount']}", + 'image': "", + })); + } // TODO handle else + } + + return BuyResponse(value: fiats); + } catch (e, s) { + Logging.instance + .log("_parseSupported exception: $e\n$s", level: LogLevel.Error); + return BuyResponse( + exception: BuyException( + e.toString(), + BuyExceptionType.generic, + ), + ); + } + } + + Future> getQuote(SimplexQuote quote) async { + try { + await _prefs.init(); + String? userID = _prefs.userID; + + Map headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + Map data = { + 'ROUTE': 'quote', + 'CRYPTO_TICKER': quote.crypto.ticker.toUpperCase(), + 'FIAT_TICKER': quote.fiat.ticker.toUpperCase(), + 'REQUESTED_TICKER': quote.buyWithFiat + ? quote.fiat.ticker.toUpperCase() + : quote.crypto.ticker.toUpperCase(), + 'REQUESTED_AMOUNT': quote.buyWithFiat + ? "${quote.youPayFiatPrice}" + : "${quote.youReceiveCryptoAmount}", + }; + if (userID != null) { + data['USER_ID'] = userID; + } + Uri url = _buildUri('api.php', data); + + var res = await http.get(url, headers: headers); + if (res.statusCode != 200) { + throw Exception('getQuote exception: statusCode= ${res.statusCode}'); + } + final jsonArray = jsonDecode(res.body); + if (jsonArray.containsKey('error') as bool) { + if (jsonArray['error'] == true || jsonArray['error'] == 'true') { + // jsonArray['error'] as bool == true? + throw Exception('getQuote exception: ${jsonArray['error']}'); + } + } + + jsonArray['quote'] = quote; // Add and pass this on + + return _parseQuote(jsonArray); + } catch (e, s) { + Logging.instance.log("getQuote exception: $e\n$s", level: LogLevel.Error); + return BuyResponse( + exception: BuyException( + e.toString(), + BuyExceptionType.generic, + ), + ); + } + } + + BuyResponse _parseQuote(dynamic jsonArray) { + try { + // final Map lol = + // Map.from(jsonArray as Map); + + double? cryptoAmount = jsonArray['digital_money']?['amount'] as double?; + + if (cryptoAmount == null) { + String error = jsonArray['error'] as String; + return BuyResponse( + exception: BuyException( + error, + BuyExceptionType.cryptoAmountOutOfRange, + ), + ); + } + + SimplexQuote quote = jsonArray['quote'] as SimplexQuote; + final SimplexQuote _quote = SimplexQuote( + crypto: quote.crypto, + fiat: quote.fiat, + youPayFiatPrice: quote.buyWithFiat + ? quote.youPayFiatPrice + : Decimal.parse("${jsonArray['fiat_money']['base_amount']}"), + youReceiveCryptoAmount: + Decimal.parse("${jsonArray['digital_money']['amount']}"), + id: jsonArray['quote_id'] as String, + receivingAddress: quote.receivingAddress, + buyWithFiat: quote.buyWithFiat, + ); + + return BuyResponse(value: _quote); + } catch (e, s) { + Logging.instance + .log("_parseQuote exception: $e\n$s", level: LogLevel.Error); + return BuyResponse( + exception: BuyException( + e.toString(), + BuyExceptionType.generic, + ), + ); + } + } + + Future> newOrder(SimplexQuote quote) async { + // Calling Simplex's API manually: + // curl --request POST \ + // --url https://sandbox.test-simplexcc.com/wallet/merchant/v2/payments/partner/data \ + // --header 'Authorization: ApiKey $apiKey' \ + // --header 'accept: application/json' \ + // --header 'content-type: application/json' \ + // -d '{"account_details": {"app_provider_id": "$publicKey", "app_version_id": "123", "app_end_user_id": "01e7a0b9-8dfc-4988-a28d-84a34e5f0a63", "signup_login": {"timestamp": "1994-11-05T08:15:30-05:00", "ip": "207.66.86.226"}}, "transaction_details": {"payment_details": {"quote_id": "3b58f4b4-ed6f-447c-b96a-ffe97d7b6803", "payment_id": "baaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", "order_id": "789", "original_http_ref_url": "https://stackwallet.com/simplex", "destination_wallet": {"currency": "BTC", "address": "bc1qjvj9ca8gdsv3g58yrzrk6jycvgnjh9uj35rja2"}}}}' + try { + await _prefs.init(); + String? userID = _prefs.userID; + int? signupEpoch = _prefs.signupEpoch; + + Map headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + Map data = { + 'ROUTE': 'order', + 'QUOTE_ID': quote.id, + 'ADDRESS': quote.receivingAddress, + 'CRYPTO_TICKER': quote.crypto.ticker.toUpperCase(), + }; + if (userID != null) { + data['USER_ID'] = userID; + } + if (signupEpoch != null && signupEpoch != 0) { + DateTime date = DateTime.fromMillisecondsSinceEpoch(signupEpoch * 1000); + data['SIGNUP_TIMESTAMP'] = + date.toIso8601String() + timeZoneFormatter(date.timeZoneOffset); + } + Uri url = _buildUri('api.php', data); + + var res = await http.get(url, headers: headers); + if (res.statusCode != 200) { + throw Exception('newOrder exception: statusCode= ${res.statusCode}'); + } + final jsonArray = jsonDecode(res.body); // TODO check if valid json + if (jsonArray.containsKey('error') as bool) { + if (jsonArray['error'] == true || jsonArray['error'] == 'true') { + throw Exception(jsonArray['message']); + } + } + + SimplexOrder _order = SimplexOrder( + quote: quote, + paymentId: "${jsonArray['paymentId']}", + orderId: "${jsonArray['orderId']}", + userId: "${jsonArray['userId']}", + ); + + return BuyResponse(value: _order); + } catch (e, s) { + Logging.instance.log("newOrder exception: $e\n$s", level: LogLevel.Error); + return BuyResponse( + exception: BuyException( + e.toString(), + BuyExceptionType.generic, + ), + ); + } + } + + Future> redirect(SimplexOrder order) async { + try { + Map headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + }; + Map data = { + 'ROUTE': 'redirect', + 'PAYMENT_ID': order.paymentId, + }; + Uri url = _buildUri('api.php', data); + + bool status = await launchUrl( + url, + mode: LaunchMode.externalApplication, + ); + + return BuyResponse(value: status); + } catch (e, s) { + Logging.instance.log("newOrder exception: $e\n$s", level: LogLevel.Error); + return BuyResponse( + exception: BuyException( + e.toString(), + BuyExceptionType.generic, + )); + } + } + + bool isSimplexFiat(String ticker) { + try { + fiatFromTickerCaseInsensitive(ticker); + return true; + } on ArgumentError catch (_) { + return false; + } + } + + // See https://github.com/dart-lang/sdk/issues/43391#issuecomment-1229656422 + String timeZoneFormatter(Duration offset) => + "${offset.isNegative ? "-" : "+"}${offset.inHours.abs().toString().padLeft(2, "0")}:${(offset.inMinutes - offset.inHours * 60).abs().toString().padLeft(2, "0")}"; +} + +bool isStackCoin(String? ticker) { + if (ticker == null) return false; + + try { + coinFromTickerCaseInsensitive(ticker); + return true; + } on ArgumentError catch (_) { + return false; + } +} diff --git a/lib/services/coins/bitcoin/bitcoin_wallet.dart b/lib/services/coins/bitcoin/bitcoin_wallet.dart index 1ed87681b..cfa3b73cf 100644 --- a/lib/services/coins/bitcoin/bitcoin_wallet.dart +++ b/lib/services/coins/bitcoin/bitcoin_wallet.dart @@ -1,91 +1,79 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'dart:typed_data'; +import 'dart:math'; import 'package:bech32/bech32.dart'; import 'package:bip32/bip32.dart' as bip32; import 'package:bip39/bip39.dart' as bip39; import 'package:bitcoindart/bitcoindart.dart'; import 'package:bs58check/bs58check.dart' as bs58check; -import 'package:crypto/crypto.dart'; import 'package:decimal/decimal.dart'; -import 'package:devicelocale/devicelocale.dart'; import 'package:flutter/foundation.dart'; -import 'package:http/http.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; -import 'package:stackwallet/hive/db.dart'; -import 'package:stackwallet/models/models.dart' as models; +import 'package:stackwallet/exceptions/electrumx/no_such_transaction.dart'; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; import 'package:stackwallet/models/paymint/fee_object_model.dart'; -import 'package:stackwallet/models/paymint/transactions_model.dart'; -import 'package:stackwallet/models/paymint/utxo_model.dart'; +import 'package:stackwallet/models/signing_data.dart'; import 'package:stackwallet/services/coins/coin_service.dart'; import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +import 'package:stackwallet/services/mixins/coin_control_interface.dart'; +import 'package:stackwallet/services/mixins/electrum_x_parsing.dart'; +import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart'; +import 'package:stackwallet/services/mixins/wallet_cache.dart'; +import 'package:stackwallet/services/mixins/wallet_db.dart'; +import 'package:stackwallet/services/mixins/xpubable.dart'; import 'package:stackwallet/services/node_service.dart'; -import 'package:stackwallet/services/notifications_api.dart'; -import 'package:stackwallet/services/price.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; -import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/address_utils.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/bip32_utils.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/default_nodes.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart'; import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/paynym_is_api.dart'; import 'package:stackwallet/utilities/prefs.dart'; +import 'package:stackwallet/widgets/crypto_notifications.dart'; import 'package:tuple/tuple.dart'; import 'package:uuid/uuid.dart'; const int MINIMUM_CONFIRMATIONS = 1; -const int DUST_LIMIT = 294; +final Amount DUST_LIMIT = Amount( + rawValue: BigInt.from(294), + fractionDigits: Coin.bitcoin.decimals, +); +final Amount DUST_LIMIT_P2PKH = Amount( + rawValue: BigInt.from(546), + fractionDigits: Coin.bitcoin.decimals, +); const String GENESIS_HASH_MAINNET = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; const String GENESIS_HASH_TESTNET = "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"; -enum DerivePathType { bip44, bip49, bip84 } - -bip32.BIP32 getBip32Node( - int chain, - int index, - String mnemonic, - NetworkType network, - DerivePathType derivePathType, -) { - final root = getBip32Root(mnemonic, network); - - final node = getBip32NodeFromRoot(chain, index, root, derivePathType); - return node; -} - -/// wrapper for compute() -bip32.BIP32 getBip32NodeWrapper( - Tuple5 args, -) { - return getBip32Node( - args.item1, - args.item2, - args.item3, - args.item4, - args.item5, - ); -} - -bip32.BIP32 getBip32NodeFromRoot( - int chain, - int index, - bip32.BIP32 root, - DerivePathType derivePathType, -) { +String constructDerivePath({ + required DerivePathType derivePathType, + required int networkWIF, + int account = 0, + required int chain, + required int index, +}) { String coinType; - switch (root.network.wif) { + switch (networkWIF) { case 0x80: // btc mainnet wif coinType = "0"; // btc mainnet break; @@ -93,59 +81,99 @@ bip32.BIP32 getBip32NodeFromRoot( coinType = "1"; // btc testnet break; default: - throw Exception("Invalid Bitcoin network type used!"); + throw Exception("Invalid Bitcoin network wif used!"); } + + int purpose; switch (derivePathType) { case DerivePathType.bip44: - return root.derivePath("m/44'/$coinType'/0'/$chain/$index"); + purpose = 44; + break; case DerivePathType.bip49: - return root.derivePath("m/49'/$coinType'/0'/$chain/$index"); + purpose = 49; + break; case DerivePathType.bip84: - return root.derivePath("m/84'/$coinType'/0'/$chain/$index"); + purpose = 84; + break; default: - throw Exception("DerivePathType must not be null."); + throw Exception("DerivePathType $derivePathType not supported"); } + + return "m/$purpose'/$coinType'/$account'/$chain/$index"; } -/// wrapper for compute() -bip32.BIP32 getBip32NodeFromRootWrapper( - Tuple4 args, -) { - return getBip32NodeFromRoot( - args.item1, - args.item2, - args.item3, - args.item4, - ); -} +class BitcoinWallet extends CoinServiceAPI + with + WalletCache, + WalletDB, + ElectrumXParsing, + PaynymWalletInterface, + CoinControlInterface + implements XPubAble { + BitcoinWallet({ + required String walletId, + required String walletName, + required Coin coin, + required ElectrumX client, + required CachedElectrumX cachedClient, + required TransactionNotificationTracker tracker, + required SecureStorageInterface secureStore, + MainDB? mockableOverride, + }) { + txTracker = tracker; + _walletId = walletId; + _walletName = walletName; + _coin = coin; + _electrumXClient = client; + _cachedElectrumXClient = cachedClient; + _secureStore = secureStore; + initCache(walletId, coin); + initWalletDB(mockableOverride: mockableOverride); + initCoinControlInterface( + walletId: walletId, + walletName: walletName, + coin: coin, + db: db, + getChainHeight: () => chainHeight, + refreshedBalanceCallback: (balance) async { + _balance = balance; + await updateCachedBalance(_balance!); + }, + ); + initPaynymWalletInterface( + walletId: walletId, + walletName: walletName, + network: _network, + coin: coin, + db: db, + electrumXClient: electrumXClient, + secureStorage: secureStore, + getMnemonicString: () => mnemonicString, + getMnemonicPassphrase: () => mnemonicPassphrase, + getChainHeight: () => chainHeight, + // getCurrentChangeAddress: () => currentChangeAddressP2PKH, + getCurrentChangeAddress: () => currentChangeAddress, + estimateTxFee: estimateTxFee, + prepareSend: prepareSend, + getTxCount: getTxCount, + fetchBuildTxData: fetchBuildTxData, + refresh: refresh, + checkChangeAddressForTransactions: _checkChangeAddressForTransactions, + // checkChangeAddressForTransactions: + // _checkP2PKHChangeAddressForTransactions, + dustLimitP2PKH: DUST_LIMIT_P2PKH.raw.toInt(), + minConfirms: MINIMUM_CONFIRMATIONS, + dustLimit: DUST_LIMIT.raw.toInt(), + ); + } -bip32.BIP32 getBip32Root(String mnemonic, NetworkType network) { - final seed = bip39.mnemonicToSeed(mnemonic); - final networkType = bip32.NetworkType( - wif: network.wif, - bip32: bip32.Bip32Type( - public: network.bip32.public, - private: network.bip32.private, - ), - ); - - final root = bip32.BIP32.fromSeed(seed, networkType); - return root; -} - -/// wrapper for compute() -bip32.BIP32 getBip32RootWrapper(Tuple2 args) { - return getBip32Root(args.item1, args.item2); -} - -class BitcoinWallet extends CoinServiceAPI { static const integrationTestFlag = bool.fromEnvironment("IS_INTEGRATION_TEST"); final _prefs = Prefs.instance; Timer? timer; - late Coin _coin; + late final Coin _coin; late final TransactionNotificationTracker txTracker; @@ -160,93 +188,74 @@ class BitcoinWallet extends CoinServiceAPI { } } - List outputsList = []; - @override set isFavorite(bool markFavorite) { - DB.instance.put( - boxName: walletId, key: "isFavorite", value: markFavorite); + _isFavorite = markFavorite; + updateCachedIsFavorite(markFavorite); } @override - bool get isFavorite { - try { - return DB.instance.get(boxName: walletId, key: "isFavorite") - as bool; - } catch (e, s) { - Logging.instance.log( - "isFavorite fetch failed (returning false by default): $e\n$s", - level: LogLevel.Error); - return false; - } - } + bool get isFavorite => _isFavorite ??= getCachedIsFavorite(); + + bool? _isFavorite; @override Coin get coin => _coin; @override - Future> get allOwnAddresses => - _allOwnAddresses ??= _fetchAllOwnAddresses(); - Future>? _allOwnAddresses; - - Future? _utxoData; - Future get utxoData => _utxoData ??= _fetchUtxoData(); + Future> get utxos => db.getUTXOs(walletId).findAll(); @override - Future> get unspentOutputs async => - (await utxoData).unspentOutputArray; + Future> get transactions => db + .getTransactions(walletId) + .filter() + .not() + .group((q) => q + .subTypeEqualTo(isar_models.TransactionSubType.bip47Notification) + .and() + .typeEqualTo(isar_models.TransactionType.incoming)) + .sortByTimestampDesc() + .findAll(); @override - Future get availableBalance async { - final data = await utxoData; - return Format.satoshisToAmount( - data.satoshiBalance - data.satoshiBalanceUnconfirmed, - coin: coin); - } + Future get currentReceivingAddress async => + (await _currentReceivingAddress).value; - @override - Future get pendingBalance async { - final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalanceUnconfirmed, coin: coin); - } + Future get _currentReceivingAddress async => + (await db + .getAddresses(walletId) + .filter() + .typeEqualTo(isar_models.AddressType.p2wpkh) + .subTypeEqualTo(isar_models.AddressSubType.receiving) + .sortByDerivationIndexDesc() + .findFirst()) ?? + await _generateAddressForChain(0, 0, DerivePathTypeExt.primaryFor(coin)); - @override - Future get balanceMinusMaxFee async => - (await availableBalance) - - (Decimal.fromInt((await maxFee)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(); + Future get currentChangeAddress async => + (await _currentChangeAddress).value; - @override - Future get totalBalance async { - if (!isActive) { - final totalBalance = DB.instance - .get(boxName: walletId, key: 'totalBalance') as int?; - if (totalBalance == null) { - final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalance, coin: coin); - } else { - return Format.satoshisToAmount(totalBalance, coin: coin); - } - } - final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalance, coin: coin); - } + Future get _currentChangeAddress async => + (await db + .getAddresses(walletId) + .filter() + .typeEqualTo(isar_models.AddressType.p2wpkh) + .subTypeEqualTo(isar_models.AddressSubType.change) + .sortByDerivationIndexDesc() + .findFirst()) ?? + await _generateAddressForChain(1, 0, DerivePathTypeExt.primaryFor(coin)); - @override - Future get currentReceivingAddress => _currentReceivingAddress ??= - _getCurrentAddressForChain(0, DerivePathType.bip84); - Future? _currentReceivingAddress; + Future get currentChangeAddressP2PKH async => + (await _currentChangeAddressP2PKH).value; - Future get currentLegacyReceivingAddress => - _currentReceivingAddressP2PKH ??= - _getCurrentAddressForChain(0, DerivePathType.bip44); - Future? _currentReceivingAddressP2PKH; - - Future get currentReceivingAddressP2SH => - _currentReceivingAddressP2SH ??= - _getCurrentAddressForChain(0, DerivePathType.bip49); - Future? _currentReceivingAddressP2SH; + Future get _currentChangeAddressP2PKH async => + (await db + .getAddresses(walletId) + .filter() + .typeEqualTo(isar_models.AddressType.p2pkh) + .subTypeEqualTo(isar_models.AddressSubType.change) + .sortByDerivationIndexDesc() + .findFirst()) ?? + await _generateAddressForChain(1, 0, DerivePathType.bip44); @override Future exit() async { @@ -276,27 +285,38 @@ class BitcoinWallet extends CoinServiceAPI { @override Future> get mnemonic => _getMnemonicList(); + @override + Future get mnemonicString => + _secureStore.read(key: '${_walletId}_mnemonic'); + + @override + Future get mnemonicPassphrase => _secureStore.read( + key: '${_walletId}_mnemonicPassphrase', + ); + Future get chainHeight async { try { final result = await _electrumXClient.getBlockHeadTip(); - return result["height"] as int; + final height = result["height"] as int; + await updateCachedChainHeight(height); + if (height > storedChainHeight) { + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "Updated current chain height in $walletId $walletName!", + walletId, + ), + ); + } + return height; } catch (e, s) { Logging.instance.log("Exception caught in chainHeight: $e\n$s", level: LogLevel.Error); - return -1; + return storedChainHeight; } } - int get storedChainHeight { - final storedHeight = DB.instance - .get(boxName: walletId, key: "storedChainHeight") as int?; - return storedHeight ?? 0; - } - - Future updateStoredChainHeight({required int newHeight}) async { - await DB.instance.put( - boxName: walletId, key: "storedChainHeight", value: newHeight); - } + @override + int get storedChainHeight => getCachedChainHeight(); DerivePathType addressType({required String address}) { Uint8List? decodeBase58; @@ -338,6 +358,7 @@ class BitcoinWallet extends CoinServiceAPI { @override Future recoverFromMnemonic({ required String mnemonic, + String? mnemonicPassphrase, required int maxUnusedAddressGap, required int maxNumberOfIndexesToCheck, required int height, @@ -365,26 +386,24 @@ class BitcoinWallet extends CoinServiceAPI { throw Exception( "Attempted to generate a BitcoinWallet using a non bitcoin coin type: ${coin.name}"); } - // if (_networkType == BasicNetworkType.main) { - // if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { - // throw Exception("genesis hash does not match main net!"); - // } - // } else if (_networkType == BasicNetworkType.test) { - // if (features['genesis_hash'] != GENESIS_HASH_TESTNET) { - // throw Exception("genesis hash does not match test net!"); - // } - // } } // check to make sure we aren't overwriting a mnemonic // this should never fail - if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) { + if ((await mnemonicString) != null || + (await this.mnemonicPassphrase) != null) { longMutex = false; throw Exception("Attempted to overwrite mnemonic on restore!"); } await _secureStore.write( key: '${_walletId}_mnemonic', value: mnemonic.trim()); + await _secureStore.write( + key: '${_walletId}_mnemonicPassphrase', + value: mnemonicPassphrase ?? "", + ); + await _recoverWalletFromBIP32SeedPhrase( mnemonic: mnemonic.trim(), + mnemonicPassphrase: mnemonicPassphrase ?? "", maxUnusedAddressGap: maxUnusedAddressGap, maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, ); @@ -403,77 +422,79 @@ class BitcoinWallet extends CoinServiceAPI { level: LogLevel.Info); } - Future> _checkGaps( - int maxNumberOfIndexesToCheck, - int maxUnusedAddressGap, - int txCountBatchSize, - bip32.BIP32 root, - DerivePathType type, - int account) async { - List addressArray = []; - int returningIndex = -1; - Map> derivations = {}; + Future, DerivePathType, int>> _checkGaps( + int maxNumberOfIndexesToCheck, + int maxUnusedAddressGap, + int txCountBatchSize, + bip32.BIP32 root, + DerivePathType type, + int chain, + ) async { + List addressArray = []; int gapCounter = 0; + int highestIndexWithHistory = 0; + for (int index = 0; index < maxNumberOfIndexesToCheck && gapCounter < maxUnusedAddressGap; index += txCountBatchSize) { List iterationsAddressArray = []; Logging.instance.log( - "index: $index, \t GapCounter $account ${type.name}: $gapCounter", + "index: $index, \t GapCounter $chain ${type.name}: $gapCounter", level: LogLevel.Info); final _id = "k_$index"; Map txCountCallArgs = {}; - final Map receivingNodes = {}; for (int j = 0; j < txCountBatchSize; j++) { - final node = await compute( - getBip32NodeFromRootWrapper, - Tuple4( - account, - index + j, - root, - type, - ), + final derivePath = constructDerivePath( + derivePathType: type, + networkWIF: root.network.wif, + chain: chain, + index: index + j, ); - String? address; + final node = await Bip32Utils.getBip32NodeFromRoot(root, derivePath); + + String addressString; + final data = PaymentData(pubkey: node.publicKey); + isar_models.AddressType addrType; switch (type) { case DerivePathType.bip44: - address = P2PKH( - data: PaymentData(pubkey: node.publicKey), - network: _network) - .data - .address!; + addressString = P2PKH(data: data, network: _network).data.address!; + addrType = isar_models.AddressType.p2pkh; break; case DerivePathType.bip49: - address = P2SH( + addressString = P2SH( data: PaymentData( - redeem: P2WPKH( - data: PaymentData(pubkey: node.publicKey), - network: _network) - .data), + redeem: P2WPKH(data: data, network: _network).data), network: _network) .data .address!; + addrType = isar_models.AddressType.p2sh; break; case DerivePathType.bip84: - address = P2WPKH( - network: _network, - data: PaymentData(pubkey: node.publicKey)) - .data - .address!; + addressString = P2WPKH(network: _network, data: data).data.address!; + addrType = isar_models.AddressType.p2wpkh; break; default: - throw Exception("No Path type $type exists"); + throw Exception("DerivePathType $type not supported"); } - receivingNodes.addAll({ - "${_id}_$j": { - "node": node, - "address": address, - } - }); + + final address = isar_models.Address( + walletId: walletId, + value: addressString, + publicKey: node.publicKey, + type: addrType, + derivationIndex: index + j, + derivationPath: isar_models.DerivationPath()..value = derivePath, + subType: chain == 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change, + ); + + addressArray.add(address); + txCountCallArgs.addAll({ - "${_id}_$j": address, + "${_id}_$j": addressString, }); } @@ -484,20 +505,13 @@ class BitcoinWallet extends CoinServiceAPI { for (int k = 0; k < txCountBatchSize; k++) { int count = counts["${_id}_$k"]!; if (count > 0) { - final node = receivingNodes["${_id}_$k"]; - // add address to array - addressArray.add(node["address"] as String); - iterationsAddressArray.add(node["address"] as String); - // set current index - returningIndex = index + k; + iterationsAddressArray.add(txCountCallArgs["${_id}_$k"]!); + + // update highest + highestIndexWithHistory = index + k; + // reset counter gapCounter = 0; - // add info to derivations - derivations[node["address"] as String] = { - "pubKey": Format.uint8listToString( - (node["node"] as bip32.BIP32).publicKey), - "wif": (node["node"] as bip32.BIP32).toWIF(), - }; } // increase counter when no tx history found @@ -508,11 +522,7 @@ class BitcoinWallet extends CoinServiceAPI { // cache all the transactions while waiting for the current function to finish. unawaited(getTransactionCacheEarly(iterationsAddressArray)); } - return { - "addressArray": addressArray, - "index": returningIndex, - "derivations": derivations - }; + return Tuple3(addressArray, type, highestIndexWithHistory); } Future getTransactionCacheEarly(List allAddresses) async { @@ -537,235 +547,179 @@ class BitcoinWallet extends CoinServiceAPI { Future _recoverWalletFromBIP32SeedPhrase({ required String mnemonic, + required String mnemonicPassphrase, int maxUnusedAddressGap = 20, int maxNumberOfIndexesToCheck = 1000, + bool isRescan = false, }) async { longMutex = true; - Map> p2pkhReceiveDerivations = {}; - Map> p2shReceiveDerivations = {}; - Map> p2wpkhReceiveDerivations = {}; - Map> p2pkhChangeDerivations = {}; - Map> p2shChangeDerivations = {}; - Map> p2wpkhChangeDerivations = {}; + final root = await Bip32Utils.getBip32Root( + mnemonic, + mnemonicPassphrase, + _network, + ); - final root = await compute(getBip32RootWrapper, Tuple2(mnemonic, _network)); + final deriveTypes = [ + DerivePathType.bip44, + DerivePathType.bip49, + DerivePathType.bip84, + ]; - List p2pkhReceiveAddressArray = []; - List p2shReceiveAddressArray = []; - List p2wpkhReceiveAddressArray = []; - int p2pkhReceiveIndex = -1; - int p2shReceiveIndex = -1; - int p2wpkhReceiveIndex = -1; + final List, DerivePathType, int>>> + receiveFutures = []; + final List, DerivePathType, int>>> + changeFutures = []; - List p2pkhChangeAddressArray = []; - List p2shChangeAddressArray = []; - List p2wpkhChangeAddressArray = []; - int p2pkhChangeIndex = -1; - int p2shChangeIndex = -1; - int p2wpkhChangeIndex = -1; + const receiveChain = 0; + const changeChain = 1; + const indexZero = 0; // actual size is 36 due to p2pkh, p2sh, and p2wpkh so 12x3 const txCountBatchSize = 12; try { // receiving addresses - Logging.instance - .log("checking receiving addresses...", level: LogLevel.Info); - final resultReceive44 = _checkGaps(maxNumberOfIndexesToCheck, - maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 0); + Logging.instance.log( + "checking receiving addresses...", + level: LogLevel.Info, + ); - final resultReceive49 = _checkGaps(maxNumberOfIndexesToCheck, - maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip49, 0); + for (final type in deriveTypes) { + receiveFutures.add( + _checkGaps( + maxNumberOfIndexesToCheck, + maxUnusedAddressGap, + txCountBatchSize, + root, + type, + receiveChain, + ), + ); + } - final resultReceive84 = _checkGaps(maxNumberOfIndexesToCheck, - maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip84, 0); - - Logging.instance - .log("checking change addresses...", level: LogLevel.Info); // change addresses - final resultChange44 = _checkGaps(maxNumberOfIndexesToCheck, - maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 1); + Logging.instance.log( + "checking change addresses...", + level: LogLevel.Info, + ); + for (final type in deriveTypes) { + changeFutures.add( + _checkGaps( + maxNumberOfIndexesToCheck, + maxUnusedAddressGap, + txCountBatchSize, + root, + type, + changeChain, + ), + ); + } - final resultChange49 = _checkGaps(maxNumberOfIndexesToCheck, - maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip49, 1); - - final resultChange84 = _checkGaps(maxNumberOfIndexesToCheck, - maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip84, 1); - - await Future.wait([ - resultReceive44, - resultReceive49, - resultReceive84, - resultChange44, - resultChange49, - resultChange84 + // io limitations may require running these linearly instead + final futuresResult = await Future.wait([ + Future.wait(receiveFutures), + Future.wait(changeFutures), ]); - p2pkhReceiveAddressArray = - (await resultReceive44)['addressArray'] as List; - p2pkhReceiveIndex = (await resultReceive44)['index'] as int; - p2pkhReceiveDerivations = (await resultReceive44)['derivations'] - as Map>; + final receiveResults = futuresResult[0]; + final changeResults = futuresResult[1]; - p2shReceiveAddressArray = - (await resultReceive49)['addressArray'] as List; - p2shReceiveIndex = (await resultReceive49)['index'] as int; - p2shReceiveDerivations = (await resultReceive49)['derivations'] - as Map>; - - p2wpkhReceiveAddressArray = - (await resultReceive84)['addressArray'] as List; - p2wpkhReceiveIndex = (await resultReceive84)['index'] as int; - p2wpkhReceiveDerivations = (await resultReceive84)['derivations'] - as Map>; - - p2pkhChangeAddressArray = - (await resultChange44)['addressArray'] as List; - p2pkhChangeIndex = (await resultChange44)['index'] as int; - p2pkhChangeDerivations = (await resultChange44)['derivations'] - as Map>; - - p2shChangeAddressArray = - (await resultChange49)['addressArray'] as List; - p2shChangeIndex = (await resultChange49)['index'] as int; - p2shChangeDerivations = (await resultChange49)['derivations'] - as Map>; - - p2wpkhChangeAddressArray = - (await resultChange84)['addressArray'] as List; - p2wpkhChangeIndex = (await resultChange84)['index'] as int; - p2wpkhChangeDerivations = (await resultChange84)['derivations'] - as Map>; - - // save the derivations (if any) - if (p2pkhReceiveDerivations.isNotEmpty) { - await addDerivations( - chain: 0, - derivePathType: DerivePathType.bip44, - derivationsToAdd: p2pkhReceiveDerivations); - } - if (p2shReceiveDerivations.isNotEmpty) { - await addDerivations( - chain: 0, - derivePathType: DerivePathType.bip49, - derivationsToAdd: p2shReceiveDerivations); - } - if (p2wpkhReceiveDerivations.isNotEmpty) { - await addDerivations( - chain: 0, - derivePathType: DerivePathType.bip84, - derivationsToAdd: p2wpkhReceiveDerivations); - } - if (p2pkhChangeDerivations.isNotEmpty) { - await addDerivations( - chain: 1, - derivePathType: DerivePathType.bip44, - derivationsToAdd: p2pkhChangeDerivations); - } - if (p2shChangeDerivations.isNotEmpty) { - await addDerivations( - chain: 1, - derivePathType: DerivePathType.bip49, - derivationsToAdd: p2shChangeDerivations); - } - if (p2wpkhChangeDerivations.isNotEmpty) { - await addDerivations( - chain: 1, - derivePathType: DerivePathType.bip84, - derivationsToAdd: p2wpkhChangeDerivations); - } + final List addressesToStore = []; + int highestReceivingIndexWithHistory = 0; // If restoring a wallet that never received any funds, then set receivingArray manually // If we didn't do this, it'd store an empty array - if (p2pkhReceiveIndex == -1) { - final address = - await _generateAddressForChain(0, 0, DerivePathType.bip44); - p2pkhReceiveAddressArray.add(address); - p2pkhReceiveIndex = 0; - } - if (p2shReceiveIndex == -1) { - final address = - await _generateAddressForChain(0, 0, DerivePathType.bip49); - p2shReceiveAddressArray.add(address); - p2shReceiveIndex = 0; - } - if (p2wpkhReceiveIndex == -1) { - final address = - await _generateAddressForChain(0, 0, DerivePathType.bip84); - p2wpkhReceiveAddressArray.add(address); - p2wpkhReceiveIndex = 0; + for (final tuple in receiveResults) { + if (tuple.item1.isEmpty) { + final address = await _generateAddressForChain( + receiveChain, + indexZero, + tuple.item2, + ); + addressesToStore.add(address); + } else { + highestReceivingIndexWithHistory = + max(tuple.item3, highestReceivingIndexWithHistory); + addressesToStore.addAll(tuple.item1); + } } + int highestChangeIndexWithHistory = 0; // If restoring a wallet that never sent any funds with change, then set changeArray // manually. If we didn't do this, it'd store an empty array. - if (p2pkhChangeIndex == -1) { - final address = - await _generateAddressForChain(1, 0, DerivePathType.bip44); - p2pkhChangeAddressArray.add(address); - p2pkhChangeIndex = 0; - } - if (p2shChangeIndex == -1) { - final address = - await _generateAddressForChain(1, 0, DerivePathType.bip49); - p2shChangeAddressArray.add(address); - p2shChangeIndex = 0; - } - if (p2wpkhChangeIndex == -1) { - final address = - await _generateAddressForChain(1, 0, DerivePathType.bip84); - p2wpkhChangeAddressArray.add(address); - p2wpkhChangeIndex = 0; + for (final tuple in changeResults) { + if (tuple.item1.isEmpty) { + final address = await _generateAddressForChain( + changeChain, + indexZero, + tuple.item2, + ); + addressesToStore.add(address); + } else { + highestChangeIndexWithHistory = + max(tuple.item3, highestChangeIndexWithHistory); + addressesToStore.addAll(tuple.item1); + } } - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2WPKH', - value: p2wpkhReceiveAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2WPKH', - value: p2wpkhChangeAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2PKH', - value: p2pkhReceiveAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2PKH', - value: p2pkhChangeAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2SH', - value: p2shReceiveAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2SH', - value: p2shChangeAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2WPKH', - value: p2wpkhReceiveIndex); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2WPKH', - value: p2wpkhChangeIndex); - await DB.instance.put( - boxName: walletId, key: 'changeIndexP2PKH', value: p2pkhChangeIndex); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2PKH', - value: p2pkhReceiveIndex); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2SH', - value: p2shReceiveIndex); - await DB.instance.put( - boxName: walletId, key: 'changeIndexP2SH', value: p2shChangeIndex); - await DB.instance - .put(boxName: walletId, key: "id", value: _walletId); - await DB.instance - .put(boxName: walletId, key: "isFavorite", value: false); + // remove extra addresses to help minimize risk of creating a large gap + addressesToStore.removeWhere((e) => + e.subType == isar_models.AddressSubType.change && + e.derivationIndex > highestChangeIndexWithHistory); + addressesToStore.removeWhere((e) => + e.subType == isar_models.AddressSubType.receiving && + e.derivationIndex > highestReceivingIndexWithHistory); + + if (isRescan) { + await db.updateOrPutAddresses(addressesToStore); + } else { + await db.putAddresses(addressesToStore); + } + + // get own payment code + // isSegwit does not matter here at all + final myCode = await getPaymentCode(isSegwit: false); + + // refresh transactions to pick up any received notification transactions + await _refreshNotificationAddressTransactions(); + + try { + final Set codesToCheck = {}; + final nym = await PaynymIsApi().nym(myCode.toString()); + if (nym.value != null) { + for (final follower in nym.value!.followers) { + codesToCheck.add(follower.code); + } + for (final following in nym.value!.following) { + codesToCheck.add(following.code); + } + } + + // restore paynym transactions + await restoreAllHistory( + maxUnusedAddressGap: maxUnusedAddressGap, + maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + paymentCodeStrings: codesToCheck, + ); + } catch (e, s) { + Logging.instance.log( + "Failed to check paynym.is followers/following for history during " + "bitcoin wallet ($walletId $walletName) " + "_recoverWalletFromBIP32SeedPhrase: $e/n$s", + level: LogLevel.Error, + ); + } + + await Future.wait([ + _refreshTransactions(), + _updateUTXOs(), + ]); + + await Future.wait([ + updateCachedId(walletId), + updateCachedIsFavorite(false), + ]); longMutex = false; } catch (e, s) { @@ -804,12 +758,16 @@ class BitcoinWallet extends CoinServiceAPI { } } if (!needsRefresh) { - var allOwnAddresses = await _fetchAllOwnAddresses(); - List> allTxs = - await _fetchHistory(allOwnAddresses); - final txData = await transactionData; + final allOwnAddresses = await _fetchAllOwnAddresses(); + List> allTxs = await _fetchHistory( + allOwnAddresses.map((e) => e.value).toList(growable: false)); for (Map transaction in allTxs) { - if (txData.findTransaction(transaction['tx_hash'] as String) == + final txid = transaction['tx_hash'] as String; + if ((await db + .getTransactions(walletId) + .filter() + .txidMatches(txid) + .findFirst()) == null) { Logging.instance.log( " txid not found in address history already ${transaction['tx_hash']}", @@ -820,6 +778,13 @@ class BitcoinWallet extends CoinServiceAPI { } } return needsRefresh; + } on NoSuchTransactionException catch (e) { + // TODO: move direct transactions elsewhere + await db.isar.writeTxn(() async { + await db.isar.transactions.deleteByTxidWalletId(e.txid, walletId); + }); + await txTracker.deleteTransaction(e.txid); + return true; } catch (e, s) { Logging.instance.log( "Exception caught in refreshIfThereIsNewData: $e\n$s", @@ -828,16 +793,25 @@ class BitcoinWallet extends CoinServiceAPI { } } - Future getAllTxsToWatch( - TransactionData txData, - ) async { + Future getAllTxsToWatch() async { if (_hasCalledExit) return; - List unconfirmedTxnsToNotifyPending = []; - List unconfirmedTxnsToNotifyConfirmed = []; + List unconfirmedTxnsToNotifyPending = []; + List unconfirmedTxnsToNotifyConfirmed = []; - for (final chunk in txData.txChunks) { - for (final tx in chunk.transactions) { - if (tx.confirmedStatus) { + final currentChainHeight = await chainHeight; + + final txCount = await db.getTransactions(walletId).count(); + + const paginateLimit = 50; + + for (int i = 0; i < txCount; i += paginateLimit) { + final transactions = await db + .getTransactions(walletId) + .offset(i) + .limit(paginateLimit) + .findAll(); + for (final tx in transactions) { + if (tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { // get all transactions that were notified as pending but not as confirmed if (txTracker.wasNotifiedPending(tx.txid) && !txTracker.wasNotifiedConfirmed(tx.txid)) { @@ -854,60 +828,73 @@ class BitcoinWallet extends CoinServiceAPI { // notify on unconfirmed transactions for (final tx in unconfirmedTxnsToNotifyPending) { - if (tx.txType == "Received") { - unawaited(NotificationApi.showNotification( - title: "Incoming transaction", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS, - coinName: coin.name, - txid: tx.txid, - confirmations: tx.confirmations, - requiredConfirmations: MINIMUM_CONFIRMATIONS, - )); + final confirmations = tx.getConfirmations(currentChainHeight); + + if (tx.type == isar_models.TransactionType.incoming) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Incoming transaction", + walletId: walletId, + walletName: walletName, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, + coin: coin, + txid: tx.txid, + confirmations: confirmations, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + ), + ); await txTracker.addNotifiedPending(tx.txid); - } else if (tx.txType == "Sent") { - unawaited(NotificationApi.showNotification( - title: "Sending transaction", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS, - coinName: coin.name, - txid: tx.txid, - confirmations: tx.confirmations, - requiredConfirmations: MINIMUM_CONFIRMATIONS, - )); + } else if (tx.type == isar_models.TransactionType.outgoing) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Sending transaction", + walletId: walletId, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, + txid: tx.txid, + confirmations: confirmations, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); + await txTracker.addNotifiedPending(tx.txid); } } // notify on confirmed for (final tx in unconfirmedTxnsToNotifyConfirmed) { - if (tx.txType == "Received") { - unawaited(NotificationApi.showNotification( - title: "Incoming transaction confirmed", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: false, - coinName: coin.name, - )); + if (tx.type == isar_models.TransactionType.incoming) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Incoming transaction confirmed", + walletId: walletId, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: false, + txid: tx.txid, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); + await txTracker.addNotifiedConfirmed(tx.txid); - } else if (tx.txType == "Sent") { - unawaited(NotificationApi.showNotification( - title: "Outgoing transaction confirmed", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: false, - coinName: coin.name, - )); + } else if (tx.type == isar_models.TransactionType.outgoing) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Outgoing transaction confirmed", + walletId: walletId, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: false, + txid: tx.txid, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); + await txTracker.addNotifiedConfirmed(tx.txid); } } @@ -962,56 +949,57 @@ class BitcoinWallet extends CoinServiceAPI { GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId)); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId)); + // isSegwit does not matter here at all + final myCode = await getPaymentCode(isSegwit: false); + final Set codesToCheck = {}; + final nym = await PaynymIsApi().nym(myCode.toString()); + if (nym.value != null) { + for (final follower in nym.value!.followers) { + codesToCheck.add(follower.code); + } + for (final following in nym.value!.following) { + codesToCheck.add(following.code); + } + } final currentHeight = await chainHeight; const storedHeight = 1; //await storedChainHeight; Logging.instance .log("chain height: $currentHeight", level: LogLevel.Info); - Logging.instance - .log("cached height: $storedHeight", level: LogLevel.Info); + // Logging.instance + // .log("cached height: $storedHeight", level: LogLevel.Info); if (currentHeight != storedHeight) { - if (currentHeight != -1) { - // -1 failed to fetch current height - unawaited(updateStoredChainHeight(newHeight: currentHeight)); - } - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId)); - final changeAddressForTransactions = - _checkChangeAddressForTransactions(DerivePathType.bip84); + await _checkChangeAddressForTransactions(); + await _checkP2PKHChangeAddressForTransactions(); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId)); - final currentReceivingAddressesForTransactions = - _checkCurrentReceivingAddressesForTransactions(); + await _checkCurrentReceivingAddressesForTransactions(); - final newTxData = _fetchTransactionData(); + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.4, walletId)); + await checkAllCurrentReceivingPaynymAddressesForTransactions(); + + final fetchFuture = _refreshTransactions(); GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.50, walletId)); - final newUtxoData = _fetchUtxoData(); final feeObj = _getFees(); GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.60, walletId)); - _transactionData = Future(() => newTxData); - GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.70, walletId)); _feeObject = Future(() => feeObj); - _utxoData = Future(() => newUtxoData); + + await fetchFuture; GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.80, walletId)); - final allTxsToWatch = getAllTxsToWatch(await newTxData); - await Future.wait([ - newTxData, - changeAddressForTransactions, - currentReceivingAddressesForTransactions, - newUtxoData, - feeObj, - allTxsToWatch, - ]); + await checkForNotificationTransactionsTo(codesToCheck); + await _updateUTXOs(); + await getAllTxsToWatch(); GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.90, walletId)); } @@ -1067,12 +1055,13 @@ class BitcoinWallet extends CoinServiceAPI { @override Future> prepareSend({ required String address, - required int satoshiAmount, + required Amount amount, Map? args, }) async { try { final feeRateType = args?["feeRate"]; final feeRateAmount = args?["feeRateAmount"]; + final utxos = args?["UTXOs"] as Set?; if (feeRateType is FeeRateType || feeRateAmount is int) { late final int rate; if (feeRateType is FeeRateType) { @@ -1096,14 +1085,20 @@ class BitcoinWallet extends CoinServiceAPI { // check for send all bool isSendAll = false; - final balance = - Format.decimalAmountToSatoshis(await availableBalance, coin); - if (satoshiAmount == balance) { + if (amount == balance.spendable) { isSendAll = true; } - final txData = - await coinSelection(satoshiAmount, rate, address, isSendAll); + final bool coinControl = utxos != null; + + final txData = await coinSelection( + satoshiAmountToSend: amount.raw.toInt(), + selectedTxFeeRate: rate, + recipientAddress: address, + isSendAll: isSendAll, + utxos: utxos?.toList(), + coinControl: coinControl, + ); Logging.instance.log("prepare send: $txData", level: LogLevel.Info); try { @@ -1166,6 +1161,11 @@ class BitcoinWallet extends CoinServiceAPI { final txHash = await _electrumXClient.broadcastTransaction(rawTx: hex); Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info); + final utxos = txData["usedUTXOs"] as List; + + // mark utxos as used + await db.putUTXOs(utxos.map((e) => e.copyWith(used: true)).toList()); + return txHash; } catch (e, s) { Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s", @@ -1174,24 +1174,6 @@ class BitcoinWallet extends CoinServiceAPI { } } - @override - Future send({ - required String toAddress, - required int amount, - Map args = const {}, - }) async { - try { - final txData = await prepareSend( - address: toAddress, satoshiAmount: amount, args: args); - final txHash = await confirmSend(txData: txData); - return txHash; - } catch (e, s) { - Logging.instance - .log("Exception rethrown from send(): $e\n$s", level: LogLevel.Error); - rethrow; - } - } - @override Future testNetworkConnection() async { try { @@ -1244,7 +1226,7 @@ class BitcoinWallet extends CoinServiceAPI { Logging.instance .log("Generating new ${coin.prettyName} wallet.", level: LogLevel.Info); - if ((DB.instance.get(boxName: walletId, key: "id")) != null) { + if (getCachedId() != null) { throw Exception( "Attempted to initialize a new wallet using an existing wallet ID!"); } @@ -1258,81 +1240,66 @@ class BitcoinWallet extends CoinServiceAPI { rethrow; } await Future.wait([ - DB.instance.put(boxName: walletId, key: "id", value: walletId), - DB.instance - .put(boxName: walletId, key: "isFavorite", value: false), + updateCachedId(walletId), + updateCachedIsFavorite(false), ]); } @override Future initializeExisting() async { - Logging.instance.log("Opening existing ${coin.prettyName} wallet.", + Logging.instance.log("initializeExisting() ${coin.prettyName} wallet.", level: LogLevel.Info); - if ((DB.instance.get(boxName: walletId, key: "id")) == null) { + if (getCachedId() == null) { throw Exception( "Attempted to initialize an existing wallet using an unknown wallet ID!"); } + await _prefs.init(); - final data = - DB.instance.get(boxName: walletId, key: "latest_tx_model") - as TransactionData?; - if (data != null) { - _transactionData = Future(() => data); - } + + // this will add the notification address to the db if it isn't + // already there for older wallets + await getMyNotificationAddress(); + + // await _checkCurrentChangeAddressesForTransactions(); + // await _checkCurrentReceivingAddressesForTransactions(); } - @override - Future get transactionData => - _transactionData ??= _fetchTransactionData(); - Future? _transactionData; - - TransactionData? cachedTxData; - // hack to add tx to txData before refresh completes // required based on current app architecture where we don't properly store // transactions locally in a good way @override Future updateSentCachedTxData(Map txData) async { - final priceData = - await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final locale = Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; - final String worthNow = Format.localizedStringAsFixed( - value: - ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2), - decimalPlaces: 2, - locale: locale!); - - final tx = models.Transaction( + final transaction = isar_models.Transaction( + walletId: walletId, txid: txData["txid"] as String, - confirmedStatus: false, timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, - txType: "Sent", - amount: txData["recipientAmt"] as int, - worthNow: worthNow, - worthAtBlockTimestamp: worthNow, - fees: txData["fee"] as int, - inputSize: 0, - outputSize: 0, + type: isar_models.TransactionType.outgoing, + subType: isar_models.TransactionSubType.none, + // precision may be lost here hence the following amountString + amount: (txData["recipientAmt"] as Amount).raw.toInt(), + amountString: (txData["recipientAmt"] as Amount).toJsonString(), + fee: txData["fee"] as int, + height: null, + isCancelled: false, + isLelantus: false, + otherData: null, + slateId: null, + nonce: null, inputs: [], outputs: [], - address: txData["address"] as String, - height: -1, - confirmations: 0, ); - if (cachedTxData == null) { - final data = await _fetchTransactionData(); - _transactionData = Future(() => data); - } + final address = txData["address"] is String + ? await db.getAddress(walletId, txData["address"] as String) + : null; - final transactions = cachedTxData!.getAllTransactions(); - transactions[tx.txid] = tx; - cachedTxData = models.TransactionData.fromMap(transactions); - _transactionData = Future(() => cachedTxData!); + await db.addNewTransactionData( + [ + Tuple2(transaction, address), + ], + walletId, + ); } @override @@ -1342,7 +1309,7 @@ class BitcoinWallet extends CoinServiceAPI { @override String get walletId => _walletId; - late String _walletId; + late final String _walletId; @override String get walletName => _walletName; @@ -1362,29 +1329,6 @@ class BitcoinWallet extends CoinServiceAPI { late SecureStorageInterface _secureStore; - late PriceAPI _priceAPI; - - BitcoinWallet({ - required String walletId, - required String walletName, - required Coin coin, - required ElectrumX client, - required CachedElectrumX cachedClient, - required TransactionNotificationTracker tracker, - PriceAPI? priceAPI, - required SecureStorageInterface secureStore, - }) { - txTracker = tracker; - _walletId = walletId; - _walletName = walletName; - _coin = coin; - _electrumXClient = client; - _cachedElectrumXClient = cachedClient; - - _priceAPI = priceAPI ?? PriceAPI(Client()); - _secureStore = secureStore; - } - @override Future updateNode(bool shouldRefresh) async { final failovers = NodeService(secureStorageInterface: _secureStore) @@ -1415,12 +1359,11 @@ class BitcoinWallet extends CoinServiceAPI { } Future> _getMnemonicList() async { - final mnemonicString = - await _secureStore.read(key: '${_walletId}_mnemonic'); - if (mnemonicString == null) { + final _mnemonicString = await mnemonicString; + if (_mnemonicString == null) { return []; } - final List data = mnemonicString.split(' '); + final List data = _mnemonicString.split(' '); return data; } @@ -1438,53 +1381,18 @@ class BitcoinWallet extends CoinServiceAPI { ); } - Future> _fetchAllOwnAddresses() async { - final List allAddresses = []; - final receivingAddresses = DB.instance.get( - boxName: walletId, key: 'receivingAddressesP2WPKH') as List; - final changeAddresses = DB.instance.get( - boxName: walletId, key: 'changeAddressesP2WPKH') as List; - final receivingAddressesP2PKH = DB.instance.get( - boxName: walletId, key: 'receivingAddressesP2PKH') as List; - final changeAddressesP2PKH = - DB.instance.get(boxName: walletId, key: 'changeAddressesP2PKH') - as List; - final receivingAddressesP2SH = DB.instance.get( - boxName: walletId, key: 'receivingAddressesP2SH') as List; - final changeAddressesP2SH = - DB.instance.get(boxName: walletId, key: 'changeAddressesP2SH') - as List; - - for (var i = 0; i < receivingAddresses.length; i++) { - if (!allAddresses.contains(receivingAddresses[i])) { - allAddresses.add(receivingAddresses[i] as String); - } - } - for (var i = 0; i < changeAddresses.length; i++) { - if (!allAddresses.contains(changeAddresses[i])) { - allAddresses.add(changeAddresses[i] as String); - } - } - for (var i = 0; i < receivingAddressesP2PKH.length; i++) { - if (!allAddresses.contains(receivingAddressesP2PKH[i])) { - allAddresses.add(receivingAddressesP2PKH[i] as String); - } - } - for (var i = 0; i < changeAddressesP2PKH.length; i++) { - if (!allAddresses.contains(changeAddressesP2PKH[i])) { - allAddresses.add(changeAddressesP2PKH[i] as String); - } - } - for (var i = 0; i < receivingAddressesP2SH.length; i++) { - if (!allAddresses.contains(receivingAddressesP2SH[i])) { - allAddresses.add(receivingAddressesP2SH[i] as String); - } - } - for (var i = 0; i < changeAddressesP2SH.length; i++) { - if (!allAddresses.contains(changeAddressesP2SH[i])) { - allAddresses.add(changeAddressesP2SH[i] as String); - } - } + Future> _fetchAllOwnAddresses() async { + final allAddresses = await db + .getAddresses(walletId) + .filter() + .not() + .group( + (q) => q + .typeEqualTo(isar_models.AddressType.nonWallet) + .or() + .subTypeEqualTo(isar_models.AddressSubType.nonWallet), + ) + .findAll(); return allAddresses; } @@ -1501,9 +1409,18 @@ class BitcoinWallet extends CoinServiceAPI { numberOfBlocksFast: f, numberOfBlocksAverage: m, numberOfBlocksSlow: s, - fast: Format.decimalAmountToSatoshis(fast, coin), - medium: Format.decimalAmountToSatoshis(medium, coin), - slow: Format.decimalAmountToSatoshis(slow, coin), + fast: Amount.fromDecimal( + fast, + fractionDigits: coin.decimals, + ).raw.toInt(), + medium: Amount.fromDecimal( + medium, + fractionDigits: coin.decimals, + ).raw.toInt(), + slow: Amount.fromDecimal( + slow, + fractionDigits: coin.decimals, + ).raw.toInt(), ); Logging.instance.log("fetched fees: $feeObject", level: LogLevel.Info); @@ -1545,123 +1462,35 @@ class BitcoinWallet extends CoinServiceAPI { } // this should never fail - if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) { + if ((await mnemonicString) != null || (await mnemonicPassphrase) != null) { throw Exception( "Attempted to overwrite mnemonic on generate new wallet!"); } await _secureStore.write( key: '${_walletId}_mnemonic', value: bip39.generateMnemonic(strength: 256)); - - // Set relevant indexes - await DB.instance - .put(boxName: walletId, key: "receivingIndexP2WPKH", value: 0); - await DB.instance - .put(boxName: walletId, key: "changeIndexP2WPKH", value: 0); - await DB.instance - .put(boxName: walletId, key: "receivingIndexP2PKH", value: 0); - await DB.instance - .put(boxName: walletId, key: "changeIndexP2PKH", value: 0); - await DB.instance - .put(boxName: walletId, key: "receivingIndexP2SH", value: 0); - await DB.instance - .put(boxName: walletId, key: "changeIndexP2SH", value: 0); - await DB.instance.put( - boxName: walletId, - key: 'blocked_tx_hashes', - value: ["0xdefault"], - ); // A list of transaction hashes to represent frozen utxos in wallet - // initialize address book entries - await DB.instance.put( - boxName: walletId, - key: 'addressBookEntries', - value: {}); + await _secureStore.write(key: '${_walletId}_mnemonicPassphrase', value: ""); // Generate and add addresses to relevant arrays - await Future.wait([ + final initialAddresses = await Future.wait([ // P2WPKH - _generateAddressForChain(0, 0, DerivePathType.bip84).then( - (initialReceivingAddressP2WPKH) { - _addToAddressesArrayForChain( - initialReceivingAddressP2WPKH, 0, DerivePathType.bip84); - _currentReceivingAddress = - Future(() => initialReceivingAddressP2WPKH); - }, - ), - _generateAddressForChain(1, 0, DerivePathType.bip84).then( - (initialChangeAddressP2WPKH) => _addToAddressesArrayForChain( - initialChangeAddressP2WPKH, - 1, - DerivePathType.bip84, - ), - ), + _generateAddressForChain(0, 0, DerivePathType.bip84), + _generateAddressForChain(1, 0, DerivePathType.bip84), // P2PKH - _generateAddressForChain(0, 0, DerivePathType.bip44).then( - (initialReceivingAddressP2PKH) { - _addToAddressesArrayForChain( - initialReceivingAddressP2PKH, 0, DerivePathType.bip44); - _currentReceivingAddressP2PKH = - Future(() => initialReceivingAddressP2PKH); - }, - ), - _generateAddressForChain(1, 0, DerivePathType.bip44).then( - (initialChangeAddressP2PKH) => _addToAddressesArrayForChain( - initialChangeAddressP2PKH, - 1, - DerivePathType.bip44, - ), - ), + _generateAddressForChain(0, 0, DerivePathType.bip44), + _generateAddressForChain(1, 0, DerivePathType.bip44), // P2SH - _generateAddressForChain(0, 0, DerivePathType.bip49).then( - (initialReceivingAddressP2SH) { - _addToAddressesArrayForChain( - initialReceivingAddressP2SH, 0, DerivePathType.bip49); - _currentReceivingAddressP2SH = - Future(() => initialReceivingAddressP2SH); - }, - ), - _generateAddressForChain(1, 0, DerivePathType.bip49).then( - (initialChangeAddressP2SH) => _addToAddressesArrayForChain( - initialChangeAddressP2SH, - 1, - DerivePathType.bip49, - ), - ), + _generateAddressForChain(0, 0, DerivePathType.bip49), + _generateAddressForChain(1, 0, DerivePathType.bip49), ]); - // // P2PKH - // _generateAddressForChain(0, 0, DerivePathType.bip44).then( - // (initialReceivingAddressP2PKH) { - // _addToAddressesArrayForChain( - // initialReceivingAddressP2PKH, 0, DerivePathType.bip44); - // this._currentReceivingAddressP2PKH = - // Future(() => initialReceivingAddressP2PKH); - // }, - // ); - // _generateAddressForChain(1, 0, DerivePathType.bip44) - // .then((initialChangeAddressP2PKH) => _addToAddressesArrayForChain( - // initialChangeAddressP2PKH, - // 1, - // DerivePathType.bip44, - // )); - // - // // P2SH - // _generateAddressForChain(0, 0, DerivePathType.bip49).then( - // (initialReceivingAddressP2SH) { - // _addToAddressesArrayForChain( - // initialReceivingAddressP2SH, 0, DerivePathType.bip49); - // this._currentReceivingAddressP2SH = - // Future(() => initialReceivingAddressP2SH); - // }, - // ); - // _generateAddressForChain(1, 0, DerivePathType.bip49) - // .then((initialChangeAddressP2SH) => _addToAddressesArrayForChain( - // initialChangeAddressP2SH, - // 1, - // DerivePathType.bip49, - // )); + // this will add the notification address to the db if it isn't + // already there so it can be watched + await getMyNotificationAddress(); + + await db.putAddresses(initialAddresses); Logging.instance.log("_generateNewWalletFinished", level: LogLevel.Info); } @@ -1669,28 +1498,40 @@ class BitcoinWallet extends CoinServiceAPI { /// Generates a new internal or external chain address for the wallet using a BIP84, BIP44, or BIP49 derivation path. /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! /// [index] - This can be any integer >= 0 - Future _generateAddressForChain( + Future _generateAddressForChain( int chain, int index, DerivePathType derivePathType, ) async { - final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic'); - final node = await compute( - getBip32NodeWrapper, - Tuple5( - chain, - index, - mnemonic!, - _network, - derivePathType, - ), + final _mnemonic = await mnemonicString; + final _mnemonicPassphrase = await mnemonicPassphrase; + if (_mnemonicPassphrase == null) { + Logging.instance.log( + "Exception in _generateAddressForChain: mnemonic passphrase null, possible migration issue; if using internal builds, delete wallet and restore from seed, if using a release build, please file bug report", + level: LogLevel.Error); + } + + final derivePath = constructDerivePath( + derivePathType: derivePathType, + networkWIF: _network.wif, + chain: chain, + index: index, ); + final node = await Bip32Utils.getBip32Node( + _mnemonic!, + _mnemonicPassphrase!, + _network, + derivePath, + ); + final data = PaymentData(pubkey: node.publicKey); String address; + isar_models.AddressType addrType; switch (derivePathType) { case DerivePathType.bip44: address = P2PKH(data: data, network: _network).data.address!; + addrType = isar_models.AddressType.p2pkh; break; case DerivePathType.bip49: address = P2SH( @@ -1699,113 +1540,27 @@ class BitcoinWallet extends CoinServiceAPI { network: _network) .data .address!; + addrType = isar_models.AddressType.p2sh; break; case DerivePathType.bip84: address = P2WPKH(network: _network, data: data).data.address!; + addrType = isar_models.AddressType.p2wpkh; break; + default: + throw Exception("DerivePathType $derivePathType not supported"); } - // add generated address & info to derivations - await addDerivation( - chain: chain, - address: address, - pubKey: Format.uint8listToString(node.publicKey), - wif: node.toWIF(), - derivePathType: derivePathType, + return isar_models.Address( + walletId: walletId, + value: address, + publicKey: node.publicKey, + type: addrType, + derivationIndex: index, + derivationPath: isar_models.DerivationPath()..value = derivePath, + subType: chain == 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change, ); - - return address; - } - - /// Increases the index for either the internal or external chain, depending on [chain]. - /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! - Future _incrementAddressIndexForChain( - int chain, DerivePathType derivePathType) async { - // Here we assume chain == 1 if it isn't 0 - String indexKey = chain == 0 ? "receivingIndex" : "changeIndex"; - switch (derivePathType) { - case DerivePathType.bip44: - indexKey += "P2PKH"; - break; - case DerivePathType.bip49: - indexKey += "P2SH"; - break; - case DerivePathType.bip84: - indexKey += "P2WPKH"; - break; - } - - final newIndex = - (DB.instance.get(boxName: walletId, key: indexKey)) + 1; - await DB.instance - .put(boxName: walletId, key: indexKey, value: newIndex); - } - - /// Adds [address] to the relevant chain's address array, which is determined by [chain]. - /// [address] - Expects a standard native segwit address - /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! - Future _addToAddressesArrayForChain( - String address, int chain, DerivePathType derivePathType) async { - String chainArray = ''; - if (chain == 0) { - chainArray = 'receivingAddresses'; - } else { - chainArray = 'changeAddresses'; - } - switch (derivePathType) { - case DerivePathType.bip44: - chainArray += "P2PKH"; - break; - case DerivePathType.bip49: - chainArray += "P2SH"; - break; - case DerivePathType.bip84: - chainArray += "P2WPKH"; - break; - } - - final addressArray = - DB.instance.get(boxName: walletId, key: chainArray); - if (addressArray == null) { - Logging.instance.log( - 'Attempting to add the following to $chainArray array for chain $chain:${[ - address - ]}', - level: LogLevel.Info); - await DB.instance - .put(boxName: walletId, key: chainArray, value: [address]); - } else { - // Make a deep copy of the existing list - final List newArray = []; - addressArray - .forEach((dynamic _address) => newArray.add(_address as String)); - newArray.add(address); // Add the address passed into the method - await DB.instance - .put(boxName: walletId, key: chainArray, value: newArray); - } - } - - /// Returns the latest receiving/change (external/internal) address for the wallet depending on [chain] - /// and - /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! - Future _getCurrentAddressForChain( - int chain, DerivePathType derivePathType) async { - // Here, we assume that chain == 1 if it isn't 0 - String arrayKey = chain == 0 ? "receivingAddresses" : "changeAddresses"; - switch (derivePathType) { - case DerivePathType.bip44: - arrayKey += "P2PKH"; - break; - case DerivePathType.bip49: - arrayKey += "P2SH"; - break; - case DerivePathType.bip84: - arrayKey += "P2WPKH"; - break; - } - final internalChainArray = - DB.instance.get(boxName: walletId, key: arrayKey); - return internalChainArray.last as String; } String _buildDerivationStorageKey({ @@ -1824,6 +1579,8 @@ class BitcoinWallet extends CoinServiceAPI { case DerivePathType.bip84: key = "${walletId}_${chainId}DerivationsP2WPKH"; break; + default: + throw Exception("DerivePathType unsupported"); } return key; } @@ -1842,76 +1599,45 @@ class BitcoinWallet extends CoinServiceAPI { jsonDecode(derivationsString ?? "{}") as Map); } - /// Add a single derivation to the local secure storage for [chain] and - /// [derivePathType] where [chain] must either be 1 for change or 0 for receive. - /// This will overwrite a previous entry where the address of the new derivation - /// matches a derivation currently stored. - Future addDerivation({ - required int chain, - required String address, - required String pubKey, - required String wif, - required DerivePathType derivePathType, - }) async { - // build lookup key - final key = _buildDerivationStorageKey( - chain: chain, derivePathType: derivePathType); + Future>> fastFetch(List allTxHashes) async { + List> allTransactions = []; - // fetch current derivations - final derivationsString = await _secureStore.read(key: key); - final derivations = - Map.from(jsonDecode(derivationsString ?? "{}") as Map); + const futureLimit = 30; + List>> transactionFutures = []; + int currentFutureCount = 0; + for (final txHash in allTxHashes) { + Future> transactionFuture = + cachedElectrumXClient.getTransaction( + txHash: txHash, + verbose: true, + coin: coin, + ); + transactionFutures.add(transactionFuture); + currentFutureCount++; + if (currentFutureCount > futureLimit) { + currentFutureCount = 0; + await Future.wait(transactionFutures); + for (final fTx in transactionFutures) { + final tx = await fTx; - // add derivation - derivations[address] = { - "pubKey": pubKey, - "wif": wif, - }; + allTransactions.add(tx); + } + } + } + if (currentFutureCount != 0) { + currentFutureCount = 0; + await Future.wait(transactionFutures); + for (final fTx in transactionFutures) { + final tx = await fTx; - // save derivations - final newReceiveDerivationsString = jsonEncode(derivations); - await _secureStore.write(key: key, value: newReceiveDerivationsString); + allTransactions.add(tx); + } + } + return allTransactions; } - /// Add multiple derivations to the local secure storage for [chain] and - /// [derivePathType] where [chain] must either be 1 for change or 0 for receive. - /// This will overwrite any previous entries where the address of the new derivation - /// matches a derivation currently stored. - /// The [derivationsToAdd] must be in the format of: - /// { - /// addressA : { - /// "pubKey": , - /// "wif": , - /// }, - /// addressB : { - /// "pubKey": , - /// "wif": , - /// }, - /// } - Future addDerivations({ - required int chain, - required DerivePathType derivePathType, - required Map derivationsToAdd, - }) async { - // build lookup key - final key = _buildDerivationStorageKey( - chain: chain, derivePathType: derivePathType); - - // fetch current derivations - final derivationsString = await _secureStore.read(key: key); - final derivations = - Map.from(jsonDecode(derivationsString ?? "{}") as Map); - - // add derivation - derivations.addAll(derivationsToAdd); - - // save derivations - final newReceiveDerivationsString = jsonEncode(derivations); - await _secureStore.write(key: key, value: newReceiveDerivationsString); - } - - Future _fetchUtxoData() async { - final List allAddresses = await _fetchAllOwnAddresses(); + Future _updateUTXOs() async { + final allAddresses = await _fetchAllOwnAddresses(); try { final fetchedUtxoList = >>[]; @@ -1923,7 +1649,8 @@ class BitcoinWallet extends CoinServiceAPI { if (batches[batchNumber] == null) { batches[batchNumber] = {}; } - final scripthash = _convertToScriptHash(allAddresses[i], _network); + final scripthash = + AddressUtils.convertToScriptHash(allAddresses[i].value, _network); batches[batchNumber]!.addAll({ scripthash: [scripthash] }); @@ -1941,147 +1668,101 @@ class BitcoinWallet extends CoinServiceAPI { } } } - final priceData = - await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final List> outputArray = []; - int satoshiBalance = 0; - int satoshiBalancePending = 0; + + final List outputArray = []; for (int i = 0; i < fetchedUtxoList.length; i++) { for (int j = 0; j < fetchedUtxoList[i].length; j++) { - int value = fetchedUtxoList[i][j]["value"] as int; - satoshiBalance += value; + final jsonUTXO = fetchedUtxoList[i][j]; final txn = await cachedElectrumXClient.getTransaction( - txHash: fetchedUtxoList[i][j]["tx_hash"] as String, + txHash: jsonUTXO["tx_hash"] as String, verbose: true, coin: coin, ); - final Map utxo = {}; - final int confirmations = txn["confirmations"] as int? ?? 0; - final bool confirmed = confirmations >= MINIMUM_CONFIRMATIONS; - if (!confirmed) { - satoshiBalancePending += value; + // fetch stored tx to see if paynym notification tx and block utxo + final storedTx = await db.getTransaction( + walletId, + jsonUTXO["tx_hash"] as String, + ); + + bool shouldBlock = false; + String? blockReason; + String? label; + + if (storedTx?.subType == + isar_models.TransactionSubType.bip47Notification) { + if (storedTx?.type == isar_models.TransactionType.incoming) { + shouldBlock = true; + blockReason = "Incoming paynym notification transaction."; + } else if (storedTx?.type == isar_models.TransactionType.outgoing) { + shouldBlock = false; + blockReason = "Paynym notification change output. Incautious " + "handling of change outputs from notification transactions " + "may cause unintended loss of privacy."; + label = blockReason; + } } - utxo["txid"] = txn["txid"]; - utxo["vout"] = fetchedUtxoList[i][j]["tx_pos"]; - utxo["value"] = value; + final vout = jsonUTXO["tx_pos"] as int; - utxo["status"] = {}; - utxo["status"]["confirmed"] = confirmed; - utxo["status"]["confirmations"] = confirmations; - utxo["status"]["block_height"] = fetchedUtxoList[i][j]["height"]; - utxo["status"]["block_hash"] = txn["blockhash"]; - utxo["status"]["block_time"] = txn["blocktime"]; + final outputs = txn["vout"] as List; + + String? utxoOwnerAddress; + // get UTXO owner address + for (final output in outputs) { + if (output["n"] == vout) { + utxoOwnerAddress = + output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]?["address"] as String?; + } + } + + final utxo = isar_models.UTXO( + walletId: walletId, + txid: txn["txid"] as String, + vout: vout, + value: jsonUTXO["value"] as int, + name: label ?? "", + isBlocked: shouldBlock, + blockedReason: blockReason, + isCoinbase: txn["is_coinbase"] as bool? ?? false, + blockHash: txn["blockhash"] as String?, + blockHeight: jsonUTXO["height"] as int?, + blockTime: txn["blocktime"] as int?, + address: utxoOwnerAddress, + ); - final fiatValue = ((Decimal.fromInt(value) * currentPrice) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2); - utxo["rawWorth"] = fiatValue; - utxo["fiatWorth"] = fiatValue.toString(); outputArray.add(utxo); } } - Decimal currencyBalanceRaw = - ((Decimal.fromInt(satoshiBalance) * currentPrice) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2); - - final Map result = { - "total_user_currency": currencyBalanceRaw.toString(), - "total_sats": satoshiBalance, - "total_btc": (Decimal.fromInt(satoshiBalance) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal( - scaleOnInfinitePrecision: Constants.decimalPlacesForCoin(coin)) - .toString(), - "outputArray": outputArray, - "unconfirmed": satoshiBalancePending, - }; - - final dataModel = UtxoData.fromJson(result); - - final List allOutputs = dataModel.unspentOutputArray; Logging.instance - .log('Outputs fetched: $allOutputs', level: LogLevel.Info); - await _sortOutputs(allOutputs); - await DB.instance.put( - boxName: walletId, key: 'latest_utxo_model', value: dataModel); - await DB.instance.put( - boxName: walletId, - key: 'totalBalance', - value: dataModel.satoshiBalance); - return dataModel; + .log('Outputs fetched: $outputArray', level: LogLevel.Info); + + await db.updateUTXOs(walletId, outputArray); + + // finally update balance + await _updateBalance(); } catch (e, s) { Logging.instance .log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error); - final latestTxModel = - DB.instance.get(boxName: walletId, key: 'latest_utxo_model') - as models.UtxoData?; - - if (latestTxModel == null) { - final emptyModel = { - "total_user_currency": "0.00", - "total_sats": 0, - "total_btc": "0", - "outputArray": [] - }; - return UtxoData.fromJson(emptyModel); - } else { - Logging.instance - .log("Old output model located", level: LogLevel.Warning); - return latestTxModel; - } } } - /// Takes in a list of UtxoObjects and adds a name (dependent on object index within list) - /// and checks for the txid associated with the utxo being blocked and marks it accordingly. - /// Now also checks for output labeling. - Future _sortOutputs(List utxos) async { - final blockedHashArray = - DB.instance.get(boxName: walletId, key: 'blocked_tx_hashes') - as List?; - final List lst = []; - if (blockedHashArray != null) { - for (var hash in blockedHashArray) { - lst.add(hash as String); - } - } - final labels = - DB.instance.get(boxName: walletId, key: 'labels') as Map? ?? - {}; - - outputsList = []; - - for (var i = 0; i < utxos.length; i++) { - if (labels[utxos[i].txid] != null) { - utxos[i].txName = labels[utxos[i].txid] as String? ?? ""; - } else { - utxos[i].txName = 'Output #$i'; - } - - if (utxos[i].status.confirmed == false) { - outputsList.add(utxos[i]); - } else { - if (lst.contains(utxos[i].txid)) { - utxos[i].blocked = true; - outputsList.add(utxos[i]); - } else if (!lst.contains(utxos[i].txid)) { - outputsList.add(utxos[i]); - } - } - } + Future _updateBalance() async { + await refreshBalance(); } + @override + Balance get balance => _balance ??= getCachedBalance(); + Balance? _balance; + Future getTxCount({required String address}) async { String? scripthash; try { - scripthash = _convertToScriptHash(address, _network); + scripthash = AddressUtils.convertToScriptHash(address, _network); final transactions = await electrumXClient.getHistory(scripthash: scripthash); return transactions.length; @@ -2099,7 +1780,9 @@ class BitcoinWallet extends CoinServiceAPI { try { final Map> args = {}; for (final entry in addresses.entries) { - args[entry.key] = [_convertToScriptHash(entry.value, _network)]; + args[entry.key] = [ + AddressUtils.convertToScriptHash(entry.value, _network) + ]; } final response = await electrumXClient.getBatchHistory(args: args); @@ -2116,111 +1799,129 @@ class BitcoinWallet extends CoinServiceAPI { } } - Future _checkReceivingAddressForTransactions( - DerivePathType derivePathType) async { + Future _checkReceivingAddressForTransactions() async { try { - final String currentExternalAddr = - await _getCurrentAddressForChain(0, derivePathType); - final int txCount = await getTxCount(address: currentExternalAddr); + final currentReceiving = await _currentReceivingAddress; + + final int txCount = await getTxCount(address: currentReceiving.value); Logging.instance.log( - 'Number of txs for current receiving address $currentExternalAddr: $txCount', + 'Number of txs for current receiving address $currentReceiving: $txCount', level: LogLevel.Info); - if (txCount >= 1) { + if (txCount >= 1 || currentReceiving.derivationIndex < 0) { // First increment the receiving index - await _incrementAddressIndexForChain(0, derivePathType); - - // Check the new receiving index - String indexKey = "receivingIndex"; - switch (derivePathType) { - case DerivePathType.bip44: - indexKey += "P2PKH"; - break; - case DerivePathType.bip49: - indexKey += "P2SH"; - break; - case DerivePathType.bip84: - indexKey += "P2WPKH"; - break; - } - final newReceivingIndex = - DB.instance.get(boxName: walletId, key: indexKey) as int; + final newReceivingIndex = currentReceiving.derivationIndex + 1; // Use new index to derive a new receiving address final newReceivingAddress = await _generateAddressForChain( - 0, newReceivingIndex, derivePathType); + 0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin)); - // Add that new receiving address to the array of receiving addresses - await _addToAddressesArrayForChain( - newReceivingAddress, 0, derivePathType); - - // Set the new receiving address that the service - - switch (derivePathType) { - case DerivePathType.bip44: - _currentReceivingAddressP2PKH = Future(() => newReceivingAddress); - break; - case DerivePathType.bip49: - _currentReceivingAddressP2SH = Future(() => newReceivingAddress); - break; - case DerivePathType.bip84: - _currentReceivingAddress = Future(() => newReceivingAddress); - break; + final existing = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(newReceivingAddress.value) + .findFirst(); + if (existing == null) { + // Add that new change address + await db.putAddress(newReceivingAddress); + } else { + // we need to update the address + await db.updateAddress(existing, newReceivingAddress); } + // keep checking until address with no tx history is set as current + await _checkReceivingAddressForTransactions(); } } catch (e, s) { Logging.instance.log( - "Exception rethrown from _checkReceivingAddressForTransactions($derivePathType): $e\n$s", + "Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s", level: LogLevel.Error); rethrow; } } - Future _checkChangeAddressForTransactions( - DerivePathType derivePathType) async { + Future _checkChangeAddressForTransactions() async { try { - final String currentExternalAddr = - await _getCurrentAddressForChain(1, derivePathType); - final int txCount = await getTxCount(address: currentExternalAddr); + final currentChange = await _currentChangeAddress; + final int txCount = await getTxCount(address: currentChange.value); Logging.instance.log( - 'Number of txs for current change address $currentExternalAddr: $txCount', + 'Number of txs for current change address $currentChange: $txCount', level: LogLevel.Info); - if (txCount >= 1) { + if (txCount >= 1 || currentChange.derivationIndex < 0) { // First increment the change index - await _incrementAddressIndexForChain(1, derivePathType); - - // Check the new change index - String indexKey = "changeIndex"; - switch (derivePathType) { - case DerivePathType.bip44: - indexKey += "P2PKH"; - break; - case DerivePathType.bip49: - indexKey += "P2SH"; - break; - case DerivePathType.bip84: - indexKey += "P2WPKH"; - break; - } - final newChangeIndex = - DB.instance.get(boxName: walletId, key: indexKey) as int; + final newChangeIndex = currentChange.derivationIndex + 1; // Use new index to derive a new change address - final newChangeAddress = - await _generateAddressForChain(1, newChangeIndex, derivePathType); + final newChangeAddress = await _generateAddressForChain( + 1, newChangeIndex, DerivePathTypeExt.primaryFor(coin)); - // Add that new receiving address to the array of change addresses - await _addToAddressesArrayForChain(newChangeAddress, 1, derivePathType); + final existing = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(newChangeAddress.value) + .findFirst(); + if (existing == null) { + // Add that new change address + await db.putAddress(newChangeAddress); + } else { + // we need to update the address + await db.updateAddress(existing, newChangeAddress); + } + // keep checking until address with no tx history is set as current + await _checkChangeAddressForTransactions(); } } on SocketException catch (se, s) { Logging.instance.log( - "SocketException caught in _checkReceivingAddressForTransactions($derivePathType): $se\n$s", + "SocketException caught in _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $se\n$s", level: LogLevel.Error); return; } catch (e, s) { Logging.instance.log( - "Exception rethrown from _checkReceivingAddressForTransactions($derivePathType): $e\n$s", + "Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + Future _checkP2PKHChangeAddressForTransactions() async { + try { + final currentChange = await _currentChangeAddressP2PKH; + final int txCount = await getTxCount(address: currentChange.value); + Logging.instance.log( + 'Number of txs for current change address $currentChange: $txCount', + level: LogLevel.Info); + + if (txCount >= 1 || currentChange.derivationIndex < 0) { + // First increment the change index + final newChangeIndex = currentChange.derivationIndex + 1; + + // Use new index to derive a new change address + final newChangeAddress = await _generateAddressForChain( + 1, newChangeIndex, DerivePathType.bip44); + + final existing = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(newChangeAddress.value) + .findFirst(); + if (existing == null) { + // Add that new change address + await db.putAddress(newChangeAddress); + } else { + // we need to update the address + await db.updateAddress(existing, newChangeAddress); + } + // keep checking until address with no tx history is set as current + await _checkP2PKHChangeAddressForTransactions(); + } + } on SocketException catch (se, s) { + Logging.instance.log( + "SocketException caught in _checkReceivingAddressForTransactions(${DerivePathType.bip44}): $se\n$s", + level: LogLevel.Error); + return; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathType.bip44}): $e\n$s", level: LogLevel.Error); rethrow; } @@ -2228,9 +1929,9 @@ class BitcoinWallet extends CoinServiceAPI { Future _checkCurrentReceivingAddressesForTransactions() async { try { - for (final type in DerivePathType.values) { - await _checkReceivingAddressForTransactions(type); - } + // for (final type in DerivePathType.values) { + await _checkReceivingAddressForTransactions(); + // } } catch (e, s) { Logging.instance.log( "Exception rethrown from _checkCurrentReceivingAddressesForTransactions(): $e\n$s", @@ -2252,9 +1953,9 @@ class BitcoinWallet extends CoinServiceAPI { Future _checkCurrentChangeAddressesForTransactions() async { try { - for (final type in DerivePathType.values) { - await _checkChangeAddressForTransactions(type); - } + // for (final type in DerivePathType.values) { + await _checkChangeAddressForTransactions(); + // } } catch (e, s) { Logging.instance.log( "Exception rethrown from _checkCurrentChangeAddressesForTransactions(): $e\n$s", @@ -2274,28 +1975,6 @@ class BitcoinWallet extends CoinServiceAPI { } } - /// attempts to convert a string to a valid scripthash - /// - /// Returns the scripthash or throws an exception on invalid bitcoin address - String _convertToScriptHash(String bitcoinAddress, NetworkType network) { - try { - final output = Address.addressToOutputScript(bitcoinAddress, network); - final hash = sha256.convert(output.toList(growable: false)).toString(); - - final chars = hash.split(""); - final reversedPairs = []; - var i = chars.length - 1; - while (i > 0) { - reversedPairs.add(chars[i - 1]); - reversedPairs.add(chars[i]); - i -= 2; - } - return reversedPairs.join(""); - } catch (e) { - rethrow; - } - } - Future>> _fetchHistory( List allAddresses) async { try { @@ -2309,7 +1988,8 @@ class BitcoinWallet extends CoinServiceAPI { if (batches[batchNumber] == null) { batches[batchNumber] = {}; } - final scripthash = _convertToScriptHash(allAddresses[i], _network); + final scripthash = + AddressUtils.convertToScriptHash(allAddresses[i], _network); final id = Logger.isTestEnv ? "$i" : const Uuid().v1(); requestIdToAddressMap[id] = allAddresses[i]; batches[batchNumber]!.addAll({ @@ -2350,378 +2030,147 @@ class BitcoinWallet extends CoinServiceAPI { return false; } - Future>> fastFetch(List allTxHashes) async { + Future _refreshNotificationAddressTransactions() async { + final address = await getMyNotificationAddress(); + final hashes = await _fetchHistory([address.value]); + List> allTransactions = []; - const futureLimit = 30; - List>> transactionFutures = []; - int currentFutureCount = 0; - for (final txHash in allTxHashes) { - Future> transactionFuture = - cachedElectrumXClient.getTransaction( - txHash: txHash, - verbose: true, - coin: coin, - ); - transactionFutures.add(transactionFuture); - currentFutureCount++; - if (currentFutureCount > futureLimit) { - currentFutureCount = 0; - await Future.wait(transactionFutures); - for (final fTx in transactionFutures) { - final tx = await fTx; + final currentHeight = await chainHeight; - allTransactions.add(tx); - } - } - } - if (currentFutureCount != 0) { - currentFutureCount = 0; - await Future.wait(transactionFutures); - for (final fTx in transactionFutures) { - final tx = await fTx; + for (final txHash in hashes) { + final storedTx = await db + .getTransactions(walletId) + .filter() + .txidEqualTo(txHash["tx_hash"] as String) + .findFirst(); - allTransactions.add(tx); - } - } - return allTransactions; - } + // TODO: remove bip47Notification type check sometime after Q2 2023 + if (storedTx == null || + storedTx.subType == + isar_models.TransactionSubType.bip47Notification || + !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)) { + final tx = await cachedElectrumXClient.getTransaction( + txHash: txHash["tx_hash"] as String, + verbose: true, + coin: coin, + ); - Future _fetchTransactionData() async { - final List allAddresses = await _fetchAllOwnAddresses(); - - final changeAddresses = DB.instance.get( - boxName: walletId, key: 'changeAddressesP2WPKH') as List; - final changeAddressesP2PKH = - DB.instance.get(boxName: walletId, key: 'changeAddressesP2PKH') - as List; - final changeAddressesP2SH = - DB.instance.get(boxName: walletId, key: 'changeAddressesP2SH') - as List; - - for (var i = 0; i < changeAddressesP2PKH.length; i++) { - changeAddresses.add(changeAddressesP2PKH[i] as String); - } - for (var i = 0; i < changeAddressesP2SH.length; i++) { - changeAddresses.add(changeAddressesP2SH[i] as String); - } - - final List> allTxHashes = - await _fetchHistory(allAddresses); - - final cachedTransactions = - DB.instance.get(boxName: walletId, key: 'latest_tx_model') - as TransactionData?; - int latestTxnBlockHeight = - DB.instance.get(boxName: walletId, key: "storedTxnDataHeight") - as int? ?? - 0; - - final unconfirmedCachedTransactions = - cachedTransactions?.getAllTransactions() ?? {}; - unconfirmedCachedTransactions - .removeWhere((key, value) => value.confirmedStatus); - - if (cachedTransactions != null) { - for (final tx in allTxHashes.toList(growable: false)) { - final txHeight = tx["height"] as int; - if (txHeight > 0 && - txHeight < latestTxnBlockHeight - MINIMUM_CONFIRMATIONS) { - if (unconfirmedCachedTransactions[tx["tx_hash"] as String] == null) { - allTxHashes.remove(tx); - } - } - } - } - - Set hashes = {}; - for (var element in allTxHashes) { - hashes.add(element['tx_hash'] as String); - } - await fastFetch(hashes.toList()); - List> allTransactions = []; - - for (final txHash in allTxHashes) { - final tx = await cachedElectrumXClient.getTransaction( - txHash: txHash["tx_hash"] as String, - verbose: true, - coin: coin, - ); - - // Logging.instance.log("TRANSACTION: ${jsonEncode(tx)}"); - // TODO fix this for sent to self transactions? - if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { - tx["address"] = txHash["address"]; + tx["address"] = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(txHash["address"] as String) + .findFirst(); tx["height"] = txHash["height"]; allTransactions.add(tx); } } - Logging.instance.log("addAddresses: $allAddresses", level: LogLevel.Info); - Logging.instance.log("allTxHashes: $allTxHashes", level: LogLevel.Info); + final List> txnsData = + []; - Logging.instance.log("allTransactions length: ${allTransactions.length}", - level: LogLevel.Info); - - final priceData = - await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final List> midSortedArray = []; - - Set vHashes = {}; for (final txObject in allTransactions) { - for (int i = 0; i < (txObject["vin"] as List).length; i++) { - final input = txObject["vin"]![i] as Map; - final prevTxid = input["txid"] as String; - vHashes.add(prevTxid); - } + final data = await parseTransaction( + txObject, + cachedElectrumXClient, + [address], + coin, + MINIMUM_CONFIRMATIONS, + walletId, + ); + + txnsData.add(data); } - await fastFetch(vHashes.toList()); + await db.addNewTransactionData(txnsData, walletId); + } - for (final txObject in allTransactions) { - List sendersArray = []; - List recipientsArray = []; + Future _refreshTransactions() async { + final List allAddresses = + await _fetchAllOwnAddresses(); - // Usually only has value when txType = 'Send' - int inputAmtSentFromWallet = 0; - // Usually has value regardless of txType due to change addresses - int outputAmtAddressedToWallet = 0; - int fee = 0; + final Set> allTxHashes = + (await _fetchHistory(allAddresses.map((e) => e.value).toList())) + .toSet(); - Map midSortedTx = {}; + // // prefetch/cache + // Set hashes = {}; + // for (var element in allReceivingTxHashes) { + // hashes.add(element['tx_hash'] as String); + // } + // await fastFetch(hashes.toList()); - for (int i = 0; i < (txObject["vin"] as List).length; i++) { - final input = txObject["vin"]![i] as Map; - final prevTxid = input["txid"] as String; - final prevOut = input["vout"] as int; + List> allTransactions = []; - final tx = await _cachedElectrumXClient.getTransaction( - txHash: prevTxid, + final currentHeight = await chainHeight; + + for (final txHash in allTxHashes) { + final storedTx = await db + .getTransactions(walletId) + .filter() + .txidEqualTo(txHash["tx_hash"] as String) + .findFirst(); + + // TODO: remove bip47Notification type check sometime after Q2 2023 + if (storedTx == null || + storedTx.subType == + isar_models.TransactionSubType.bip47Notification || + !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)) { + final tx = await cachedElectrumXClient.getTransaction( + txHash: txHash["tx_hash"] as String, + verbose: true, coin: coin, ); - for (final out in tx["vout"] as List) { - if (prevOut == out["n"]) { - final address = out["scriptPubKey"]["address"] as String?; - if (address != null) { - sendersArray.add(address); - } - } + if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { + tx["address"] = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(txHash["address"] as String) + .findFirst(); + tx["height"] = txHash["height"]; + allTransactions.add(tx); } } - - Logging.instance.log("sendersArray: $sendersArray", level: LogLevel.Info); - - for (final output in txObject["vout"] as List) { - final address = output["scriptPubKey"]["address"] as String?; - if (address != null) { - recipientsArray.add(address); - } - } - - Logging.instance - .log("recipientsArray: $recipientsArray", level: LogLevel.Info); - - final foundInSenders = - allAddresses.any((element) => sendersArray.contains(element)); - Logging.instance - .log("foundInSenders: $foundInSenders", level: LogLevel.Info); - - // If txType = Sent, then calculate inputAmtSentFromWallet - if (foundInSenders) { - int totalInput = 0; - for (int i = 0; i < (txObject["vin"] as List).length; i++) { - final input = txObject["vin"]![i] as Map; - final prevTxid = input["txid"] as String; - final prevOut = input["vout"] as int; - final tx = await _cachedElectrumXClient.getTransaction( - txHash: prevTxid, - coin: coin, - ); - - for (final out in tx["vout"] as List) { - if (prevOut == out["n"]) { - inputAmtSentFromWallet += - (Decimal.parse(out["value"]!.toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - } - } - } - totalInput = inputAmtSentFromWallet; - int totalOutput = 0; - - for (final output in txObject["vout"] as List) { - final String address = output["scriptPubKey"]!["address"] as String; - final value = output["value"]!; - final _value = (Decimal.parse(value.toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - totalOutput += _value; - if (changeAddresses.contains(address)) { - inputAmtSentFromWallet -= _value; - } else { - // change address from 'sent from' to the 'sent to' address - txObject["address"] = address; - } - } - // calculate transaction fee - fee = totalInput - totalOutput; - // subtract fee from sent to calculate correct value of sent tx - inputAmtSentFromWallet -= fee; - } else { - // counters for fee calculation - int totalOut = 0; - int totalIn = 0; - - // add up received tx value - for (final output in txObject["vout"] as List) { - final address = output["scriptPubKey"]["address"]; - if (address != null) { - final value = (Decimal.parse(output["value"].toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - totalOut += value; - if (allAddresses.contains(address)) { - outputAmtAddressedToWallet += value; - } - } - } - - // calculate fee for received tx - for (int i = 0; i < (txObject["vin"] as List).length; i++) { - final input = txObject["vin"][i] as Map; - final prevTxid = input["txid"] as String; - final prevOut = input["vout"] as int; - final tx = await _cachedElectrumXClient.getTransaction( - txHash: prevTxid, - coin: coin, - ); - - for (final out in tx["vout"] as List) { - if (prevOut == out["n"]) { - totalIn += (Decimal.parse(out["value"].toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - } - } - } - fee = totalIn - totalOut; - } - - // create final tx map - midSortedTx["txid"] = txObject["txid"]; - midSortedTx["confirmed_status"] = (txObject["confirmations"] != null) && - (txObject["confirmations"] as int >= MINIMUM_CONFIRMATIONS); - midSortedTx["confirmations"] = txObject["confirmations"] ?? 0; - midSortedTx["timestamp"] = txObject["blocktime"] ?? - (DateTime.now().millisecondsSinceEpoch ~/ 1000); - - if (foundInSenders) { - midSortedTx["txType"] = "Sent"; - midSortedTx["amount"] = inputAmtSentFromWallet; - final String worthNow = - ((currentPrice * Decimal.fromInt(inputAmtSentFromWallet)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2) - .toStringAsFixed(2); - midSortedTx["worthNow"] = worthNow; - midSortedTx["worthAtBlockTimestamp"] = worthNow; - } else { - midSortedTx["txType"] = "Received"; - midSortedTx["amount"] = outputAmtAddressedToWallet; - final worthNow = - ((currentPrice * Decimal.fromInt(outputAmtAddressedToWallet)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2) - .toStringAsFixed(2); - midSortedTx["worthNow"] = worthNow; - } - midSortedTx["aliens"] = []; - midSortedTx["fees"] = fee; - midSortedTx["address"] = txObject["address"]; - midSortedTx["inputSize"] = txObject["vin"].length; - midSortedTx["outputSize"] = txObject["vout"].length; - midSortedTx["inputs"] = txObject["vin"]; - midSortedTx["outputs"] = txObject["vout"]; - - final int height = txObject["height"] as int; - midSortedTx["height"] = height; - - if (height >= latestTxnBlockHeight) { - latestTxnBlockHeight = height; - } - - midSortedArray.add(midSortedTx); } - // sort by date ---- //TODO not sure if needed - // shouldn't be any issues with a null timestamp but I got one at some point? - midSortedArray - .sort((a, b) => (b["timestamp"] as int) - (a["timestamp"] as int)); - // { - // final aT = a["timestamp"]; - // final bT = b["timestamp"]; - // - // if (aT == null && bT == null) { - // return 0; - // } else if (aT == null) { - // return -1; - // } else if (bT == null) { - // return 1; - // } else { - // return bT - aT; + // // prefetch/cache + // Set vHashes = {}; + // for (final txObject in allTransactions) { + // for (int i = 0; i < (txObject["vin"] as List).length; i++) { + // final input = txObject["vin"]![i] as Map; + // final prevTxid = input["txid"] as String; + // vHashes.add(prevTxid); // } - // }); + // } + // await fastFetch(vHashes.toList()); - // buildDateTimeChunks - final Map result = {"dateTimeChunks": []}; - final dateArray = []; + final List> txnsData = + []; - for (int i = 0; i < midSortedArray.length; i++) { - final txObject = midSortedArray[i]; - final date = extractDateFromTimestamp(txObject["timestamp"] as int); - final txTimeArray = [txObject["timestamp"], date]; + for (final txObject in allTransactions) { + final data = await parseTransaction( + txObject, + cachedElectrumXClient, + allAddresses, + coin, + MINIMUM_CONFIRMATIONS, + walletId, + ); - if (dateArray.contains(txTimeArray[1])) { - result["dateTimeChunks"].forEach((dynamic chunk) { - if (extractDateFromTimestamp(chunk["timestamp"] as int) == - txTimeArray[1]) { - if (chunk["transactions"] == null) { - chunk["transactions"] = >[]; - } - chunk["transactions"].add(txObject); - } - }); - } else { - dateArray.add(txTimeArray[1]); - final chunk = { - "timestamp": txTimeArray[0], - "transactions": [txObject], - }; - result["dateTimeChunks"].add(chunk); - } + txnsData.add(data); } + await db.addNewTransactionData(txnsData, walletId); - final transactionsMap = cachedTransactions?.getAllTransactions() ?? {}; - transactionsMap - .addAll(TransactionData.fromJson(result).getAllTransactions()); - - final txModel = TransactionData.fromMap(transactionsMap); - - await DB.instance.put( - boxName: walletId, - key: 'storedTxnDataHeight', - value: latestTxnBlockHeight); - await DB.instance.put( - boxName: walletId, key: 'latest_tx_model', value: txModel); - - cachedTxData = txModel; - return txModel; + // quick hack to notify manager to call notifyListeners if + // transactions changed + if (txnsData.isNotEmpty) { + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "Transactions updated/added for: $walletId $walletName ", + walletId, + ), + ); + } } int estimateTxFee({required int vSize, required int feeRatePerKB}) { @@ -2732,35 +2181,48 @@ class BitcoinWallet extends CoinServiceAPI { /// with [satoshiAmountToSend] and [selectedTxFeeRate]. If so, it will call buildTrasaction() and return /// a map containing the tx hex along with other important information. If not, then it will return /// an integer (1 or 2) - dynamic coinSelection( - int satoshiAmountToSend, - int selectedTxFeeRate, - String _recipientAddress, - bool isSendAll, { + dynamic coinSelection({ + required int satoshiAmountToSend, + required int selectedTxFeeRate, + required String recipientAddress, + required bool coinControl, + required bool isSendAll, int additionalOutputs = 0, - List? utxos, + List? utxos, }) async { Logging.instance .log("Starting coinSelection ----------", level: LogLevel.Info); - final List availableOutputs = utxos ?? outputsList; - final List spendableOutputs = []; + final List availableOutputs = utxos ?? await this.utxos; + final currentChainHeight = await chainHeight; + final List spendableOutputs = []; int spendableSatoshiValue = 0; // Build list of spendable outputs and totaling their satoshi amount - for (var i = 0; i < availableOutputs.length; i++) { - if (availableOutputs[i].blocked == false && - availableOutputs[i].status.confirmed == true) { - spendableOutputs.add(availableOutputs[i]); - spendableSatoshiValue += availableOutputs[i].value; + for (final utxo in availableOutputs) { + if (utxo.isBlocked == false && + utxo.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) && + utxo.used != true) { + spendableOutputs.add(utxo); + spendableSatoshiValue += utxo.value; } } - // sort spendable by age (oldest first) - spendableOutputs.sort( - (a, b) => b.status.confirmations.compareTo(a.status.confirmations)); + if (coinControl) { + if (spendableOutputs.length < availableOutputs.length) { + throw ArgumentError("Attempted to use an unavailable utxo"); + } + } + + // don't care about sorting if using all utxos + if (!coinControl) { + // sort spendable by age (oldest first) + spendableOutputs.sort((a, b) => b.blockTime!.compareTo(a.blockTime!)); + } Logging.instance.log("spendableOutputs.length: ${spendableOutputs.length}", level: LogLevel.Info); + Logging.instance.log("availableOutputs.length: ${availableOutputs.length}", + level: LogLevel.Info); Logging.instance .log("spendableOutputs: $spendableOutputs", level: LogLevel.Info); Logging.instance.log("spendableSatoshiValue: $spendableSatoshiValue", @@ -2784,21 +2246,29 @@ class BitcoinWallet extends CoinServiceAPI { // Possible situation right here int satoshisBeingUsed = 0; int inputsBeingConsumed = 0; - List utxoObjectsToUse = []; + List utxoObjectsToUse = []; - for (var i = 0; - satoshisBeingUsed < satoshiAmountToSend && i < spendableOutputs.length; - i++) { - utxoObjectsToUse.add(spendableOutputs[i]); - satoshisBeingUsed += spendableOutputs[i].value; - inputsBeingConsumed += 1; - } - for (int i = 0; - i < additionalOutputs && inputsBeingConsumed < spendableOutputs.length; - i++) { - utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]); - satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value; - inputsBeingConsumed += 1; + if (!coinControl) { + for (var i = 0; + satoshisBeingUsed < satoshiAmountToSend && + i < spendableOutputs.length; + i++) { + utxoObjectsToUse.add(spendableOutputs[i]); + satoshisBeingUsed += spendableOutputs[i].value; + inputsBeingConsumed += 1; + } + for (int i = 0; + i < additionalOutputs && + inputsBeingConsumed < spendableOutputs.length; + i++) { + utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]); + satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value; + inputsBeingConsumed += 1; + } + } else { + satoshisBeingUsed = spendableSatoshiValue; + utxoObjectsToUse = spendableOutputs; + inputsBeingConsumed = spendableOutputs.length; } Logging.instance @@ -2809,7 +2279,7 @@ class BitcoinWallet extends CoinServiceAPI { .log('utxoObjectsToUse: $utxoObjectsToUse', level: LogLevel.Info); // numberOfOutputs' length must always be equal to that of recipientsArray and recipientsAmtArray - List recipientsArray = [_recipientAddress]; + List recipientsArray = [recipientAddress]; List recipientsAmtArray = [satoshiAmountToSend]; // gather required signing data @@ -2820,9 +2290,8 @@ class BitcoinWallet extends CoinServiceAPI { .log("Attempting to send all $coin", level: LogLevel.Info); final int vSizeForOneOutput = (await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, - recipients: [_recipientAddress], + recipients: [recipientAddress], satoshiAmounts: [satoshisBeingUsed - 1], ))["vSize"] as int; int feeForOneOutput = estimateTxFee( @@ -2830,15 +2299,17 @@ class BitcoinWallet extends CoinServiceAPI { feeRatePerKB: selectedTxFeeRate, ); - final int roughEstimate = - roughFeeEstimate(spendableOutputs.length, 1, selectedTxFeeRate); + final int roughEstimate = roughFeeEstimate( + spendableOutputs.length, + 1, + selectedTxFeeRate, + ).raw.toInt(); if (feeForOneOutput < roughEstimate) { feeForOneOutput = roughEstimate; } final int amount = satoshiAmountToSend - feeForOneOutput; dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: [amount], @@ -2846,31 +2317,46 @@ class BitcoinWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": amount, + "recipientAmt": Amount( + rawValue: BigInt.from(amount), + fractionDigits: coin.decimals, + ), "fee": feeForOneOutput, "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), }; return transactionObject; } - final int vSizeForOneOutput = (await buildTransaction( - utxosToUse: utxoObjectsToUse, - utxoSigningData: utxoSigningData, - recipients: [_recipientAddress], - satoshiAmounts: [satoshisBeingUsed - 1], - ))["vSize"] as int; - final int vSizeForTwoOutPuts = (await buildTransaction( - utxosToUse: utxoObjectsToUse, - utxoSigningData: utxoSigningData, - recipients: [ - _recipientAddress, - await _getCurrentAddressForChain(1, DerivePathType.bip84), - ], - satoshiAmounts: [ - satoshiAmountToSend, - satoshisBeingUsed - satoshiAmountToSend - 1 - ], // dust limit is the minimum amount a change output should be - ))["vSize"] as int; + final int vSizeForOneOutput; + try { + vSizeForOneOutput = (await buildTransaction( + utxoSigningData: utxoSigningData, + recipients: [recipientAddress], + satoshiAmounts: [satoshisBeingUsed - 1], + ))["vSize"] as int; + } catch (e) { + Logging.instance.log("vSizeForOneOutput: $e", level: LogLevel.Error); + rethrow; + } + + final int vSizeForTwoOutPuts; + try { + vSizeForTwoOutPuts = (await buildTransaction( + utxoSigningData: utxoSigningData, + recipients: [ + recipientAddress, + await currentChangeAddress, + ], + satoshiAmounts: [ + satoshiAmountToSend, + max(0, satoshisBeingUsed - satoshiAmountToSend - 1), + ], + ))["vSize"] as int; + } catch (e) { + Logging.instance.log("vSizeForTwoOutPuts: $e", level: LogLevel.Error); + rethrow; + } // Assume 1 output, only for recipient and no change final feeForOneOutput = estimateTxFee( @@ -2890,7 +2376,7 @@ class BitcoinWallet extends CoinServiceAPI { if (satoshisBeingUsed - satoshiAmountToSend > feeForOneOutput) { if (satoshisBeingUsed - satoshiAmountToSend > - feeForOneOutput + DUST_LIMIT) { + feeForOneOutput + DUST_LIMIT.raw.toInt()) { // Here, we know that theoretically, we may be able to include another output(change) but we first need to // factor in the value of this output in satoshis. int changeOutputSize = @@ -2898,13 +2384,12 @@ class BitcoinWallet extends CoinServiceAPI { // We check to see if the user can pay for the new transaction with 2 outputs instead of one. If they can and // the second output's size > DUST_LIMIT satoshis, we perform the mechanics required to properly generate and use a new // change address. - if (changeOutputSize > DUST_LIMIT && + if (changeOutputSize > DUST_LIMIT.raw.toInt() && satoshisBeingUsed - satoshiAmountToSend - changeOutputSize == feeForTwoOutputs) { // generate new change address if current change address has been used - await _checkChangeAddressForTransactions(DerivePathType.bip84); - final String newChangeAddress = - await _getCurrentAddressForChain(1, DerivePathType.bip84); + await _checkChangeAddressForTransactions(); + final String newChangeAddress = await currentChangeAddress; int feeBeingPaid = satoshisBeingUsed - satoshiAmountToSend - changeOutputSize; @@ -2926,7 +2411,6 @@ class BitcoinWallet extends CoinServiceAPI { Logging.instance .log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info); dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -2954,7 +2438,6 @@ class BitcoinWallet extends CoinServiceAPI { Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs', level: LogLevel.Info); txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -2964,9 +2447,13 @@ class BitcoinWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": feeBeingPaid, "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), }; return transactionObject; } else { @@ -2983,7 +2470,6 @@ class BitcoinWallet extends CoinServiceAPI { Logging.instance .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -2991,9 +2477,13 @@ class BitcoinWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": satoshisBeingUsed - satoshiAmountToSend, "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), }; return transactionObject; } @@ -3012,7 +2502,6 @@ class BitcoinWallet extends CoinServiceAPI { Logging.instance .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -3020,9 +2509,13 @@ class BitcoinWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": satoshisBeingUsed - satoshiAmountToSend, "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), }; return transactionObject; } @@ -3041,7 +2534,6 @@ class BitcoinWallet extends CoinServiceAPI { Logging.instance .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -3049,9 +2541,13 @@ class BitcoinWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": feeForOneOutput, "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), }; return transactionObject; } else { @@ -3063,247 +2559,182 @@ class BitcoinWallet extends CoinServiceAPI { level: LogLevel.Warning); // try adding more outputs if (spendableOutputs.length > inputsBeingConsumed) { - return coinSelection(satoshiAmountToSend, selectedTxFeeRate, - _recipientAddress, isSendAll, - additionalOutputs: additionalOutputs + 1, utxos: utxos); + return coinSelection( + satoshiAmountToSend: satoshiAmountToSend, + selectedTxFeeRate: selectedTxFeeRate, + recipientAddress: recipientAddress, + isSendAll: isSendAll, + additionalOutputs: additionalOutputs + 1, + utxos: utxos, + coinControl: coinControl, + ); } return 2; } } - Future> fetchBuildTxData( - List utxosToUse, + Future> fetchBuildTxData( + List utxosToUse, ) async { // return data - Map results = {}; - Map> addressTxid = {}; - - // addresses to check - List addressesP2PKH = []; - List addressesP2SH = []; - List addressesP2WPKH = []; + List signingData = []; try { // Populating the addresses to check for (var i = 0; i < utxosToUse.length; i++) { - final txid = utxosToUse[i].txid; - final tx = await _cachedElectrumXClient.getTransaction( - txHash: txid, - coin: coin, - ); - - for (final output in tx["vout"] as List) { - final n = output["n"]; - if (n != null && n == utxosToUse[i].vout) { - final address = output["scriptPubKey"]["address"] as String; - if (!addressTxid.containsKey(address)) { - addressTxid[address] = []; - } - (addressTxid[address] as List).add(txid); - switch (addressType(address: address)) { - case DerivePathType.bip44: - addressesP2PKH.add(address); - break; - case DerivePathType.bip49: - addressesP2SH.add(address); - break; - case DerivePathType.bip84: - addressesP2WPKH.add(address); - break; + if (utxosToUse[i].address == null) { + final txid = utxosToUse[i].txid; + final tx = await _cachedElectrumXClient.getTransaction( + txHash: txid, + coin: coin, + ); + for (final output in tx["vout"] as List) { + final n = output["n"]; + if (n != null && n == utxosToUse[i].vout) { + utxosToUse[i] = utxosToUse[i].copyWith( + address: output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]["address"] as String, + ); } } } + + final derivePathType = addressType(address: utxosToUse[i].address!); + + signingData.add( + SigningData( + derivePathType: derivePathType, + utxo: utxosToUse[i], + ), + ); } - // p2pkh / bip44 - final p2pkhLength = addressesP2PKH.length; - if (p2pkhLength > 0) { - final receiveDerivations = await _fetchDerivations( - chain: 0, - derivePathType: DerivePathType.bip44, - ); - final changeDerivations = await _fetchDerivations( - chain: 1, - derivePathType: DerivePathType.bip44, - ); - for (int i = 0; i < p2pkhLength; i++) { - // receives - final receiveDerivation = receiveDerivations[addressesP2PKH[i]]; - // if a match exists it will not be null - if (receiveDerivation != null) { - final data = P2PKH( - data: PaymentData( - pubkey: Format.stringToUint8List( - receiveDerivation["pubKey"] as String)), - network: _network, - ).data; + Map> receiveDerivations = {}; + Map> changeDerivations = {}; - for (String tx in addressTxid[addressesP2PKH[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - receiveDerivation["wif"] as String, - network: _network, + for (final sd in signingData) { + String? pubKey; + String? wif; + + final address = await db.getAddress(walletId, sd.utxo.address!); + if (address?.derivationPath != null) { + final bip32.BIP32 node; + if (address!.subType == isar_models.AddressSubType.paynymReceive) { + final code = await paymentCodeStringByKey(address.otherData!); + + final bip47base = await getBip47BaseNode(); + + final privateKey = await getPrivateKeyForPaynymReceivingAddress( + paymentCodeString: code!, + index: address.derivationIndex, + ); + + node = bip32.BIP32.fromPrivateKey( + privateKey, + bip47base.chainCode, + bip32.NetworkType( + wif: _network.wif, + bip32: bip32.Bip32Type( + public: _network.bip32.public, + private: _network.bip32.private, ), - }; - } + ), + ); } else { - // if its not a receive, check change - final changeDerivation = changeDerivations[addressesP2PKH[i]]; - // if a match exists it will not be null + node = await Bip32Utils.getBip32Node( + (await mnemonicString)!, + (await mnemonicPassphrase)!, + _network, + address.derivationPath!.value, + ); + } + + wif = node.toWIF(); + pubKey = Format.uint8listToString(node.publicKey); + } + + if (wif == null || pubKey == null) { + // fetch receiving derivations if null + receiveDerivations[sd.derivePathType] ??= await _fetchDerivations( + chain: 0, + derivePathType: sd.derivePathType, + ); + final receiveDerivation = + receiveDerivations[sd.derivePathType]![sd.utxo.address!]; + + if (receiveDerivation != null) { + pubKey = receiveDerivation["pubKey"] as String; + wif = receiveDerivation["wif"] as String; + } else { + // fetch change derivations if null + changeDerivations[sd.derivePathType] ??= await _fetchDerivations( + chain: 1, + derivePathType: sd.derivePathType, + ); + final changeDerivation = + changeDerivations[sd.derivePathType]![sd.utxo.address!]; if (changeDerivation != null) { - final data = P2PKH( + pubKey = changeDerivation["pubKey"] as String; + wif = changeDerivation["wif"] as String; + } + } + } + + if (wif != null && pubKey != null) { + final PaymentData data; + final Uint8List? redeemScript; + + switch (sd.derivePathType) { + case DerivePathType.bip44: + data = P2PKH( data: PaymentData( - pubkey: Format.stringToUint8List( - changeDerivation["pubKey"] as String)), + pubkey: Format.stringToUint8List(pubKey), + ), network: _network, ).data; + redeemScript = null; + break; - for (String tx in addressTxid[addressesP2PKH[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - changeDerivation["wif"] as String, - network: _network, - ), - }; - } - } - } - } - } - - // p2sh / bip49 - final p2shLength = addressesP2SH.length; - if (p2shLength > 0) { - final receiveDerivations = await _fetchDerivations( - chain: 0, - derivePathType: DerivePathType.bip49, - ); - final changeDerivations = await _fetchDerivations( - chain: 1, - derivePathType: DerivePathType.bip49, - ); - for (int i = 0; i < p2shLength; i++) { - // receives - final receiveDerivation = receiveDerivations[addressesP2SH[i]]; - // if a match exists it will not be null - if (receiveDerivation != null) { - final p2wpkh = P2WPKH( - data: PaymentData( - pubkey: Format.stringToUint8List( - receiveDerivation["pubKey"] as String)), - network: _network) - .data; - - final redeemScript = p2wpkh.output; - - final data = - P2SH(data: PaymentData(redeem: p2wpkh), network: _network).data; - - for (String tx in addressTxid[addressesP2SH[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - receiveDerivation["wif"] as String, - network: _network, - ), - "redeemScript": redeemScript, - }; - } - } else { - // if its not a receive, check change - final changeDerivation = changeDerivations[addressesP2SH[i]]; - // if a match exists it will not be null - if (changeDerivation != null) { + case DerivePathType.bip49: final p2wpkh = P2WPKH( - data: PaymentData( - pubkey: Format.stringToUint8List( - changeDerivation["pubKey"] as String)), - network: _network) - .data; - - final redeemScript = p2wpkh.output; - - final data = - P2SH(data: PaymentData(redeem: p2wpkh), network: _network) - .data; - - for (String tx in addressTxid[addressesP2SH[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - changeDerivation["wif"] as String, - network: _network, - ), - "redeemScript": redeemScript, - }; - } - } - } - } - } - - // p2wpkh / bip84 - final p2wpkhLength = addressesP2WPKH.length; - if (p2wpkhLength > 0) { - final receiveDerivations = await _fetchDerivations( - chain: 0, - derivePathType: DerivePathType.bip84, - ); - final changeDerivations = await _fetchDerivations( - chain: 1, - derivePathType: DerivePathType.bip84, - ); - - for (int i = 0; i < p2wpkhLength; i++) { - // receives - final receiveDerivation = receiveDerivations[addressesP2WPKH[i]]; - // if a match exists it will not be null - if (receiveDerivation != null) { - final data = P2WPKH( - data: PaymentData( - pubkey: Format.stringToUint8List( - receiveDerivation["pubKey"] as String)), - network: _network, - ).data; - - for (String tx in addressTxid[addressesP2WPKH[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - receiveDerivation["wif"] as String, - network: _network, - ), - }; - } - } else { - // if its not a receive, check change - final changeDerivation = changeDerivations[addressesP2WPKH[i]]; - // if a match exists it will not be null - if (changeDerivation != null) { - final data = P2WPKH( data: PaymentData( - pubkey: Format.stringToUint8List( - changeDerivation["pubKey"] as String)), + pubkey: Format.stringToUint8List(pubKey), + ), network: _network, ).data; + redeemScript = p2wpkh.output; + data = P2SH( + data: PaymentData(redeem: p2wpkh), + network: _network, + ).data; + break; - for (String tx in addressTxid[addressesP2WPKH[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - changeDerivation["wif"] as String, - network: _network, - ), - }; - } - } + case DerivePathType.bip84: + data = P2WPKH( + data: PaymentData( + pubkey: Format.stringToUint8List(pubKey), + ), + network: _network, + ).data; + redeemScript = null; + break; + + default: + throw Exception("DerivePathType unsupported"); } + + final keyPair = ECPair.fromWIF( + wif, + network: _network, + ); + + sd.redeemScript = redeemScript; + sd.output = data.output; + sd.keyPair = keyPair; } } - return results; + return signingData; } catch (e, s) { Logging.instance .log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error); @@ -3313,8 +2744,7 @@ class BitcoinWallet extends CoinServiceAPI { /// Builds and signs a transaction Future> buildTransaction({ - required List utxosToUse, - required Map utxoSigningData, + required List utxoSigningData, required List recipients, required List satoshiAmounts, }) async { @@ -3325,10 +2755,14 @@ class BitcoinWallet extends CoinServiceAPI { txb.setVersion(1); // Add transaction inputs - for (var i = 0; i < utxosToUse.length; i++) { - final txid = utxosToUse[i].txid; - txb.addInput(txid, utxosToUse[i].vout, null, - utxoSigningData[txid]["output"] as Uint8List); + for (var i = 0; i < utxoSigningData.length; i++) { + final txid = utxoSigningData[i].utxo.txid; + txb.addInput( + txid, + utxoSigningData[i].utxo.vout, + null, + utxoSigningData[i].output!, + ); } // Add transaction output @@ -3338,13 +2772,12 @@ class BitcoinWallet extends CoinServiceAPI { try { // Sign the transaction accordingly - for (var i = 0; i < utxosToUse.length; i++) { - final txid = utxosToUse[i].txid; + for (var i = 0; i < utxoSigningData.length; i++) { txb.sign( vin: i, - keyPair: utxoSigningData[txid]["keyPair"] as ECPair, - witnessValue: utxosToUse[i].value, - redeemScript: utxoSigningData[txid]["redeemScript"] as Uint8List?, + keyPair: utxoSigningData[i].keyPair!, + witnessValue: utxoSigningData[i].utxo.value, + redeemScript: utxoSigningData[i].redeemScript, ); } } catch (e, s) { @@ -3378,17 +2811,30 @@ class BitcoinWallet extends CoinServiceAPI { await _cachedElectrumXClient.clearSharedTransactionCache(coin: coin); // back up data - await _rescanBackup(); + // await _rescanBackup(); + + await db.deleteWalletBlockchainData(walletId); + await _deleteDerivations(); try { - final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic'); + final _mnemonic = await mnemonicString; + final _mnemonicPassphrase = await mnemonicPassphrase; + if (_mnemonicPassphrase == null) { + Logging.instance.log( + "Exception in fullRescan: mnemonic passphrase null, possible migration issue; if using internal builds, delete wallet and restore from seed, if using a release build, please file bug report", + level: LogLevel.Error); + } + await _recoverWalletFromBIP32SeedPhrase( - mnemonic: mnemonic!, + mnemonic: _mnemonic!, + mnemonicPassphrase: _mnemonicPassphrase!, maxUnusedAddressGap: maxUnusedAddressGap, maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + isRescan: true, ); longMutex = false; + await refresh(); Logging.instance.log("Full rescan complete!", level: LogLevel.Info); GlobalEventBus.instance.fire( WalletSyncStatusChangedEvent( @@ -3407,7 +2853,7 @@ class BitcoinWallet extends CoinServiceAPI { ); // restore from backup - await _rescanRestore(); + // await _rescanRestore(); longMutex = false; Logging.instance.log("Exception rethrown from fullRescan(): $e\n$s", @@ -3416,344 +2862,18 @@ class BitcoinWallet extends CoinServiceAPI { } } - Future _rescanRestore() async { - Logging.instance.log("starting rescan restore", level: LogLevel.Info); - - // restore from backup - // p2pkh - final tempReceivingAddressesP2PKH = DB.instance - .get(boxName: walletId, key: 'receivingAddressesP2PKH_BACKUP'); - final tempChangeAddressesP2PKH = DB.instance - .get(boxName: walletId, key: 'changeAddressesP2PKH_BACKUP'); - final tempReceivingIndexP2PKH = DB.instance - .get(boxName: walletId, key: 'receivingIndexP2PKH_BACKUP'); - final tempChangeIndexP2PKH = DB.instance - .get(boxName: walletId, key: 'changeIndexP2PKH_BACKUP'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2PKH', - value: tempReceivingAddressesP2PKH); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2PKH', - value: tempChangeAddressesP2PKH); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2PKH', - value: tempReceivingIndexP2PKH); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2PKH', - value: tempChangeIndexP2PKH); - await DB.instance.delete( - key: 'receivingAddressesP2PKH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'changeAddressesP2PKH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'receivingIndexP2PKH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'changeIndexP2PKH_BACKUP', boxName: walletId); - - // p2Sh - final tempReceivingAddressesP2SH = DB.instance - .get(boxName: walletId, key: 'receivingAddressesP2SH_BACKUP'); - final tempChangeAddressesP2SH = DB.instance - .get(boxName: walletId, key: 'changeAddressesP2SH_BACKUP'); - final tempReceivingIndexP2SH = DB.instance - .get(boxName: walletId, key: 'receivingIndexP2SH_BACKUP'); - final tempChangeIndexP2SH = DB.instance - .get(boxName: walletId, key: 'changeIndexP2SH_BACKUP'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2SH', - value: tempReceivingAddressesP2SH); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2SH', - value: tempChangeAddressesP2SH); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2SH', - value: tempReceivingIndexP2SH); - await DB.instance.put( - boxName: walletId, key: 'changeIndexP2SH', value: tempChangeIndexP2SH); - await DB.instance.delete( - key: 'receivingAddressesP2SH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'changeAddressesP2SH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'receivingIndexP2SH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'changeIndexP2SH_BACKUP', boxName: walletId); - - // p2wpkh - final tempReceivingAddressesP2WPKH = DB.instance.get( - boxName: walletId, key: 'receivingAddressesP2WPKH_BACKUP'); - final tempChangeAddressesP2WPKH = DB.instance - .get(boxName: walletId, key: 'changeAddressesP2WPKH_BACKUP'); - final tempReceivingIndexP2WPKH = DB.instance - .get(boxName: walletId, key: 'receivingIndexP2WPKH_BACKUP'); - final tempChangeIndexP2WPKH = DB.instance - .get(boxName: walletId, key: 'changeIndexP2WPKH_BACKUP'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2WPKH', - value: tempReceivingAddressesP2WPKH); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2WPKH', - value: tempChangeAddressesP2WPKH); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2WPKH', - value: tempReceivingIndexP2WPKH); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2WPKH', - value: tempChangeIndexP2WPKH); - await DB.instance.delete( - key: 'receivingAddressesP2WPKH_BACKUP', boxName: walletId); - await DB.instance.delete( - key: 'changeAddressesP2WPKH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'receivingIndexP2WPKH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'changeIndexP2WPKH_BACKUP', boxName: walletId); - + Future _deleteDerivations() async { // P2PKH derivations - final p2pkhReceiveDerivationsString = await _secureStore.read( - key: "${walletId}_receiveDerivationsP2PKH_BACKUP"); - final p2pkhChangeDerivationsString = await _secureStore.read( - key: "${walletId}_changeDerivationsP2PKH_BACKUP"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2PKH", - value: p2pkhReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2PKH", - value: p2pkhChangeDerivationsString); - - await _secureStore.delete( - key: "${walletId}_receiveDerivationsP2PKH_BACKUP"); - await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH_BACKUP"); - - // P2SH derivations - final p2shReceiveDerivationsString = await _secureStore.read( - key: "${walletId}_receiveDerivationsP2SH_BACKUP"); - final p2shChangeDerivationsString = await _secureStore.read( - key: "${walletId}_changeDerivationsP2SH_BACKUP"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2SH", - value: p2shReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2SH", - value: p2shChangeDerivationsString); - - await _secureStore.delete(key: "${walletId}_receiveDerivationsP2SH_BACKUP"); - await _secureStore.delete(key: "${walletId}_changeDerivationsP2SH_BACKUP"); - - // P2WPKH derivations - final p2wpkhReceiveDerivationsString = await _secureStore.read( - key: "${walletId}_receiveDerivationsP2WPKH_BACKUP"); - final p2wpkhChangeDerivationsString = await _secureStore.read( - key: "${walletId}_changeDerivationsP2WPKH_BACKUP"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2WPKH", - value: p2wpkhReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2WPKH", - value: p2wpkhChangeDerivationsString); - - await _secureStore.delete( - key: "${walletId}_receiveDerivationsP2WPKH_BACKUP"); - await _secureStore.delete( - key: "${walletId}_changeDerivationsP2WPKH_BACKUP"); - - // UTXOs - final utxoData = DB.instance - .get(boxName: walletId, key: 'latest_utxo_model_BACKUP'); - await DB.instance.put( - boxName: walletId, key: 'latest_utxo_model', value: utxoData); - await DB.instance - .delete(key: 'latest_utxo_model_BACKUP', boxName: walletId); - - Logging.instance.log("rescan restore complete", level: LogLevel.Info); - } - - Future _rescanBackup() async { - Logging.instance.log("starting rescan backup", level: LogLevel.Info); - - // backup current and clear data - // p2pkh - final tempReceivingAddressesP2PKH = DB.instance - .get(boxName: walletId, key: 'receivingAddressesP2PKH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2PKH_BACKUP', - value: tempReceivingAddressesP2PKH); - await DB.instance - .delete(key: 'receivingAddressesP2PKH', boxName: walletId); - - final tempChangeAddressesP2PKH = DB.instance - .get(boxName: walletId, key: 'changeAddressesP2PKH'); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2PKH_BACKUP', - value: tempChangeAddressesP2PKH); - await DB.instance - .delete(key: 'changeAddressesP2PKH', boxName: walletId); - - final tempReceivingIndexP2PKH = - DB.instance.get(boxName: walletId, key: 'receivingIndexP2PKH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2PKH_BACKUP', - value: tempReceivingIndexP2PKH); - await DB.instance - .delete(key: 'receivingIndexP2PKH', boxName: walletId); - - final tempChangeIndexP2PKH = - DB.instance.get(boxName: walletId, key: 'changeIndexP2PKH'); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2PKH_BACKUP', - value: tempChangeIndexP2PKH); - await DB.instance - .delete(key: 'changeIndexP2PKH', boxName: walletId); - - // p2sh - final tempReceivingAddressesP2SH = DB.instance - .get(boxName: walletId, key: 'receivingAddressesP2SH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2SH_BACKUP', - value: tempReceivingAddressesP2SH); - await DB.instance - .delete(key: 'receivingAddressesP2SH', boxName: walletId); - - final tempChangeAddressesP2SH = - DB.instance.get(boxName: walletId, key: 'changeAddressesP2SH'); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2SH_BACKUP', - value: tempChangeAddressesP2SH); - await DB.instance - .delete(key: 'changeAddressesP2SH', boxName: walletId); - - final tempReceivingIndexP2SH = - DB.instance.get(boxName: walletId, key: 'receivingIndexP2SH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2SH_BACKUP', - value: tempReceivingIndexP2SH); - await DB.instance - .delete(key: 'receivingIndexP2SH', boxName: walletId); - - final tempChangeIndexP2SH = - DB.instance.get(boxName: walletId, key: 'changeIndexP2SH'); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2SH_BACKUP', - value: tempChangeIndexP2SH); - await DB.instance - .delete(key: 'changeIndexP2SH', boxName: walletId); - - // p2wpkh - final tempReceivingAddressesP2WPKH = DB.instance - .get(boxName: walletId, key: 'receivingAddressesP2WPKH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2WPKH_BACKUP', - value: tempReceivingAddressesP2WPKH); - await DB.instance - .delete(key: 'receivingAddressesP2WPKH', boxName: walletId); - - final tempChangeAddressesP2WPKH = DB.instance - .get(boxName: walletId, key: 'changeAddressesP2WPKH'); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2WPKH_BACKUP', - value: tempChangeAddressesP2WPKH); - await DB.instance - .delete(key: 'changeAddressesP2WPKH', boxName: walletId); - - final tempReceivingIndexP2WPKH = DB.instance - .get(boxName: walletId, key: 'receivingIndexP2WPKH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2WPKH_BACKUP', - value: tempReceivingIndexP2WPKH); - await DB.instance - .delete(key: 'receivingIndexP2WPKH', boxName: walletId); - - final tempChangeIndexP2WPKH = - DB.instance.get(boxName: walletId, key: 'changeIndexP2WPKH'); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2WPKH_BACKUP', - value: tempChangeIndexP2WPKH); - await DB.instance - .delete(key: 'changeIndexP2WPKH', boxName: walletId); - - // P2PKH derivations - final p2pkhReceiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivationsP2PKH"); - final p2pkhChangeDerivationsString = - await _secureStore.read(key: "${walletId}_changeDerivationsP2PKH"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2PKH_BACKUP", - value: p2pkhReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2PKH_BACKUP", - value: p2pkhChangeDerivationsString); - await _secureStore.delete(key: "${walletId}_receiveDerivationsP2PKH"); await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH"); // P2SH derivations - final p2shReceiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivationsP2SH"); - final p2shChangeDerivationsString = - await _secureStore.read(key: "${walletId}_changeDerivationsP2SH"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2SH_BACKUP", - value: p2shReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2SH_BACKUP", - value: p2shChangeDerivationsString); - await _secureStore.delete(key: "${walletId}_receiveDerivationsP2SH"); await _secureStore.delete(key: "${walletId}_changeDerivationsP2SH"); // P2WPKH derivations - final p2wpkhReceiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivationsP2WPKH"); - final p2wpkhChangeDerivationsString = - await _secureStore.read(key: "${walletId}_changeDerivationsP2WPKH"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2WPKH_BACKUP", - value: p2wpkhReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2WPKH_BACKUP", - value: p2wpkhChangeDerivationsString); - await _secureStore.delete(key: "${walletId}_receiveDerivationsP2WPKH"); await _secureStore.delete(key: "${walletId}_changeDerivationsP2WPKH"); - - // UTXOs - final utxoData = - DB.instance.get(boxName: walletId, key: 'latest_utxo_model'); - await DB.instance.put( - boxName: walletId, key: 'latest_utxo_model_BACKUP', value: utxoData); - await DB.instance - .delete(key: 'latest_utxo_model', boxName: walletId); - - Logging.instance.log("rescan backup complete", level: LogLevel.Info); } bool isActive = false; @@ -3763,58 +2883,70 @@ class BitcoinWallet extends CoinServiceAPI { (isActive) => this.isActive = isActive; @override - Future estimateFeeFor(int satoshiAmount, int feeRate) async { - final available = - Format.decimalAmountToSatoshis(await availableBalance, coin); + Future estimateFeeFor(Amount amount, int feeRate) async { + final available = balance.spendable; - if (available == satoshiAmount) { - return satoshiAmount - sweepAllEstimate(feeRate); - } else if (satoshiAmount <= 0 || satoshiAmount > available) { + if (available == amount) { + return amount - (await sweepAllEstimate(feeRate)); + } else if (amount <= Amount.zero || amount > available) { return roughFeeEstimate(1, 2, feeRate); } - int runningBalance = 0; + Amount runningBalance = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); int inputCount = 0; - for (final output in outputsList) { - runningBalance += output.value; - inputCount++; - if (runningBalance > satoshiAmount) { - break; + for (final output in (await utxos)) { + if (!output.isBlocked) { + runningBalance += Amount( + rawValue: BigInt.from(output.value), + fractionDigits: coin.decimals, + ); + inputCount++; + if (runningBalance > amount) { + break; + } } } final oneOutPutFee = roughFeeEstimate(inputCount, 1, feeRate); final twoOutPutFee = roughFeeEstimate(inputCount, 2, feeRate); - if (runningBalance - satoshiAmount > oneOutPutFee) { - if (runningBalance - satoshiAmount > oneOutPutFee + DUST_LIMIT) { - final change = runningBalance - satoshiAmount - twoOutPutFee; + if (runningBalance - amount > oneOutPutFee) { + if (runningBalance - amount > oneOutPutFee + DUST_LIMIT) { + final change = runningBalance - amount - twoOutPutFee; if (change > DUST_LIMIT && - runningBalance - satoshiAmount - change == twoOutPutFee) { - return runningBalance - satoshiAmount - change; + runningBalance - amount - change == twoOutPutFee) { + return runningBalance - amount - change; } else { - return runningBalance - satoshiAmount; + return runningBalance - amount; } } else { - return runningBalance - satoshiAmount; + return runningBalance - amount; } - } else if (runningBalance - satoshiAmount == oneOutPutFee) { + } else if (runningBalance - amount == oneOutPutFee) { return oneOutPutFee; } else { return twoOutPutFee; } } - int roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { - return ((42 + (272 * inputCount) + (128 * outputCount)) / 4).ceil() * - (feeRatePerKB / 1000).ceil(); + Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { + return Amount( + rawValue: BigInt.from( + ((42 + (272 * inputCount) + (128 * outputCount)) / 4).ceil() * + (feeRatePerKB / 1000).ceil()), + fractionDigits: coin.decimals, + ); } - int sweepAllEstimate(int feeRate) { + Future sweepAllEstimate(int feeRate) async { int available = 0; int inputCount = 0; - for (final output in outputsList) { - if (output.status.confirmed) { + for (final output in (await utxos)) { + if (!output.isBlocked && + output.isConfirmed(storedChainHeight, MINIMUM_CONFIRMATIONS)) { available += output.value; inputCount++; } @@ -3823,29 +2955,26 @@ class BitcoinWallet extends CoinServiceAPI { // transaction will only have 1 output minus the fee final estimatedFee = roughFeeEstimate(inputCount, 1, feeRate); - return available - estimatedFee; + return Amount( + rawValue: BigInt.from(available), + fractionDigits: coin.decimals, + ) - + estimatedFee; } @override Future generateNewAddress() async { try { - await _incrementAddressIndexForChain( - 0, DerivePathType.bip84); // First increment the receiving index - final newReceivingIndex = DB.instance.get( - boxName: walletId, - key: 'receivingIndexP2WPKH') as int; // Check the new receiving index + final currentReceiving = await _currentReceivingAddress; + + final newReceivingIndex = currentReceiving.derivationIndex + 1; + + // Use new index to derive a new receiving address final newReceivingAddress = await _generateAddressForChain( - 0, - newReceivingIndex, - DerivePathType - .bip84); // Use new index to derive a new receiving address - await _addToAddressesArrayForChain( - newReceivingAddress, - 0, - DerivePathType - .bip84); // Add that new receiving address to the array of receiving addresses - _currentReceivingAddress = Future(() => - newReceivingAddress); // Set the new receiving address that the service + 0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin)); + + // Add that new receiving address + await db.putAddress(newReceivingAddress); return true; } catch (e, s) { @@ -3855,4 +2984,15 @@ class BitcoinWallet extends CoinServiceAPI { return false; } } + + @override + Future get xpub async { + final node = await Bip32Utils.getBip32Root( + (await mnemonic).join(" "), + await mnemonicPassphrase ?? "", + _network, + ); + + return node.neutered().toBase58(); + } } diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index d84bce65c..e5117d08b 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'dart:typed_data'; +import 'dart:math'; import 'package:bech32/bech32.dart'; import 'package:bip32/bip32.dart' as bip32; @@ -9,132 +9,139 @@ import 'package:bip39/bip39.dart' as bip39; import 'package:bitbox/bitbox.dart' as bitbox; import 'package:bitcoindart/bitcoindart.dart'; import 'package:bs58check/bs58check.dart' as bs58check; -import 'package:crypto/crypto.dart'; import 'package:decimal/decimal.dart'; -import 'package:devicelocale/devicelocale.dart'; import 'package:flutter/foundation.dart'; -import 'package:http/http.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; -import 'package:stackwallet/hive/db.dart'; -import 'package:stackwallet/models/models.dart' as models; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/address.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; import 'package:stackwallet/models/paymint/fee_object_model.dart'; -import 'package:stackwallet/models/paymint/transactions_model.dart'; -import 'package:stackwallet/models/paymint/utxo_model.dart'; +import 'package:stackwallet/models/signing_data.dart'; import 'package:stackwallet/services/coins/coin_service.dart'; import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +import 'package:stackwallet/services/mixins/coin_control_interface.dart'; +import 'package:stackwallet/services/mixins/wallet_cache.dart'; +import 'package:stackwallet/services/mixins/wallet_db.dart'; +import 'package:stackwallet/services/mixins/xpubable.dart'; import 'package:stackwallet/services/node_service.dart'; -import 'package:stackwallet/services/notifications_api.dart'; -import 'package:stackwallet/services/price.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; -import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/address_utils.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/bip32_utils.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/default_nodes.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart'; import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; +import 'package:stackwallet/widgets/crypto_notifications.dart'; import 'package:tuple/tuple.dart'; import 'package:uuid/uuid.dart'; -const int MINIMUM_CONFIRMATIONS = 1; -const int DUST_LIMIT = 546; +const int MINIMUM_CONFIRMATIONS = 0; +final Amount DUST_LIMIT = Amount( + rawValue: BigInt.from(546), + fractionDigits: Coin.particl.decimals, +); const String GENESIS_HASH_MAINNET = "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; const String GENESIS_HASH_TESTNET = "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"; -enum DerivePathType { bip44, bip49 } - -bip32.BIP32 getBip32Node(int chain, int index, String mnemonic, - NetworkType network, DerivePathType derivePathType) { - final root = getBip32Root(mnemonic, network); - - final node = getBip32NodeFromRoot(chain, index, root, derivePathType); - return node; -} - -/// wrapper for compute() -bip32.BIP32 getBip32NodeWrapper( - Tuple5 args, -) { - return getBip32Node( - args.item1, - args.item2, - args.item3, - args.item4, - args.item5, - ); -} - -bip32.BIP32 getBip32NodeFromRoot( - int chain, int index, bip32.BIP32 root, DerivePathType derivePathType) { - String coinType; - switch (root.network.wif) { +String constructDerivePath({ + required DerivePathType derivePathType, + required int networkWIF, + int account = 0, + required int chain, + required int index, +}) { + int coinType; + switch (networkWIF) { case 0x80: // bch mainnet wif - coinType = "145"; // bch mainnet + switch (derivePathType) { + case DerivePathType.bip44: + coinType = 145; // bch mainnet + break; + case DerivePathType.bch44: // bitcoin.com wallet specific + coinType = 0; // bch mainnet + break; + default: + throw Exception( + "DerivePathType $derivePathType not supported for coinType"); + } break; case 0xef: // bch testnet wif - coinType = "1"; // bch testnet + coinType = 1; // bch testnet break; default: - throw Exception("Invalid Bitcoincash network type used!"); + throw Exception("Invalid Bitcoincash network wif used!"); } + + int purpose; switch (derivePathType) { case DerivePathType.bip44: - return root.derivePath("m/44'/$coinType'/0'/$chain/$index"); - case DerivePathType.bip49: - return root.derivePath("m/49'/$coinType'/0'/$chain/$index"); + case DerivePathType.bch44: + purpose = 44; + break; default: - throw Exception("DerivePathType must not be null."); + throw Exception("DerivePathType $derivePathType not supported"); } + + return "m/$purpose'/$coinType'/$account'/$chain/$index"; } -/// wrapper for compute() -bip32.BIP32 getBip32NodeFromRootWrapper( - Tuple4 args, -) { - return getBip32NodeFromRoot( - args.item1, - args.item2, - args.item3, - args.item4, - ); -} +class BitcoinCashWallet extends CoinServiceAPI + with WalletCache, WalletDB, CoinControlInterface + implements XPubAble { + BitcoinCashWallet({ + required String walletId, + required String walletName, + required Coin coin, + required ElectrumX client, + required CachedElectrumX cachedClient, + required TransactionNotificationTracker tracker, + required SecureStorageInterface secureStore, + MainDB? mockableOverride, + }) { + txTracker = tracker; + _walletId = walletId; + _walletName = walletName; + _coin = coin; + _electrumXClient = client; + _cachedElectrumXClient = cachedClient; + _secureStore = secureStore; + initCache(walletId, coin); + initWalletDB(mockableOverride: mockableOverride); + initCoinControlInterface( + walletId: walletId, + walletName: walletName, + coin: coin, + db: db, + getChainHeight: () => chainHeight, + refreshedBalanceCallback: (balance) async { + _balance = balance; + await updateCachedBalance(_balance!); + }, + ); + } -bip32.BIP32 getBip32Root(String mnemonic, NetworkType network) { - final seed = bip39.mnemonicToSeed(mnemonic); - final networkType = bip32.NetworkType( - wif: network.wif, - bip32: bip32.Bip32Type( - public: network.bip32.public, - private: network.bip32.private, - ), - ); - - final root = bip32.BIP32.fromSeed(seed, networkType); - return root; -} - -/// wrapper for compute() -bip32.BIP32 getBip32RootWrapper(Tuple2 args) { - return getBip32Root(args.item1, args.item2); -} - -class BitcoinCashWallet extends CoinServiceAPI { static const integrationTestFlag = bool.fromEnvironment("IS_INTEGRATION_TEST"); final _prefs = Prefs.instance; Timer? timer; - late Coin _coin; + late final Coin _coin; late final TransactionNotificationTracker txTracker; @@ -149,70 +156,44 @@ class BitcoinCashWallet extends CoinServiceAPI { } } - List outputsList = []; + @override + Future> get utxos => db.getUTXOs(walletId).findAll(); + + @override + Future> get transactions => + db.getTransactions(walletId).sortByTimestampDesc().findAll(); @override Coin get coin => _coin; @override - Future> get allOwnAddresses => - _allOwnAddresses ??= _fetchAllOwnAddresses(); - Future>? _allOwnAddresses; + Future get currentReceivingAddress async => + (await _currentReceivingAddress).value; - Future? _utxoData; - Future get utxoData => _utxoData ??= _fetchUtxoData(); + Future get _currentReceivingAddress async => + (await db + .getAddresses(walletId) + .filter() + .typeEqualTo(isar_models.AddressType.p2pkh) + .subTypeEqualTo(isar_models.AddressSubType.receiving) + .derivationPath((q) => q.not().valueStartsWith("m/44'/0'")) + .sortByDerivationIndexDesc() + .findFirst()) ?? + await _generateAddressForChain(0, 0, DerivePathTypeExt.primaryFor(coin)); - @override - Future> get unspentOutputs async => - (await utxoData).unspentOutputArray; + Future get currentChangeAddress async => + (await _currentChangeAddress).value; - @override - Future get availableBalance async { - final data = await utxoData; - return Format.satoshisToAmount( - data.satoshiBalance - data.satoshiBalanceUnconfirmed, - coin: coin); - } - - @override - Future get pendingBalance async { - final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalanceUnconfirmed, coin: coin); - } - - @override - Future get balanceMinusMaxFee async => - (await availableBalance) - - (Decimal.fromInt((await maxFee)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(); - - @override - Future get totalBalance async { - if (!isActive) { - final totalBalance = DB.instance - .get(boxName: walletId, key: 'totalBalance') as int?; - if (totalBalance == null) { - final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalance, coin: coin); - } else { - return Format.satoshisToAmount(totalBalance, coin: coin); - } - } - final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalance, coin: coin); - } - - @override - Future get currentReceivingAddress => - _currentReceivingAddressP2PKH ??= - _getCurrentAddressForChain(0, DerivePathType.bip44); - Future? _currentReceivingAddressP2PKH; - - // Future get currentReceivingAddressP2SH => - // _currentReceivingAddressP2SH ??= - // _getCurrentAddressForChain(0, DerivePathType.bip49); - Future? _currentReceivingAddressP2SH; + Future get _currentChangeAddress async => + (await db + .getAddresses(walletId) + .filter() + .typeEqualTo(isar_models.AddressType.p2pkh) + .subTypeEqualTo(isar_models.AddressSubType.change) + .derivationPath((q) => q.not().valueStartsWith("m/44'/0'")) + .sortByDerivationIndexDesc() + .findFirst()) ?? + await _generateAddressForChain(1, 0, DerivePathTypeExt.primaryFor(coin)); @override Future exit() async { @@ -233,36 +214,44 @@ class BitcoinCashWallet extends CoinServiceAPI { @override Future get maxFee async { - final fee = (await fees).fast; - final satsFee = Format.satoshisToAmount(fee, coin: coin) * - Decimal.fromInt(Constants.satsPerCoin(coin)); - return satsFee.floor().toBigInt().toInt(); + throw UnimplementedError("Not used in bch"); } @override Future> get mnemonic => _getMnemonicList(); + @override + Future get mnemonicString => + _secureStore.read(key: '${_walletId}_mnemonic'); + + @override + Future get mnemonicPassphrase => _secureStore.read( + key: '${_walletId}_mnemonicPassphrase', + ); + Future get chainHeight async { try { final result = await _electrumXClient.getBlockHeadTip(); - return result["height"] as int; + final height = result["height"] as int; + await updateCachedChainHeight(height); + if (height > storedChainHeight) { + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "Updated current chain height in $walletId $walletName!", + walletId, + ), + ); + } + return height; } catch (e, s) { Logging.instance.log("Exception caught in chainHeight: $e\n$s", level: LogLevel.Error); - return -1; + return storedChainHeight; } } - Future get storedChainHeight async { - final storedHeight = DB.instance - .get(boxName: walletId, key: "storedChainHeight") as int?; - return storedHeight ?? 0; - } - - Future updateStoredChainHeight({required int newHeight}) async { - await DB.instance.put( - boxName: walletId, key: "storedChainHeight", value: newHeight); - } + @override + int get storedChainHeight => getCachedChainHeight(); DerivePathType addressType({required String address}) { Uint8List? decodeBase58; @@ -276,7 +265,9 @@ class BitcoinCashWallet extends CoinServiceAPI { throw ArgumentError('$address is not currently supported'); } } - } catch (e, s) {} + } catch (_) { + // invalid cash addr format + } try { decodeBase58 = bs58check.decode(address); } catch (err) { @@ -288,10 +279,6 @@ class BitcoinCashWallet extends CoinServiceAPI { return DerivePathType.bip44; } - if (decodeBase58[0] == _network.scriptHash) { - // P2SH - return DerivePathType.bip49; - } throw ArgumentError('Invalid version or Network mismatch'); } else { try { @@ -317,6 +304,7 @@ class BitcoinCashWallet extends CoinServiceAPI { @override Future recoverFromMnemonic({ required String mnemonic, + String? mnemonicPassphrase, required int maxUnusedAddressGap, required int maxNumberOfIndexesToCheck, required int height, @@ -347,16 +335,24 @@ class BitcoinCashWallet extends CoinServiceAPI { } // check to make sure we aren't overwriting a mnemonic // this should never fail - if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) { + if ((await mnemonicString) != null || + (await this.mnemonicPassphrase) != null) { longMutex = false; throw Exception("Attempted to overwrite mnemonic on restore!"); } await _secureStore.write( key: '${_walletId}_mnemonic', value: mnemonic.trim()); + await _secureStore.write( + key: '${_walletId}_mnemonicPassphrase', + value: mnemonicPassphrase ?? "", + ); + await _recoverWalletFromBIP32SeedPhrase( mnemonic: mnemonic.trim(), + mnemonicPassphrase: mnemonicPassphrase ?? "", maxUnusedAddressGap: maxUnusedAddressGap, maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + coin: coin, ); } catch (e, s) { Logging.instance.log( @@ -373,247 +369,68 @@ class BitcoinCashWallet extends CoinServiceAPI { level: LogLevel.Info); } - Future _recoverWalletFromBIP32SeedPhrase({ - required String mnemonic, - int maxUnusedAddressGap = 20, - int maxNumberOfIndexesToCheck = 1000, - }) async { - longMutex = true; - - Map> p2pkhReceiveDerivations = {}; - Map> p2shReceiveDerivations = {}; - Map> p2pkhChangeDerivations = {}; - Map> p2shChangeDerivations = {}; - - final root = await compute(getBip32RootWrapper, Tuple2(mnemonic, _network)); - - List p2pkhReceiveAddressArray = []; - List p2shReceiveAddressArray = []; - int p2pkhReceiveIndex = -1; - int p2shReceiveIndex = -1; - - List p2pkhChangeAddressArray = []; - List p2shChangeAddressArray = []; - int p2pkhChangeIndex = -1; - int p2shChangeIndex = -1; - - // The gap limit will be capped at [maxUnusedAddressGap] - // int receivingGapCounter = 0; - // int changeGapCounter = 0; - - // actual size is 24 due to p2pkh and p2sh so 12x2 - const txCountBatchSize = 12; - - try { - // receiving addresses - Logging.instance - .log("checking receiving addresses...", level: LogLevel.Info); - final resultReceive44 = _checkGaps(maxNumberOfIndexesToCheck, - maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 0); - - final resultReceive49 = _checkGaps(maxNumberOfIndexesToCheck, - maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip49, 0); - - Logging.instance - .log("checking change addresses...", level: LogLevel.Info); - // change addresses - final resultChange44 = _checkGaps(maxNumberOfIndexesToCheck, - maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip44, 1); - - final resultChange49 = _checkGaps(maxNumberOfIndexesToCheck, - maxUnusedAddressGap, txCountBatchSize, root, DerivePathType.bip49, 1); - - await Future.wait( - [resultReceive44, resultReceive49, resultChange44, resultChange49]); - - p2pkhReceiveAddressArray = - (await resultReceive44)['addressArray'] as List; - p2pkhReceiveIndex = (await resultReceive44)['index'] as int; - p2pkhReceiveDerivations = (await resultReceive44)['derivations'] - as Map>; - - p2shReceiveAddressArray = - (await resultReceive49)['addressArray'] as List; - p2shReceiveIndex = (await resultReceive49)['index'] as int; - p2shReceiveDerivations = (await resultReceive49)['derivations'] - as Map>; - - p2pkhChangeAddressArray = - (await resultChange44)['addressArray'] as List; - p2pkhChangeIndex = (await resultChange44)['index'] as int; - p2pkhChangeDerivations = (await resultChange44)['derivations'] - as Map>; - - p2shChangeAddressArray = - (await resultChange49)['addressArray'] as List; - p2shChangeIndex = (await resultChange49)['index'] as int; - p2shChangeDerivations = (await resultChange49)['derivations'] - as Map>; - - // save the derivations (if any) - if (p2pkhReceiveDerivations.isNotEmpty) { - await addDerivations( - chain: 0, - derivePathType: DerivePathType.bip44, - derivationsToAdd: p2pkhReceiveDerivations); - } - if (p2shReceiveDerivations.isNotEmpty) { - await addDerivations( - chain: 0, - derivePathType: DerivePathType.bip49, - derivationsToAdd: p2shReceiveDerivations); - } - if (p2pkhChangeDerivations.isNotEmpty) { - await addDerivations( - chain: 1, - derivePathType: DerivePathType.bip44, - derivationsToAdd: p2pkhChangeDerivations); - } - if (p2shChangeDerivations.isNotEmpty) { - await addDerivations( - chain: 1, - derivePathType: DerivePathType.bip49, - derivationsToAdd: p2shChangeDerivations); - } - - // If restoring a wallet that never received any funds, then set receivingArray manually - // If we didn't do this, it'd store an empty array - if (p2pkhReceiveIndex == -1) { - final address = - await _generateAddressForChain(0, 0, DerivePathType.bip44); - p2pkhReceiveAddressArray.add(address); - p2pkhReceiveIndex = 0; - } - if (p2shReceiveIndex == -1) { - final address = - await _generateAddressForChain(0, 0, DerivePathType.bip49); - p2shReceiveAddressArray.add(address); - p2shReceiveIndex = 0; - } - - // If restoring a wallet that never sent any funds with change, then set changeArray - // manually. If we didn't do this, it'd store an empty array. - if (p2pkhChangeIndex == -1) { - final address = - await _generateAddressForChain(1, 0, DerivePathType.bip44); - p2pkhChangeAddressArray.add(address); - p2pkhChangeIndex = 0; - } - if (p2shChangeIndex == -1) { - final address = - await _generateAddressForChain(1, 0, DerivePathType.bip49); - p2shChangeAddressArray.add(address); - p2shChangeIndex = 0; - } - - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2PKH', - value: p2pkhReceiveAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2PKH', - value: p2pkhChangeAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2SH', - value: p2shReceiveAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2SH', - value: p2shChangeAddressArray); - await DB.instance.put( - boxName: walletId, key: 'changeIndexP2PKH', value: p2pkhChangeIndex); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2PKH', - value: p2pkhReceiveIndex); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2SH', - value: p2shReceiveIndex); - await DB.instance.put( - boxName: walletId, key: 'changeIndexP2SH', value: p2shChangeIndex); - await DB.instance - .put(boxName: walletId, key: "id", value: _walletId); - await DB.instance - .put(boxName: walletId, key: "isFavorite", value: false); - - longMutex = false; - } catch (e, s) { - Logging.instance.log( - "Exception rethrown from _recoverWalletFromBIP32SeedPhrase(): $e\n$s", - level: LogLevel.Info); - - longMutex = false; - rethrow; - } - } - - Future> _checkGaps( - int maxNumberOfIndexesToCheck, - int maxUnusedAddressGap, - int txCountBatchSize, - bip32.BIP32 root, - DerivePathType type, - int account) async { - List addressArray = []; - int returningIndex = -1; - Map> derivations = {}; + Future, DerivePathType, int>> _checkGaps( + int maxNumberOfIndexesToCheck, + int maxUnusedAddressGap, + int txCountBatchSize, + bip32.BIP32 root, + DerivePathType type, + int chain, + ) async { + List addressArray = []; int gapCounter = 0; + int highestIndexWithHistory = 0; + for (int index = 0; index < maxNumberOfIndexesToCheck && gapCounter < maxUnusedAddressGap; index += txCountBatchSize) { List iterationsAddressArray = []; Logging.instance.log( - "index: $index, \t GapCounter $account ${type.name}: $gapCounter", + "index: $index, \t GapCounter $chain ${type.name}: $gapCounter", level: LogLevel.Info); final _id = "k_$index"; Map txCountCallArgs = {}; - final Map receivingNodes = {}; for (int j = 0; j < txCountBatchSize; j++) { - final node = await compute( - getBip32NodeFromRootWrapper, - Tuple4( - account, - index + j, - root, - type, - ), + final derivePath = constructDerivePath( + derivePathType: type, + networkWIF: root.network.wif, + chain: chain, + index: index + j, ); - String? address; + final node = await Bip32Utils.getBip32NodeFromRoot(root, derivePath); + + String addressString; + final data = PaymentData(pubkey: node.publicKey); + isar_models.AddressType addrType; switch (type) { case DerivePathType.bip44: - address = P2PKH( - data: PaymentData(pubkey: node.publicKey), - network: _network) - .data - .address!; - break; - case DerivePathType.bip49: - address = P2SH( - data: PaymentData( - redeem: P2WPKH( - data: PaymentData(pubkey: node.publicKey), - network: _network) - .data), - network: _network) - .data - .address!; + case DerivePathType.bch44: + addressString = P2PKH(data: data, network: _network).data.address!; + addrType = isar_models.AddressType.p2pkh; + addressString = bitbox.Address.toCashAddress(addressString); break; default: - throw Exception("No Path type $type exists"); + throw Exception("DerivePathType $type not supported"); } - receivingNodes.addAll({ - "${_id}_$j": { - "node": node, - "address": address, - } - }); + + final address = isar_models.Address( + walletId: walletId, + value: addressString, + publicKey: node.publicKey, + type: addrType, + derivationIndex: index + j, + derivationPath: isar_models.DerivationPath()..value = derivePath, + subType: chain == 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change, + ); + + addressArray.add(address); + txCountCallArgs.addAll({ - "${_id}_$j": address, + "${_id}_$j": addressString, }); } @@ -626,20 +443,13 @@ class BitcoinCashWallet extends CoinServiceAPI { for (int k = 0; k < txCountBatchSize; k++) { int count = counts["${_id}_$k"]!; if (count > 0) { - final node = receivingNodes["${_id}_$k"]; - // add address to array - addressArray.add(node["address"] as String); - iterationsAddressArray.add(node["address"] as String); - // set current index - returningIndex = index + k; + iterationsAddressArray.add(txCountCallArgs["${_id}_$k"]!); + + // update highest + highestIndexWithHistory = index + k; + // reset counter gapCounter = 0; - // add info to derivations - derivations[node["address"] as String] = { - "pubKey": Format.uint8listToString( - (node["node"] as bip32.BIP32).publicKey), - "wif": (node["node"] as bip32.BIP32).toWIF(), - }; } // increase counter when no tx history found @@ -650,11 +460,7 @@ class BitcoinCashWallet extends CoinServiceAPI { // cache all the transactions while waiting for the current function to finish. unawaited(getTransactionCacheEarly(iterationsAddressArray)); } - return { - "addressArray": addressArray, - "index": returningIndex, - "derivations": derivations - }; + return Tuple3(addressArray, type, highestIndexWithHistory); } Future getTransactionCacheEarly(List allAddresses) async { @@ -677,6 +483,166 @@ class BitcoinCashWallet extends CoinServiceAPI { } } + Future _recoverWalletFromBIP32SeedPhrase({ + required String mnemonic, + required String mnemonicPassphrase, + int maxUnusedAddressGap = 20, + int maxNumberOfIndexesToCheck = 1000, + bool isRescan = false, + Coin? coin, + }) async { + longMutex = true; + + final root = await Bip32Utils.getBip32Root( + mnemonic, + mnemonicPassphrase, + _network, + ); + + final deriveTypes = [ + DerivePathType.bip44, + ]; + + if (coin != Coin.bitcoincashTestnet) { + deriveTypes.add(DerivePathType.bch44); + } + + final List, DerivePathType, int>>> + receiveFutures = []; + final List, DerivePathType, int>>> + changeFutures = []; + + const receiveChain = 0; + const changeChain = 1; + const indexZero = 0; + + // actual size is 24 due to p2pkh and p2sh so 12x2 + const txCountBatchSize = 12; + + try { + // receiving addresses + Logging.instance.log( + "checking receiving addresses...", + level: LogLevel.Info, + ); + + for (final type in deriveTypes) { + receiveFutures.add( + _checkGaps( + maxNumberOfIndexesToCheck, + maxUnusedAddressGap, + txCountBatchSize, + root, + type, + receiveChain, + ), + ); + } + + // change addresses + Logging.instance.log( + "checking change addresses...", + level: LogLevel.Info, + ); + for (final type in deriveTypes) { + changeFutures.add( + _checkGaps( + maxNumberOfIndexesToCheck, + maxUnusedAddressGap, + txCountBatchSize, + root, + type, + changeChain, + ), + ); + } + + // io limitations may require running these linearly instead + final futuresResult = await Future.wait([ + Future.wait(receiveFutures), + Future.wait(changeFutures), + ]); + + final receiveResults = futuresResult[0]; + final changeResults = futuresResult[1]; + + final List addressesToStore = []; + + int highestReceivingIndexWithHistory = 0; + // If restoring a wallet that never received any funds, then set receivingArray manually + // If we didn't do this, it'd store an empty array + for (final tuple in receiveResults) { + if (tuple.item1.isEmpty) { + final address = await _generateAddressForChain( + receiveChain, + indexZero, + tuple.item2, + ); + addressesToStore.add(address); + } else { + highestReceivingIndexWithHistory = max( + tuple.item3, + highestReceivingIndexWithHistory, + ); + addressesToStore.addAll(tuple.item1); + } + } + + int highestChangeIndexWithHistory = 0; + // If restoring a wallet that never sent any funds with change, then set changeArray + // manually. If we didn't do this, it'd store an empty array. + for (final tuple in changeResults) { + if (tuple.item1.isEmpty) { + final address = await _generateAddressForChain( + changeChain, + indexZero, + tuple.item2, + ); + addressesToStore.add(address); + } else { + highestChangeIndexWithHistory = max( + tuple.item3, + highestChangeIndexWithHistory, + ); + addressesToStore.addAll(tuple.item1); + } + } + + // remove extra addresses to help minimize risk of creating a large gap + addressesToStore.removeWhere((e) => + e.subType == isar_models.AddressSubType.change && + e.derivationIndex > highestChangeIndexWithHistory); + addressesToStore.removeWhere((e) => + e.subType == isar_models.AddressSubType.receiving && + e.derivationIndex > highestReceivingIndexWithHistory); + + if (isRescan) { + await db.updateOrPutAddresses(addressesToStore); + } else { + await db.putAddresses(addressesToStore); + } + + await Future.wait([ + _refreshTransactions(), + _updateUTXOs(), + ]); + + await Future.wait([ + updateCachedId(walletId), + updateCachedIsFavorite(false), + ]); + + longMutex = false; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _recoverWalletFromBIP32SeedPhrase(): $e\n$s", + level: LogLevel.Info); + + longMutex = false; + rethrow; + } + } + Future refreshIfThereIsNewData() async { if (longMutex) return false; if (_hasCalledExit) return false; @@ -708,11 +674,15 @@ class BitcoinCashWallet extends CoinServiceAPI { } if (!needsRefresh) { var allOwnAddresses = await _fetchAllOwnAddresses(); - List> allTxs = - await _fetchHistory(allOwnAddresses); - final txData = await transactionData; + List> allTxs = await _fetchHistory( + allOwnAddresses.map((e) => e.value).toList(growable: false)); for (Map transaction in allTxs) { - if (txData.findTransaction(transaction['tx_hash'] as String) == + final txid = transaction['tx_hash'] as String; + if ((await db + .getTransactions(walletId) + .filter() + .txidMatches(txid) + .findFirst()) == null) { Logging.instance.log( " txid not found in address history already ${transaction['tx_hash']}", @@ -731,17 +701,25 @@ class BitcoinCashWallet extends CoinServiceAPI { } } - Future getAllTxsToWatch( - TransactionData txData, - ) async { + Future getAllTxsToWatch() async { if (_hasCalledExit) return; - List unconfirmedTxnsToNotifyPending = []; - List unconfirmedTxnsToNotifyConfirmed = []; + List unconfirmedTxnsToNotifyPending = []; + List unconfirmedTxnsToNotifyConfirmed = []; - // Get all unconfirmed incoming transactions - for (final chunk in txData.txChunks) { - for (final tx in chunk.transactions) { - if (tx.confirmedStatus) { + final currentChainHeight = await chainHeight; + + final txCount = await db.getTransactions(walletId).count(); + + const paginateLimit = 50; + + for (int i = 0; i < txCount; i += paginateLimit) { + final transactions = await db + .getTransactions(walletId) + .offset(i) + .limit(paginateLimit) + .findAll(); + for (final tx in transactions) { + if (tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { if (txTracker.wasNotifiedPending(tx.txid) && !txTracker.wasNotifiedConfirmed(tx.txid)) { unconfirmedTxnsToNotifyConfirmed.add(tx); @@ -756,69 +734,74 @@ class BitcoinCashWallet extends CoinServiceAPI { // notify on new incoming transaction for (final tx in unconfirmedTxnsToNotifyPending) { - if (tx.txType == "Received") { - unawaited( - NotificationApi.showNotification( + final confirmations = tx.getConfirmations(currentChainHeight); + + if (tx.type == isar_models.TransactionType.incoming) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( title: "Incoming transaction", - body: walletName, walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), date: DateTime.now(), - shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS, - coinName: coin.name, + shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, txid: tx.txid, - confirmations: tx.confirmations, + confirmations: confirmations, requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, ), ); + await txTracker.addNotifiedPending(tx.txid); - } else if (tx.txType == "Sent") { - unawaited( - NotificationApi.showNotification( + } else if (tx.type == isar_models.TransactionType.outgoing) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( title: "Sending transaction", - body: walletName, walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS, - coinName: coin.name, + shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, txid: tx.txid, - confirmations: tx.confirmations, + confirmations: confirmations, requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, ), ); + await txTracker.addNotifiedPending(tx.txid); } } // notify on confirmed for (final tx in unconfirmedTxnsToNotifyConfirmed) { - if (tx.txType == "Received") { - unawaited( - NotificationApi.showNotification( + if (tx.type == isar_models.TransactionType.incoming) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( title: "Incoming transaction confirmed", - body: walletName, walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), date: DateTime.now(), shouldWatchForUpdates: false, - coinName: coin.name, + txid: tx.txid, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, ), ); await txTracker.addNotifiedConfirmed(tx.txid); - } else if (tx.txType == "Sent") { - unawaited( - NotificationApi.showNotification( + } else if (tx.type == isar_models.TransactionType.outgoing) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( title: "Outgoing transaction confirmed", - body: walletName, walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), date: DateTime.now(), shouldWatchForUpdates: false, - coinName: coin.name, + txid: tx.txid, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, ), ); + await txTracker.addNotifiedConfirmed(tx.txid); } } @@ -880,36 +863,31 @@ class BitcoinCashWallet extends CoinServiceAPI { .log("cached height: $storedHeight", level: LogLevel.Info); if (currentHeight != storedHeight) { - if (currentHeight != -1) { - // -1 failed to fetch current height - await updateStoredChainHeight(newHeight: currentHeight); - } - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId)); - await _checkChangeAddressForTransactions(DerivePathType.bip44); + await _checkChangeAddressForTransactions(); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId)); await _checkCurrentReceivingAddressesForTransactions(); - final newTxData = _fetchTransactionData(); + final fetchFuture = _refreshTransactions(); + final utxosRefreshFuture = _updateUTXOs(); GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.50, walletId)); - final newUtxoData = _fetchUtxoData(); final feeObj = _getFees(); GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.60, walletId)); - _transactionData = Future(() => newTxData); - GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.70, walletId)); _feeObject = Future(() => feeObj); - _utxoData = Future(() => newUtxoData); + + await utxosRefreshFuture; GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.80, walletId)); - await getAllTxsToWatch(await newTxData); + await fetchFuture; + await getAllTxsToWatch(); GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.90, walletId)); } @@ -962,12 +940,13 @@ class BitcoinCashWallet extends CoinServiceAPI { @override Future> prepareSend({ required String address, - required int satoshiAmount, + required Amount amount, Map? args, }) async { try { final feeRateType = args?["feeRate"]; final feeRateAmount = args?["feeRateAmount"]; + final utxos = args?["UTXOs"] as Set?; if (feeRateType is FeeRateType || feeRateAmount is int) { late final int rate; if (feeRateType is FeeRateType) { @@ -990,15 +969,23 @@ class BitcoinCashWallet extends CoinServiceAPI { } // check for send all bool isSendAll = false; - final balance = - Format.decimalAmountToSatoshis(await availableBalance, coin); - if (satoshiAmount == balance) { + if (amount == balance.spendable) { isSendAll = true; } - final result = - await coinSelection(satoshiAmount, rate, address, isSendAll); - Logging.instance.log("SEND RESULT: $result", level: LogLevel.Info); + final bool coinControl = utxos != null; + + final result = await coinSelection( + satoshiAmountToSend: amount.raw.toInt(), + selectedTxFeeRate: rate, + recipientAddress: address, + isSendAll: isSendAll, + utxos: utxos?.toList(), + coinControl: coinControl, + ); + + Logging.instance + .log("PREPARE SEND RESULT: $result", level: LogLevel.Info); if (result is int) { switch (result) { case 1: @@ -1044,6 +1031,12 @@ class BitcoinCashWallet extends CoinServiceAPI { final txHash = await _electrumXClient.broadcastTransaction( rawTx: txData["hex"] as String); Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info); + + final utxos = txData["usedUTXOs"] as List; + + // mark utxos as used + await db.putUTXOs(utxos.map((e) => e.copyWith(used: true)).toList()); + return txHash; } catch (e, s) { Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s", @@ -1052,24 +1045,6 @@ class BitcoinCashWallet extends CoinServiceAPI { } } - @override - Future send({ - required String toAddress, - required int amount, - Map args = const {}, - }) async { - try { - final txData = await prepareSend( - address: toAddress, satoshiAmount: amount, args: args); - final txHash = await confirmSend(txData: txData); - return txHash; - } catch (e, s) { - Logging.instance - .log("Exception rethrown from send(): $e\n$s", level: LogLevel.Error); - rethrow; - } - } - @override Future testNetworkConnection() async { try { @@ -1122,7 +1097,7 @@ class BitcoinCashWallet extends CoinServiceAPI { Logging.instance .log("Generating new ${coin.prettyName} wallet.", level: LogLevel.Info); - if ((DB.instance.get(boxName: walletId, key: "id")) != null) { + if (getCachedId() != null) { throw Exception( "Attempted to initialize a new wallet using an existing wallet ID!"); } @@ -1135,81 +1110,61 @@ class BitcoinCashWallet extends CoinServiceAPI { rethrow; } await Future.wait([ - DB.instance.put(boxName: walletId, key: "id", value: _walletId), - DB.instance - .put(boxName: walletId, key: "isFavorite", value: false), + updateCachedId(walletId), + updateCachedIsFavorite(false), ]); } @override Future initializeExisting() async { - Logging.instance.log("Opening existing ${coin.prettyName} wallet.", + Logging.instance.log("initializeExisting() ${coin.prettyName} wallet.", level: LogLevel.Info); - if ((DB.instance.get(boxName: walletId, key: "id")) == null) { + if (getCachedId() == null) { throw Exception( "Attempted to initialize an existing wallet using an unknown wallet ID!"); } + await _prefs.init(); - final data = - DB.instance.get(boxName: walletId, key: "latest_tx_model") - as TransactionData?; - if (data != null) { - _transactionData = Future(() => data); - } + // await _checkCurrentChangeAddressesForTransactions(); + // await _checkCurrentReceivingAddressesForTransactions(); } - @override - Future get transactionData => - _transactionData ??= _fetchTransactionData(); - Future? _transactionData; - - TransactionData? cachedTxData; - // hack to add tx to txData before refresh completes // required based on current app architecture where we don't properly store // transactions locally in a good way @override Future updateSentCachedTxData(Map txData) async { - final priceData = - await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final locale = Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; - final String worthNow = Format.localizedStringAsFixed( - value: - ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2), - decimalPlaces: 2, - locale: locale!); - - final tx = models.Transaction( + final transaction = isar_models.Transaction( + walletId: walletId, txid: txData["txid"] as String, - confirmedStatus: false, timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, - txType: "Sent", - amount: txData["recipientAmt"] as int, - worthNow: worthNow, - worthAtBlockTimestamp: worthNow, - fees: txData["fee"] as int, - inputSize: 0, - outputSize: 0, + type: isar_models.TransactionType.outgoing, + subType: isar_models.TransactionSubType.none, + // precision may be lost here hence the following amountString + amount: (txData["recipientAmt"] as Amount).raw.toInt(), + amountString: (txData["recipientAmt"] as Amount).toJsonString(), + fee: txData["fee"] as int, + height: null, + isCancelled: false, + isLelantus: false, + otherData: null, + slateId: null, + nonce: null, inputs: [], outputs: [], - address: txData["address"] as String, - height: -1, - confirmations: 0, ); - if (cachedTxData == null) { - final data = await _fetchTransactionData(); - _transactionData = Future(() => data); - } + final address = txData["address"] is String + ? await db.getAddress(walletId, txData["address"] as String) + : null; - final transactions = cachedTxData!.getAllTransactions(); - transactions[tx.txid] = tx; - cachedTxData = models.TransactionData.fromMap(transactions); - _transactionData = Future(() => cachedTxData!); + await db.addNewTransactionData( + [ + Tuple2(transaction, address), + ], + walletId, + ); } bool validateCashAddr(String cashAddr) { @@ -1246,7 +1201,7 @@ class BitcoinCashWallet extends CoinServiceAPI { @override String get walletId => _walletId; - late String _walletId; + late final String _walletId; @override String get walletName => _walletName; @@ -1266,29 +1221,6 @@ class BitcoinCashWallet extends CoinServiceAPI { late SecureStorageInterface _secureStore; - late PriceAPI _priceAPI; - - BitcoinCashWallet({ - required String walletId, - required String walletName, - required Coin coin, - required ElectrumX client, - required CachedElectrumX cachedClient, - required TransactionNotificationTracker tracker, - PriceAPI? priceAPI, - required SecureStorageInterface secureStore, - }) { - txTracker = tracker; - _walletId = walletId; - _walletName = walletName; - _coin = coin; - _electrumXClient = client; - _cachedElectrumXClient = cachedClient; - - _priceAPI = priceAPI ?? PriceAPI(Client()); - _secureStore = secureStore; - } - @override Future updateNode(bool shouldRefresh) async { final failovers = NodeService(secureStorageInterface: _secureStore) @@ -1319,12 +1251,11 @@ class BitcoinCashWallet extends CoinServiceAPI { } Future> _getMnemonicList() async { - final mnemonicString = - await _secureStore.read(key: '${_walletId}_mnemonic'); - if (mnemonicString == null) { + final _mnemonicString = await mnemonicString; + if (_mnemonicString == null) { return []; } - final List data = mnemonicString.split(' '); + final List data = _mnemonicString.split(' '); return data; } @@ -1342,35 +1273,29 @@ class BitcoinCashWallet extends CoinServiceAPI { ); } - Future> _fetchAllOwnAddresses() async { - final List allAddresses = []; + Future> _fetchAllOwnAddresses() async { + final allAddresses = await db + .getAddresses(walletId) + .filter() + .not() + .typeEqualTo(isar_models.AddressType.nonWallet) + .and() + .group((q) => q + .subTypeEqualTo(isar_models.AddressSubType.receiving) + .or() + .subTypeEqualTo(isar_models.AddressSubType.change)) + .findAll(); - final receivingAddressesP2PKH = DB.instance.get( - boxName: walletId, key: 'receivingAddressesP2PKH') as List; - final changeAddressesP2PKH = - DB.instance.get(boxName: walletId, key: 'changeAddressesP2PKH') - as List; - - // for (var i = 0; i < receivingAddresses.length; i++) { - // if (!allAddresses.contains(receivingAddresses[i])) { - // allAddresses.add(receivingAddresses[i]); + // for (var i = 0; i < receivingAddressesP2PKH.length; i++) { + // if (!allAddresses.contains(receivingAddressesP2PKH[i])) { + // allAddresses.add(receivingAddressesP2PKH[i] as String); // } // } - // for (var i = 0; i < changeAddresses.length; i++) { - // if (!allAddresses.contains(changeAddresses[i])) { - // allAddresses.add(changeAddresses[i]); + // for (var i = 0; i < changeAddressesP2PKH.length; i++) { + // if (!allAddresses.contains(changeAddressesP2PKH[i])) { + // allAddresses.add(changeAddressesP2PKH[i] as String); // } // } - for (var i = 0; i < receivingAddressesP2PKH.length; i++) { - if (!allAddresses.contains(receivingAddressesP2PKH[i])) { - allAddresses.add(receivingAddressesP2PKH[i] as String); - } - } - for (var i = 0; i < changeAddressesP2PKH.length; i++) { - if (!allAddresses.contains(changeAddressesP2PKH[i])) { - allAddresses.add(changeAddressesP2PKH[i] as String); - } - } return allAddresses; } @@ -1387,9 +1312,18 @@ class BitcoinCashWallet extends CoinServiceAPI { numberOfBlocksFast: f, numberOfBlocksAverage: m, numberOfBlocksSlow: s, - fast: Format.decimalAmountToSatoshis(fast, coin), - medium: Format.decimalAmountToSatoshis(medium, coin), - slow: Format.decimalAmountToSatoshis(slow, coin), + fast: Amount.fromDecimal( + fast, + fractionDigits: coin.decimals, + ).raw.toInt(), + medium: Amount.fromDecimal( + medium, + fractionDigits: coin.decimals, + ).raw.toInt(), + slow: Amount.fromDecimal( + slow, + fractionDigits: coin.decimals, + ).raw.toInt(), ); Logging.instance.log("fetched fees: $feeObject", level: LogLevel.Info); @@ -1431,217 +1365,141 @@ class BitcoinCashWallet extends CoinServiceAPI { } // this should never fail - if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) { + if ((await mnemonicString) != null || (await mnemonicPassphrase) != null) { throw Exception( "Attempted to overwrite mnemonic on generate new wallet!"); } await _secureStore.write( key: '${_walletId}_mnemonic', value: bip39.generateMnemonic(strength: 256)); - - // Set relevant indexes - await DB.instance - .put(boxName: walletId, key: "receivingIndexP2PKH", value: 0); - await DB.instance - .put(boxName: walletId, key: "changeIndexP2PKH", value: 0); - await DB.instance - .put(boxName: walletId, key: "receivingIndexP2SH", value: 0); - await DB.instance - .put(boxName: walletId, key: "changeIndexP2SH", value: 0); - await DB.instance.put( - boxName: walletId, - key: 'blocked_tx_hashes', - value: ["0xdefault"], - ); // A list of transaction hashes to represent frozen utxos in wallet - // initialize address book entries - await DB.instance.put( - boxName: walletId, - key: 'addressBookEntries', - value: {}); + await _secureStore.write(key: '${_walletId}_mnemonicPassphrase', value: ""); // Generate and add addresses to relevant arrays - final initialReceivingAddressP2PKH = - await _generateAddressForChain(0, 0, DerivePathType.bip44); - final initialChangeAddressP2PKH = - await _generateAddressForChain(1, 0, DerivePathType.bip44); + final initialAddresses = await Future.wait([ + // P2PKH + _generateAddressForChain(0, 0, DerivePathType.bip44), + _generateAddressForChain(1, 0, DerivePathType.bip44), + ]); - final initialReceivingAddressP2SH = - await _generateAddressForChain(0, 0, DerivePathType.bip49); - final initialChangeAddressP2SH = - await _generateAddressForChain(1, 0, DerivePathType.bip49); - - await _addToAddressesArrayForChain( - initialReceivingAddressP2PKH, 0, DerivePathType.bip44); - await _addToAddressesArrayForChain( - initialChangeAddressP2PKH, 1, DerivePathType.bip44); - - await _addToAddressesArrayForChain( - initialReceivingAddressP2SH, 0, DerivePathType.bip49); - await _addToAddressesArrayForChain( - initialChangeAddressP2SH, 1, DerivePathType.bip49); - - // this._currentReceivingAddress = Future(() => initialReceivingAddress); - - var newaddr = await _getCurrentAddressForChain(0, DerivePathType.bip44); - _currentReceivingAddressP2PKH = Future(() => newaddr); - _currentReceivingAddressP2SH = Future(() => initialReceivingAddressP2SH); + await db.putAddresses(initialAddresses); Logging.instance.log("_generateNewWalletFinished", level: LogLevel.Info); } - /// Generates a new internal or external chain address for the wallet using a BIP44 or BIP49 derivation path. + /// Generates a new internal or external chain address for the wallet using a BIP44 derivation path. /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! /// [index] - This can be any integer >= 0 - Future _generateAddressForChain( + Future _generateAddressForChain( int chain, int index, DerivePathType derivePathType, ) async { - final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic'); - final node = await compute( - getBip32NodeWrapper, - Tuple5( - chain, - index, - mnemonic!, - _network, - derivePathType, - ), - ); - final data = PaymentData(pubkey: node.publicKey); - final p2shData = PaymentData( - redeem: - P2WPKH(data: PaymentData(pubkey: node.publicKey), network: _network) - .data); - String address; - - switch (derivePathType) { - case DerivePathType.bip44: - address = P2PKH(data: data, network: _network).data.address!; - break; - case DerivePathType.bip49: - address = P2SH(data: p2shData, network: _network).data.address!; - break; - // default: - // // should never hit this due to all enum cases handled - // return null; - } - - // add generated address & info to derivations - await addDerivation( - chain: chain, - address: address, - pubKey: Format.uint8listToString(node.publicKey), - wif: node.toWIF(), - derivePathType: derivePathType, - ); - - return address; - } - - /// Increases the index for either the internal or external chain, depending on [chain]. - /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! - Future _incrementAddressIndexForChain( - int chain, DerivePathType derivePathType) async { - // Here we assume chain == 1 if it isn't 0 - String indexKey = chain == 0 ? "receivingIndex" : "changeIndex"; - switch (derivePathType) { - case DerivePathType.bip44: - indexKey += "P2PKH"; - break; - case DerivePathType.bip49: - indexKey += "P2SH"; - break; - } - - final newIndex = - (DB.instance.get(boxName: walletId, key: indexKey)) + 1; - await DB.instance - .put(boxName: walletId, key: indexKey, value: newIndex); - } - - /// Adds [address] to the relevant chain's address array, which is determined by [chain]. - /// [address] - Expects a standard native segwit address - /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! - Future _addToAddressesArrayForChain( - String address, int chain, DerivePathType derivePathType) async { - String chainArray = ''; - if (chain == 0) { - chainArray = 'receivingAddresses'; - } else { - chainArray = 'changeAddresses'; - } - switch (derivePathType) { - case DerivePathType.bip44: - chainArray += "P2PKH"; - break; - case DerivePathType.bip49: - chainArray += "P2SH"; - break; - } - - final addressArray = - DB.instance.get(boxName: walletId, key: chainArray); - if (addressArray == null) { + final _mnemonic = await mnemonicString; + final _mnemonicPassphrase = await mnemonicPassphrase; + if (_mnemonicPassphrase == null) { Logging.instance.log( - 'Attempting to add the following to $chainArray array for chain $chain:${[ - address - ]}', - level: LogLevel.Info); - await DB.instance - .put(boxName: walletId, key: chainArray, value: [address]); - } else { - // Make a deep copy of the existing list - final List newArray = []; - addressArray - .forEach((dynamic _address) => newArray.add(_address as String)); - newArray.add(address); // Add the address passed into the method - await DB.instance - .put(boxName: walletId, key: chainArray, value: newArray); + "Exception in _generateAddressForChain: mnemonic passphrase null, possible migration issue; if using internal builds, delete wallet and restore from seed, if using a release build, please file bug report", + level: LogLevel.Error); } + + final derivePath = constructDerivePath( + derivePathType: derivePathType, + networkWIF: _network.wif, + chain: chain, + index: index, + ); + final node = await Bip32Utils.getBip32Node( + _mnemonic!, + _mnemonicPassphrase!, + _network, + derivePath, + ); + + final data = PaymentData(pubkey: node.publicKey); + + String address; + isar_models.AddressType addrType; + + switch (derivePathType) { + case DerivePathType.bip44: + case DerivePathType.bch44: + address = P2PKH(data: data, network: _network).data.address!; + addrType = isar_models.AddressType.p2pkh; + address = bitbox.Address.toCashAddress(address); + break; + default: + throw Exception("DerivePathType $derivePathType not supported"); + } + + return isar_models.Address( + walletId: walletId, + value: address, + publicKey: node.publicKey, + type: addrType, + derivationIndex: index, + derivationPath: isar_models.DerivationPath()..value = derivePath, + subType: chain == 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change, + ); } /// Returns the latest receiving/change (external/internal) address for the wallet depending on [chain] /// and /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! Future _getCurrentAddressForChain( - int chain, DerivePathType derivePathType) async { - // Here, we assume that chain == 1 if it isn't 0 - String arrayKey = chain == 0 ? "receivingAddresses" : "changeAddresses"; + int chain, + DerivePathType derivePathType, + ) async { + final subType = chain == 0 // Here, we assume that chain == 1 if it isn't 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change; + + isar_models.AddressType type; + String coinType; + String purpose; switch (derivePathType) { case DerivePathType.bip44: - arrayKey += "P2PKH"; + type = isar_models.AddressType.p2pkh; + coinType = coin == Coin.bitcoincash ? "145" : "1"; + purpose = "44"; break; - case DerivePathType.bip49: - arrayKey += "P2SH"; + case DerivePathType.bch44: + type = isar_models.AddressType.p2pkh; + coinType = coin == Coin.bitcoincash ? "0" : "1"; + purpose = "44"; break; + default: + throw Exception("DerivePathType $derivePathType not supported"); } - if (kDebugMode) { - print("Array key is ${jsonEncode(arrayKey)}"); - } - final internalChainArray = - DB.instance.get(boxName: walletId, key: arrayKey); - if (derivePathType == DerivePathType.bip44) { - if (bitbox.Address.detectFormat(internalChainArray.last as String) == - bitbox.Address.formatLegacy) { - return bitbox.Address.toCashAddress(internalChainArray.last as String); - } - } - return internalChainArray.last as String; + final address = await db + .getAddresses(walletId) + .filter() + .typeEqualTo(type) + .subTypeEqualTo(subType) + .derivationPath((q) => q.valueStartsWith("m/$purpose'/$coinType")) + .sortByDerivationIndexDesc() + .findFirst(); + return address!.value; } - String _buildDerivationStorageKey( - {required int chain, required DerivePathType derivePathType}) { + String _buildDerivationStorageKey({ + required int chain, + required DerivePathType derivePathType, + }) { String key; String chainId = chain == 0 ? "receive" : "change"; switch (derivePathType) { case DerivePathType.bip44: key = "${walletId}_${chainId}DerivationsP2PKH"; break; - case DerivePathType.bip49: - key = "${walletId}_${chainId}DerivationsP2SH"; + case DerivePathType.bch44: + key = "${walletId}_${chainId}DerivationsBch44P2PKH"; break; + default: + throw UnsupportedError( + "${derivePathType.name} not supported by ${coin.prettyName}"); } return key; } @@ -1658,76 +1516,8 @@ class BitcoinCashWallet extends CoinServiceAPI { jsonDecode(derivationsString ?? "{}") as Map); } - /// Add a single derivation to the local secure storage for [chain] and - /// [derivePathType] where [chain] must either be 1 for change or 0 for receive. - /// This will overwrite a previous entry where the address of the new derivation - /// matches a derivation currently stored. - Future addDerivation({ - required int chain, - required String address, - required String pubKey, - required String wif, - required DerivePathType derivePathType, - }) async { - // build lookup key - final key = _buildDerivationStorageKey( - chain: chain, derivePathType: derivePathType); - - // fetch current derivations - final derivationsString = await _secureStore.read(key: key); - final derivations = - Map.from(jsonDecode(derivationsString ?? "{}") as Map); - - // add derivation - derivations[address] = { - "pubKey": pubKey, - "wif": wif, - }; - - // save derivations - final newReceiveDerivationsString = jsonEncode(derivations); - await _secureStore.write(key: key, value: newReceiveDerivationsString); - } - - /// Add multiple derivations to the local secure storage for [chain] and - /// [derivePathType] where [chain] must either be 1 for change or 0 for receive. - /// This will overwrite any previous entries where the address of the new derivation - /// matches a derivation currently stored. - /// The [derivationsToAdd] must be in the format of: - /// { - /// addressA : { - /// "pubKey": , - /// "wif": , - /// }, - /// addressB : { - /// "pubKey": , - /// "wif": , - /// }, - /// } - Future addDerivations({ - required int chain, - required DerivePathType derivePathType, - required Map derivationsToAdd, - }) async { - // build lookup key - final key = _buildDerivationStorageKey( - chain: chain, derivePathType: derivePathType); - - // fetch current derivations - final derivationsString = await _secureStore.read(key: key); - final derivations = - Map.from(jsonDecode(derivationsString ?? "{}") as Map); - - // add derivation - derivations.addAll(derivationsToAdd); - - // save derivations - final newReceiveDerivationsString = jsonEncode(derivations); - await _secureStore.write(key: key, value: newReceiveDerivationsString); - } - - Future _fetchUtxoData() async { - final List allAddresses = await _fetchAllOwnAddresses(); + Future _updateUTXOs() async { + final allAddresses = await _fetchAllOwnAddresses(); try { final fetchedUtxoList = >>[]; @@ -1739,7 +1529,8 @@ class BitcoinCashWallet extends CoinServiceAPI { if (batches[batchNumber] == null) { batches[batchNumber] = {}; } - final scripthash = _convertToScriptHash(allAddresses[i], _network); + final scripthash = + _convertToScriptHash(allAddresses[i].value, _network); if (kDebugMode) { print("SCRIPT_HASH_FOR_ADDRESS ${allAddresses[i]} IS $scripthash"); } @@ -1761,144 +1552,72 @@ class BitcoinCashWallet extends CoinServiceAPI { } } - final priceData = - await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final List> outputArray = []; - int satoshiBalance = 0; - int satoshiBalancePending = 0; + final List outputArray = []; for (int i = 0; i < fetchedUtxoList.length; i++) { for (int j = 0; j < fetchedUtxoList[i].length; j++) { - int value = fetchedUtxoList[i][j]["value"] as int; - satoshiBalance += value; + final jsonUTXO = fetchedUtxoList[i][j]; final txn = await cachedElectrumXClient.getTransaction( - txHash: fetchedUtxoList[i][j]["tx_hash"] as String, + txHash: jsonUTXO["tx_hash"] as String, verbose: true, coin: coin, ); - final Map utxo = {}; - final int confirmations = txn["confirmations"] as int? ?? 0; - final bool confirmed = txn["confirmations"] == null - ? false - : txn["confirmations"] as int >= MINIMUM_CONFIRMATIONS; - if (!confirmed) { - satoshiBalancePending += value; + final vout = jsonUTXO["tx_pos"] as int; + + final outputs = txn["vout"] as List; + + String? utxoOwnerAddress; + // get UTXO owner address + for (final output in outputs) { + if (output["n"] == vout) { + utxoOwnerAddress = + output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]?["address"] as String?; + } } - utxo["txid"] = txn["txid"]; - utxo["vout"] = fetchedUtxoList[i][j]["tx_pos"]; - utxo["value"] = value; + final utxo = isar_models.UTXO( + walletId: walletId, + txid: txn["txid"] as String, + vout: vout, + value: jsonUTXO["value"] as int, + name: "", + isBlocked: false, + blockedReason: null, + isCoinbase: txn["is_coinbase"] as bool? ?? false, + blockHash: txn["blockhash"] as String?, + blockHeight: jsonUTXO["height"] as int?, + blockTime: txn["blocktime"] as int?, + address: utxoOwnerAddress, + ); - utxo["status"] = {}; - utxo["status"]["confirmed"] = confirmed; - utxo["status"]["confirmations"] = confirmations; - utxo["status"]["block_height"] = fetchedUtxoList[i][j]["height"]; - utxo["status"]["block_hash"] = txn["blockhash"]; - utxo["status"]["block_time"] = txn["blocktime"]; - - final fiatValue = ((Decimal.fromInt(value) * currentPrice) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2); - utxo["rawWorth"] = fiatValue; - utxo["fiatWorth"] = fiatValue.toString(); outputArray.add(utxo); } } - Decimal currencyBalanceRaw = - ((Decimal.fromInt(satoshiBalance) * currentPrice) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2); - - final Map result = { - "total_user_currency": currencyBalanceRaw.toString(), - "total_sats": satoshiBalance, - "total_btc": (Decimal.fromInt(satoshiBalance) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal( - scaleOnInfinitePrecision: Constants.decimalPlacesForCoin(coin)) - .toString(), - "outputArray": outputArray, - "unconfirmed": satoshiBalancePending, - }; - - final dataModel = UtxoData.fromJson(result); - - final List allOutputs = dataModel.unspentOutputArray; Logging.instance - .log('Outputs fetched: $allOutputs', level: LogLevel.Info); - await _sortOutputs(allOutputs); - await DB.instance.put( - boxName: walletId, key: 'latest_utxo_model', value: dataModel); - await DB.instance.put( - boxName: walletId, - key: 'totalBalance', - value: dataModel.satoshiBalance); - return dataModel; + .log('Outputs fetched: $outputArray', level: LogLevel.Info); + + await db.updateUTXOs(walletId, outputArray); + + // finally update balance + await _updateBalance(); } catch (e, s) { Logging.instance .log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error); - final latestTxModel = - DB.instance.get(boxName: walletId, key: 'latest_utxo_model'); - - if (latestTxModel == null) { - final emptyModel = { - "total_user_currency": "0.00", - "total_sats": 0, - "total_btc": "0", - "outputArray": [] - }; - return UtxoData.fromJson(emptyModel); - } else { - Logging.instance - .log("Old output model located", level: LogLevel.Warning); - return latestTxModel as models.UtxoData; - } } } - /// Takes in a list of UtxoObjects and adds a name (dependent on object index within list) - /// and checks for the txid associated with the utxo being blocked and marks it accordingly. - /// Now also checks for output labeling. - Future _sortOutputs(List utxos) async { - final blockedHashArray = - DB.instance.get(boxName: walletId, key: 'blocked_tx_hashes') - as List?; - final List lst = []; - if (blockedHashArray != null) { - for (var hash in blockedHashArray) { - lst.add(hash as String); - } - } - final labels = - DB.instance.get(boxName: walletId, key: 'labels') as Map? ?? - {}; - - outputsList = []; - - for (var i = 0; i < utxos.length; i++) { - if (labels[utxos[i].txid] != null) { - utxos[i].txName = labels[utxos[i].txid] as String? ?? ""; - } else { - utxos[i].txName = 'Output #$i'; - } - - if (utxos[i].status.confirmed == false) { - outputsList.add(utxos[i]); - } else { - if (lst.contains(utxos[i].txid)) { - utxos[i].blocked = true; - outputsList.add(utxos[i]); - } else if (!lst.contains(utxos[i].txid)) { - outputsList.add(utxos[i]); - } - } - } + Future _updateBalance() async { + await refreshBalance(); } + @override + Balance get balance => _balance ??= getCachedBalance(); + Balance? _balance; + Future getTxCount({required String address}) async { String? scripthash; try { @@ -1950,102 +1669,90 @@ class BitcoinCashWallet extends CoinServiceAPI { } } - Future _checkReceivingAddressForTransactions( - DerivePathType derivePathType) async { + Future _checkReceivingAddressForTransactions() async { try { - final String currentExternalAddr = - await _getCurrentAddressForChain(0, derivePathType); - final int txCount = await getTxCount(address: currentExternalAddr); + final currentReceiving = await _currentReceivingAddress; + + final int txCount = await getTxCount(address: currentReceiving.value); Logging.instance.log( - 'Number of txs for current receiving address $currentExternalAddr: $txCount', + 'Number of txs for current receiving address $currentReceiving: $txCount', level: LogLevel.Info); - if (txCount >= 1) { + if (txCount >= 1 || currentReceiving.derivationIndex < 0) { // First increment the receiving index - await _incrementAddressIndexForChain(0, derivePathType); - - // Check the new receiving index - String indexKey = "receivingIndex"; - switch (derivePathType) { - case DerivePathType.bip44: - indexKey += "P2PKH"; - break; - case DerivePathType.bip49: - indexKey += "P2SH"; - break; - } - final newReceivingIndex = - DB.instance.get(boxName: walletId, key: indexKey) as int; + final newReceivingIndex = currentReceiving.derivationIndex + 1; // Use new index to derive a new receiving address final newReceivingAddress = await _generateAddressForChain( - 0, newReceivingIndex, derivePathType); + 0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin)); - // Add that new receiving address to the array of receiving addresses - await _addToAddressesArrayForChain( - newReceivingAddress, 0, derivePathType); - - // Set the new receiving address that the service - - switch (derivePathType) { - case DerivePathType.bip44: - _currentReceivingAddressP2PKH = Future(() => newReceivingAddress); - break; - case DerivePathType.bip49: - _currentReceivingAddressP2SH = Future(() => newReceivingAddress); - break; + final existing = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(newReceivingAddress.value) + .findFirst(); + if (existing == null) { + // Add that new change address + await db.putAddress(newReceivingAddress); + } else { + // we need to update the address + await db.updateAddress(existing, newReceivingAddress); } + // keep checking until address with no tx history is set as current + await _checkReceivingAddressForTransactions(); } } on SocketException catch (se, s) { Logging.instance.log( - "SocketException caught in _checkReceivingAddressForTransactions($derivePathType): $se\n$s", + "SocketException caught in _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $se\n$s", level: LogLevel.Error); return; } catch (e, s) { Logging.instance.log( - "Exception rethrown from _checkReceivingAddressForTransactions($derivePathType): $e\n$s", + "Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s", level: LogLevel.Error); rethrow; } } - Future _checkChangeAddressForTransactions( - DerivePathType derivePathType) async { + Future _checkChangeAddressForTransactions() async { try { - final String currentExternalAddr = - await _getCurrentAddressForChain(1, derivePathType); - final int txCount = await getTxCount(address: currentExternalAddr); + final currentChange = await _currentChangeAddress; + final int txCount = await getTxCount(address: currentChange.value); Logging.instance.log( - 'Number of txs for current change address $currentExternalAddr: $txCount', + 'Number of txs for current change address $currentChange: $txCount', level: LogLevel.Info); - if (txCount >= 1) { + if (txCount >= 1 || currentChange.derivationIndex < 0) { // First increment the change index - await _incrementAddressIndexForChain(1, derivePathType); - - // Check the new change index - String indexKey = "changeIndex"; - switch (derivePathType) { - case DerivePathType.bip44: - indexKey += "P2PKH"; - break; - case DerivePathType.bip49: - indexKey += "P2SH"; - break; - } - final newChangeIndex = - DB.instance.get(boxName: walletId, key: indexKey) as int; + final newChangeIndex = currentChange.derivationIndex + 1; // Use new index to derive a new change address - final newChangeAddress = - await _generateAddressForChain(1, newChangeIndex, derivePathType); + final newChangeAddress = await _generateAddressForChain( + 1, newChangeIndex, DerivePathTypeExt.primaryFor(coin)); - // Add that new receiving address to the array of change addresses - await _addToAddressesArrayForChain(newChangeAddress, 1, derivePathType); + final existing = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(newChangeAddress.value) + .findFirst(); + if (existing == null) { + // Add that new change address + await db.putAddress(newChangeAddress); + } else { + // we need to update the address + await db.updateAddress(existing, newChangeAddress); + } + // keep checking until address with no tx history is set as current + await _checkChangeAddressForTransactions(); } + } on SocketException catch (se, s) { + Logging.instance.log( + "SocketException caught in _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $se\n$s", + level: LogLevel.Error); + return; } catch (e, s) { Logging.instance.log( - "Exception rethrown from _checkChangeAddressForTransactions($derivePathType): $e\n$s", + "Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s", level: LogLevel.Error); rethrow; } @@ -2053,9 +1760,9 @@ class BitcoinCashWallet extends CoinServiceAPI { Future _checkCurrentReceivingAddressesForTransactions() async { try { - for (final type in DerivePathType.values) { - await _checkReceivingAddressForTransactions(type); - } + // for (final type in DerivePathType.values) { + await _checkReceivingAddressForTransactions(); + // } } catch (e, s) { Logging.instance.log( "Exception rethrown from _checkCurrentReceivingAddressesForTransactions(): $e\n$s", @@ -2077,9 +1784,9 @@ class BitcoinCashWallet extends CoinServiceAPI { Future _checkCurrentChangeAddressesForTransactions() async { try { - for (final type in DerivePathType.values) { - await _checkChangeAddressForTransactions(type); - } + // for (final type in DerivePathType.values) { + await _checkChangeAddressForTransactions(); + // } } catch (e, s) { Logging.instance.log( "Exception rethrown from _checkCurrentChangeAddressesForTransactions(): $e\n$s", @@ -2109,18 +1816,7 @@ class BitcoinCashWallet extends CoinServiceAPI { validateCashAddr(bchAddress)) { bchAddress = bitbox.Address.toLegacyAddress(bchAddress); } - final output = Address.addressToOutputScript(bchAddress, network); - final hash = sha256.convert(output.toList(growable: false)).toString(); - - final chars = hash.split(""); - final reversedPairs = []; - var i = chars.length - 1; - while (i > 0) { - reversedPairs.add(chars[i - 1]); - reversedPairs.add(chars[i]); - i -= 2; - } - return reversedPairs.join(""); + return AddressUtils.convertToScriptHash(bchAddress, network); } catch (e) { rethrow; } @@ -2180,348 +1876,286 @@ class BitcoinCashWallet extends CoinServiceAPI { return false; } - Future _fetchTransactionData() async { - List allAddressesOld = await _fetchAllOwnAddresses(); - List allAddresses = []; - for (String address in allAddressesOld) { - if (bitbox.Address.detectFormat(address) == bitbox.Address.formatLegacy && - addressType(address: address) == DerivePathType.bip44) { - allAddresses.add(bitbox.Address.toCashAddress(address)); - } else { - allAddresses.add(address); - } - } + Future _refreshTransactions() async { + List allAddressesOld = await _fetchAllOwnAddresses(); - var changeAddressesP2PKHOld = - DB.instance.get(boxName: walletId, key: 'changeAddressesP2PKH') - as List; - List changeAddressesP2PKH = []; - for (var address in changeAddressesP2PKHOld) { - if (bitbox.Address.detectFormat(address as String) == - bitbox.Address.formatLegacy) { - changeAddressesP2PKH.add(bitbox.Address.toCashAddress(address)); + Set receivingAddresses = allAddressesOld + .where((e) => e.subType == isar_models.AddressSubType.receiving) + .map((e) { + if (bitbox.Address.detectFormat(e.value) == bitbox.Address.formatLegacy && + (addressType(address: e.value) == DerivePathType.bip44 || + addressType(address: e.value) == DerivePathType.bch44)) { + return bitbox.Address.toCashAddress(e.value); } else { - changeAddressesP2PKH.add(address); + return e.value; } - } + }).toSet(); + + Set changeAddresses = allAddressesOld + .where((e) => e.subType == isar_models.AddressSubType.change) + .map((e) { + if (bitbox.Address.detectFormat(e.value) == bitbox.Address.formatLegacy && + (addressType(address: e.value) == DerivePathType.bip44 || + addressType(address: e.value) == DerivePathType.bch44)) { + return bitbox.Address.toCashAddress(e.value); + } else { + return e.value; + } + }).toSet(); final List> allTxHashes = - await _fetchHistory(allAddresses); - - final cachedTransactions = - DB.instance.get(boxName: walletId, key: 'latest_tx_model') - as TransactionData?; - int latestTxnBlockHeight = - DB.instance.get(boxName: walletId, key: "storedTxnDataHeight") - as int? ?? - 0; - - final unconfirmedCachedTransactions = - cachedTransactions?.getAllTransactions() ?? {}; - unconfirmedCachedTransactions - .removeWhere((key, value) => value.confirmedStatus); - - if (kDebugMode) { - print("CACHED_TRANSACTIONS_IS $cachedTransactions"); - } - if (cachedTransactions != null) { - for (final tx in allTxHashes.toList(growable: false)) { - final txHeight = tx["height"] as int; - if (txHeight > 0 && - txHeight < latestTxnBlockHeight - MINIMUM_CONFIRMATIONS) { - if (unconfirmedCachedTransactions[tx["tx_hash"] as String] == null) { - if (kDebugMode) { - print( - cachedTransactions.findTransaction(tx["tx_hash"] as String)); - print(unconfirmedCachedTransactions[tx["tx_hash"] as String]); - } - final cachedTx = - cachedTransactions.findTransaction(tx["tx_hash"] as String); - if (!(cachedTx != null && - addressType(address: cachedTx.address) == - DerivePathType.bip44 && - bitbox.Address.detectFormat(cachedTx.address) == - bitbox.Address.formatLegacy)) { - allTxHashes.remove(tx); - } - } - } - } - } + await _fetchHistory([...receivingAddresses, ...changeAddresses]); List> allTransactions = []; for (final txHash in allTxHashes) { - final tx = await cachedElectrumXClient.getTransaction( - txHash: txHash["tx_hash"] as String, - verbose: true, - coin: coin, - ); + final storedTx = await db + .getTransactions(walletId) + .filter() + .txidEqualTo(txHash["tx_hash"] as String) + .findFirst(); - // Logging.instance.log("TRANSACTION: ${jsonEncode(tx)}"); - // TODO fix this for sent to self transactions? - if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { - tx["address"] = txHash["address"]; - tx["height"] = txHash["height"]; - allTransactions.add(tx); + if (storedTx == null || + storedTx.address.value == null || + storedTx.height == null || + (storedTx.height != null && storedTx.height! <= 0) + // zero conf messes this up + // !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS) + ) { + final tx = await cachedElectrumXClient.getTransaction( + txHash: txHash["tx_hash"] as String, + verbose: true, + coin: coin, + ); + + // Logging.instance.log("TRANSACTION: ${jsonEncode(tx)}"); + if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { + tx["address"] = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(txHash["address"] as String) + .findFirst(); + tx["height"] = txHash["height"]; + allTransactions.add(tx); + } } } + // + // Logging.instance.log("addAddresses: $allAddresses", level: LogLevel.Info); + // Logging.instance.log("allTxHashes: $allTxHashes", level: LogLevel.Info); + // + // Logging.instance.log("allTransactions length: ${allTransactions.length}", + // level: LogLevel.Info); - Logging.instance.log("addAddresses: $allAddresses", level: LogLevel.Info); - Logging.instance.log("allTxHashes: $allTxHashes", level: LogLevel.Info); + final List> txns = []; - Logging.instance.log("allTransactions length: ${allTransactions.length}", - level: LogLevel.Info); + for (final txData in allTransactions) { + Set inputAddresses = {}; + Set outputAddresses = {}; - final priceData = - await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final List> midSortedArray = []; + Amount totalInputValue = Amount( + rawValue: BigInt.from(0), + fractionDigits: coin.decimals, + ); + Amount totalOutputValue = Amount( + rawValue: BigInt.from(0), + fractionDigits: coin.decimals, + ); - for (final txObject in allTransactions) { - List sendersArray = []; - List recipientsArray = []; + Amount amountSentFromWallet = Amount( + rawValue: BigInt.from(0), + fractionDigits: coin.decimals, + ); + Amount amountReceivedInWallet = Amount( + rawValue: BigInt.from(0), + fractionDigits: coin.decimals, + ); + Amount changeAmount = Amount( + rawValue: BigInt.from(0), + fractionDigits: coin.decimals, + ); - // Usually only has value when txType = 'Send' - int inputAmtSentFromWallet = 0; - // Usually has value regardless of txType due to change addresses - int outputAmtAddressedToWallet = 0; - int fee = 0; - - Map midSortedTx = {}; - - for (int i = 0; i < (txObject["vin"] as List).length; i++) { - final input = txObject["vin"][i] as Map; + // parse inputs + for (final input in txData["vin"] as List) { final prevTxid = input["txid"] as String; final prevOut = input["vout"] as int; - final tx = await _cachedElectrumXClient.getTransaction( - txHash: prevTxid, coin: coin); + // fetch input tx to get address + final inputTx = await cachedElectrumXClient.getTransaction( + txHash: prevTxid, + coin: coin, + ); + + for (final output in inputTx["vout"] as List) { + // check matching output + if (prevOut == output["n"]) { + // get value + final value = Amount.fromDecimal( + Decimal.parse(output["value"].toString()), + fractionDigits: coin.decimals, + ); + + // add value to total + totalInputValue = totalInputValue + value; + + // get input(prevOut) address + final address = + output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]?["address"] as String?; - for (final out in tx["vout"] as List) { - if (prevOut == out["n"]) { - final address = out["scriptPubKey"]["addresses"][0] as String?; if (address != null) { - sendersArray.add(address); + inputAddresses.add(address); + + // if input was from my wallet, add value to amount sent + if (receivingAddresses.contains(address) || + changeAddresses.contains(address)) { + amountSentFromWallet = amountSentFromWallet + value; + } } } } } - Logging.instance.log("sendersArray: $sendersArray", level: LogLevel.Info); + // parse outputs + for (final output in txData["vout"] as List) { + // get value + final value = Amount.fromDecimal( + Decimal.parse(output["value"].toString()), + fractionDigits: coin.decimals, + ); - for (final output in txObject["vout"] as List) { - final address = output["scriptPubKey"]["addresses"][0] as String?; + // add value to total + totalOutputValue += value; + + // get output address + final address = output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]?["address"] as String?; if (address != null) { - recipientsArray.add(address); + outputAddresses.add(address); + + // if output was to my wallet, add value to amount received + if (receivingAddresses.contains(address)) { + amountReceivedInWallet += value; + } else if (changeAddresses.contains(address)) { + changeAmount += value; + } } } - Logging.instance - .log("recipientsArray: $recipientsArray", level: LogLevel.Info); + final mySentFromAddresses = [ + ...receivingAddresses.intersection(inputAddresses), + ...changeAddresses.intersection(inputAddresses) + ]; + final myReceivedOnAddresses = + receivingAddresses.intersection(outputAddresses); + final myChangeReceivedOnAddresses = + changeAddresses.intersection(outputAddresses); - final foundInSenders = - allAddresses.any((element) => sendersArray.contains(element)) || - allAddressesOld.any((element) => sendersArray.contains(element)); - Logging.instance - .log("foundInSenders: $foundInSenders", level: LogLevel.Info); + final fee = totalInputValue - totalOutputValue; - // If txType = Sent, then calculate inputAmtSentFromWallet - if (foundInSenders) { - int totalInput = 0; - for (int i = 0; i < (txObject["vin"] as List).length; i++) { - final input = txObject["vin"][i] as Map; - final prevTxid = input["txid"] as String; - final prevOut = input["vout"] as int; - final tx = await _cachedElectrumXClient.getTransaction( - txHash: prevTxid, - coin: coin, + // this is the address initially used to fetch the txid + isar_models.Address transactionAddress = + txData["address"] as isar_models.Address; + + isar_models.TransactionType type; + Amount amount; + if (mySentFromAddresses.isNotEmpty && myReceivedOnAddresses.isNotEmpty) { + // tx is sent to self + type = isar_models.TransactionType.sentToSelf; + amount = + amountSentFromWallet - amountReceivedInWallet - fee - changeAmount; + } else if (mySentFromAddresses.isNotEmpty) { + // outgoing tx + type = isar_models.TransactionType.outgoing; + amount = amountSentFromWallet - changeAmount - fee; + final possible = + outputAddresses.difference(myChangeReceivedOnAddresses).first; + + if (transactionAddress.value != possible) { + transactionAddress = isar_models.Address( + walletId: walletId, + value: possible, + publicKey: [], + type: AddressType.nonWallet, + derivationIndex: -1, + derivationPath: null, + subType: AddressSubType.nonWallet, ); - - for (final out in tx["vout"] as List) { - if (prevOut == out["n"]) { - inputAmtSentFromWallet += - (Decimal.parse(out["value"].toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - } - } } - totalInput = inputAmtSentFromWallet; - int totalOutput = 0; - - for (final output in txObject["vout"] as List) { - final address = output["scriptPubKey"]["addresses"][0]; - final value = output["value"]; - final _value = (Decimal.parse(value.toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - totalOutput += _value; - if (changeAddressesP2PKH.contains(address)) { - inputAmtSentFromWallet -= _value; - } else { - // change address from 'sent from' to the 'sent to' address - txObject["address"] = address; - } - } - // calculate transaction fee - fee = totalInput - totalOutput; - // subtract fee from sent to calculate correct value of sent tx - inputAmtSentFromWallet -= fee; } else { - // counters for fee calculation - int totalOut = 0; - int totalIn = 0; - - // add up received tx value - for (final output in txObject["vout"] as List) { - final address = output["scriptPubKey"]["addresses"][0]; - if (address != null) { - final value = (Decimal.parse(output["value"].toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - totalOut += value; - if (allAddresses.contains(address) || - allAddressesOld.contains(address)) { - outputAmtAddressedToWallet += value; - } - } - } - - // calculate fee for received tx - for (int i = 0; i < (txObject["vin"] as List).length; i++) { - final input = txObject["vin"][i] as Map; - final prevTxid = input["txid"] as String; - final prevOut = input["vout"] as int; - final tx = await _cachedElectrumXClient.getTransaction( - txHash: prevTxid, - coin: coin, - ); - - for (final out in tx["vout"] as List) { - if (prevOut == out["n"]) { - totalIn += (Decimal.parse(out["value"].toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - } - } - } - fee = totalIn - totalOut; + // incoming tx + type = isar_models.TransactionType.incoming; + amount = amountReceivedInWallet; } - // create final tx map - midSortedTx["txid"] = txObject["txid"]; - midSortedTx["confirmed_status"] = (txObject["confirmations"] != null) && - (txObject["confirmations"] as int >= MINIMUM_CONFIRMATIONS); - midSortedTx["confirmations"] = txObject["confirmations"] ?? 0; - midSortedTx["timestamp"] = txObject["blocktime"] ?? - (DateTime.now().millisecondsSinceEpoch ~/ 1000); + List inputs = []; + List outputs = []; - if (foundInSenders) { - midSortedTx["txType"] = "Sent"; - midSortedTx["amount"] = inputAmtSentFromWallet; - final String worthNow = - ((currentPrice * Decimal.fromInt(inputAmtSentFromWallet)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2) - .toStringAsFixed(2); - midSortedTx["worthNow"] = worthNow; - midSortedTx["worthAtBlockTimestamp"] = worthNow; - } else { - midSortedTx["txType"] = "Received"; - midSortedTx["amount"] = outputAmtAddressedToWallet; - final worthNow = - ((currentPrice * Decimal.fromInt(outputAmtAddressedToWallet)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2) - .toStringAsFixed(2); - midSortedTx["worthNow"] = worthNow; - } - midSortedTx["aliens"] = []; - midSortedTx["fees"] = fee; - midSortedTx["address"] = txObject["address"]; - midSortedTx["inputSize"] = txObject["vin"].length; - midSortedTx["outputSize"] = txObject["vout"].length; - midSortedTx["inputs"] = txObject["vin"]; - midSortedTx["outputs"] = txObject["vout"]; - - final int height = txObject["height"] as int; - midSortedTx["height"] = height; - - if (height >= latestTxnBlockHeight) { - latestTxnBlockHeight = height; + for (final json in txData["vin"] as List) { + bool isCoinBase = json['coinbase'] != null; + final input = isar_models.Input( + txid: json['txid'] as String, + vout: json['vout'] as int? ?? -1, + scriptSig: json['scriptSig']?['hex'] as String?, + scriptSigAsm: json['scriptSig']?['asm'] as String?, + isCoinbase: isCoinBase ? isCoinBase : json['is_coinbase'] as bool?, + sequence: json['sequence'] as int?, + innerRedeemScriptAsm: json['innerRedeemscriptAsm'] as String?, + ); + inputs.add(input); } - midSortedArray.add(midSortedTx); + for (final json in txData["vout"] as List) { + final output = isar_models.Output( + scriptPubKey: json['scriptPubKey']?['hex'] as String?, + scriptPubKeyAsm: json['scriptPubKey']?['asm'] as String?, + scriptPubKeyType: json['scriptPubKey']?['type'] as String?, + scriptPubKeyAddress: + json["scriptPubKey"]?["addresses"]?[0] as String? ?? + json['scriptPubKey']['type'] as String, + value: Amount.fromDecimal( + Decimal.parse(json["value"].toString()), + fractionDigits: coin.decimals, + ).raw.toInt(), + ); + outputs.add(output); + } + + final tx = isar_models.Transaction( + walletId: walletId, + txid: txData["txid"] as String, + timestamp: txData["blocktime"] as int? ?? + (DateTime.now().millisecondsSinceEpoch ~/ 1000), + type: type, + subType: isar_models.TransactionSubType.none, + amount: amount.raw.toInt(), + amountString: amount.toJsonString(), + fee: fee.raw.toInt(), + height: txData["height"] as int?, + isCancelled: false, + isLelantus: false, + slateId: null, + otherData: null, + nonce: null, + inputs: inputs, + outputs: outputs, + ); + + txns.add(Tuple2(tx, transactionAddress)); } - // sort by date ---- //TODO not sure if needed - // shouldn't be any issues with a null timestamp but I got one at some point? - midSortedArray - .sort((a, b) => (b["timestamp"] as int) - (a["timestamp"] as int)); - // { - // final aT = a["timestamp"]; - // final bT = b["timestamp"]; - // - // if (aT == null && bT == null) { - // return 0; - // } else if (aT == null) { - // return -1; - // } else if (bT == null) { - // return 1; - // } else { - // return bT - aT; - // } - // }); + await db.addNewTransactionData(txns, walletId); - // buildDateTimeChunks - final Map result = {"dateTimeChunks": []}; - final dateArray = []; - - for (int i = 0; i < midSortedArray.length; i++) { - final txObject = midSortedArray[i]; - final date = extractDateFromTimestamp(txObject["timestamp"] as int); - final txTimeArray = [txObject["timestamp"], date]; - - if (dateArray.contains(txTimeArray[1])) { - result["dateTimeChunks"].forEach((dynamic chunk) { - if (extractDateFromTimestamp(chunk["timestamp"] as int) == - txTimeArray[1]) { - if (chunk["transactions"] == null) { - chunk["transactions"] = >[]; - } - chunk["transactions"].add(txObject); - } - }); - } else { - dateArray.add(txTimeArray[1]); - final chunk = { - "timestamp": txTimeArray[0], - "transactions": [txObject], - }; - result["dateTimeChunks"].add(chunk); - } + // quick hack to notify manager to call notifyListeners if + // transactions changed + if (txns.isNotEmpty) { + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "Transactions updated/added for: $walletId $walletName ", + walletId, + ), + ); } - - final transactionsMap = cachedTransactions?.getAllTransactions() ?? {}; - transactionsMap - .addAll(TransactionData.fromJson(result).getAllTransactions()); - - final txModel = TransactionData.fromMap(transactionsMap); - - await DB.instance.put( - boxName: walletId, - key: 'storedTxnDataHeight', - value: latestTxnBlockHeight); - await DB.instance.put( - boxName: walletId, key: 'latest_tx_model', value: txModel); - - cachedTxData = txModel; - return txModel; } int estimateTxFee({required int vSize, required int feeRatePerKB}) { @@ -2532,27 +2166,51 @@ class BitcoinCashWallet extends CoinServiceAPI { /// with [satoshiAmountToSend] and [selectedTxFeeRate]. If so, it will call buildTrasaction() and return /// a map containing the tx hex along with other important information. If not, then it will return /// an integer (1 or 2) - dynamic coinSelection(int satoshiAmountToSend, int selectedTxFeeRate, - String _recipientAddress, bool isSendAll, - {int additionalOutputs = 0, List? utxos}) async { + dynamic coinSelection({ + required int satoshiAmountToSend, + required int selectedTxFeeRate, + required String recipientAddress, + required bool coinControl, + required bool isSendAll, + int additionalOutputs = 0, + List? utxos, + }) async { Logging.instance .log("Starting coinSelection ----------", level: LogLevel.Info); - final List availableOutputs = utxos ?? outputsList; - final List spendableOutputs = []; + final List availableOutputs = utxos ?? await this.utxos; + final currentChainHeight = await chainHeight; + final List spendableOutputs = []; int spendableSatoshiValue = 0; // Build list of spendable outputs and totaling their satoshi amount - for (var i = 0; i < availableOutputs.length; i++) { - if (availableOutputs[i].blocked == false && - availableOutputs[i].status.confirmed == true) { - spendableOutputs.add(availableOutputs[i]); - spendableSatoshiValue += availableOutputs[i].value; + for (final utxo in availableOutputs) { + if (utxo.isBlocked == false && + utxo.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) && + utxo.used != true) { + spendableOutputs.add(utxo); + spendableSatoshiValue += utxo.value; } } - // sort spendable by age (oldest first) - spendableOutputs.sort( - (a, b) => b.status.confirmations.compareTo(a.status.confirmations)); + if (coinControl) { + if (spendableOutputs.length < availableOutputs.length) { + throw ArgumentError("Attempted to use an unavailable utxo"); + } + } + + // don't care about sorting if using all utxos + if (!coinControl) { + // sort spendable by age (oldest first) + spendableOutputs.sort((a, b) { + if (a.blockTime != null && b.blockTime != null) { + return b.blockTime!.compareTo(a.blockTime!); + } else if (a.blockTime != null) { + return -1; + } else { + return 1; + } + }); + } Logging.instance.log("spendableOutputs.length: ${spendableOutputs.length}", level: LogLevel.Info); @@ -2579,21 +2237,29 @@ class BitcoinCashWallet extends CoinServiceAPI { // Possible situation right here int satoshisBeingUsed = 0; int inputsBeingConsumed = 0; - List utxoObjectsToUse = []; + List utxoObjectsToUse = []; - for (var i = 0; - satoshisBeingUsed < satoshiAmountToSend && i < spendableOutputs.length; - i++) { - utxoObjectsToUse.add(spendableOutputs[i]); - satoshisBeingUsed += spendableOutputs[i].value; - inputsBeingConsumed += 1; - } - for (int i = 0; - i < additionalOutputs && inputsBeingConsumed < spendableOutputs.length; - i++) { - utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]); - satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value; - inputsBeingConsumed += 1; + if (!coinControl) { + for (var i = 0; + satoshisBeingUsed < satoshiAmountToSend && + i < spendableOutputs.length; + i++) { + utxoObjectsToUse.add(spendableOutputs[i]); + satoshisBeingUsed += spendableOutputs[i].value; + inputsBeingConsumed += 1; + } + for (int i = 0; + i < additionalOutputs && + inputsBeingConsumed < spendableOutputs.length; + i++) { + utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]); + satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value; + inputsBeingConsumed += 1; + } + } else { + satoshisBeingUsed = spendableSatoshiValue; + utxoObjectsToUse = spendableOutputs; + inputsBeingConsumed = spendableOutputs.length; } Logging.instance @@ -2606,7 +2272,7 @@ class BitcoinCashWallet extends CoinServiceAPI { .log('satoshiAmountToSend $satoshiAmountToSend', level: LogLevel.Info); // numberOfOutputs' length must always be equal to that of recipientsArray and recipientsAmtArray - List recipientsArray = [_recipientAddress]; + List recipientsArray = [recipientAddress]; List recipientsAmtArray = [satoshiAmountToSend]; // gather required signing data @@ -2619,7 +2285,7 @@ class BitcoinCashWallet extends CoinServiceAPI { final int vSizeForOneOutput = (await buildTransaction( utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, - recipients: [_recipientAddress], + recipients: [recipientAddress], satoshiAmounts: [satoshisBeingUsed - 1], ))["vSize"] as int; int feeForOneOutput = estimateTxFee( @@ -2640,9 +2306,13 @@ class BitcoinCashWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": amount, + "recipientAmt": Amount( + rawValue: BigInt.from(amount), + fractionDigits: coin.decimals, + ), "fee": feeForOneOutput, "vSize": txn["vSize"], + "usedUTXOs": utxoObjectsToUse, }; return transactionObject; } @@ -2650,15 +2320,15 @@ class BitcoinCashWallet extends CoinServiceAPI { final int vSizeForOneOutput = (await buildTransaction( utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, - recipients: [_recipientAddress], + recipients: [recipientAddress], satoshiAmounts: [satoshisBeingUsed - 1], ))["vSize"] as int; final int vSizeForTwoOutPuts = (await buildTransaction( utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: [ - _recipientAddress, - await _getCurrentAddressForChain(1, DerivePathType.bip44), + recipientAddress, + await _getCurrentAddressForChain(1, DerivePathTypeExt.primaryFor(coin)), ], satoshiAmounts: [ satoshiAmountToSend, @@ -2666,8 +2336,8 @@ class BitcoinCashWallet extends CoinServiceAPI { ], // dust limit is the minimum amount a change output should be ))["vSize"] as int; //todo: check if print needed - debugPrint("vSizeForOneOutput $vSizeForOneOutput"); - debugPrint("vSizeForTwoOutPuts $vSizeForTwoOutPuts"); + // debugPrint("vSizeForOneOutput $vSizeForOneOutput"); + // debugPrint("vSizeForTwoOutPuts $vSizeForTwoOutPuts"); // Assume 1 output, only for recipient and no change var feeForOneOutput = estimateTxFee( @@ -2698,7 +2368,7 @@ class BitcoinCashWallet extends CoinServiceAPI { if (satoshisBeingUsed - satoshiAmountToSend > feeForOneOutput) { if (satoshisBeingUsed - satoshiAmountToSend > - feeForOneOutput + DUST_LIMIT) { + feeForOneOutput + DUST_LIMIT.raw.toInt()) { // Here, we know that theoretically, we may be able to include another output(change) but we first need to // factor in the value of this output in satoshis. int changeOutputSize = @@ -2706,13 +2376,13 @@ class BitcoinCashWallet extends CoinServiceAPI { // We check to see if the user can pay for the new transaction with 2 outputs instead of one. If they can and // the second output's size > 546 satoshis, we perform the mechanics required to properly generate and use a new // change address. - if (changeOutputSize > DUST_LIMIT && + if (changeOutputSize > DUST_LIMIT.raw.toInt() && satoshisBeingUsed - satoshiAmountToSend - changeOutputSize == feeForTwoOutputs) { // generate new change address if current change address has been used - await _checkChangeAddressForTransactions(DerivePathType.bip44); - final String newChangeAddress = - await _getCurrentAddressForChain(1, DerivePathType.bip44); + await _checkChangeAddressForTransactions(); + final String newChangeAddress = await _getCurrentAddressForChain( + 1, DerivePathTypeExt.primaryFor(coin)); int feeBeingPaid = satoshisBeingUsed - satoshiAmountToSend - changeOutputSize; @@ -2772,9 +2442,13 @@ class BitcoinCashWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": feeBeingPaid, "vSize": txn["vSize"], + "usedUTXOs": utxoObjectsToUse, }; return transactionObject; } else { @@ -2799,9 +2473,13 @@ class BitcoinCashWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": satoshisBeingUsed - satoshiAmountToSend, "vSize": txn["vSize"], + "usedUTXOs": utxoObjectsToUse, }; return transactionObject; } @@ -2828,9 +2506,13 @@ class BitcoinCashWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": satoshisBeingUsed - satoshiAmountToSend, "vSize": txn["vSize"], + "usedUTXOs": utxoObjectsToUse, }; return transactionObject; } @@ -2857,9 +2539,13 @@ class BitcoinCashWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": feeForOneOutput, "vSize": txn["vSize"], + "usedUTXOs": utxoObjectsToUse, }; return transactionObject; } else { @@ -2871,24 +2557,25 @@ class BitcoinCashWallet extends CoinServiceAPI { level: LogLevel.Warning); // try adding more outputs if (spendableOutputs.length > inputsBeingConsumed) { - return coinSelection(satoshiAmountToSend, selectedTxFeeRate, - _recipientAddress, isSendAll, - additionalOutputs: additionalOutputs + 1, utxos: utxos); + return coinSelection( + satoshiAmountToSend: satoshiAmountToSend, + selectedTxFeeRate: selectedTxFeeRate, + recipientAddress: recipientAddress, + isSendAll: isSendAll, + additionalOutputs: additionalOutputs + 1, + utxos: utxos, + coinControl: coinControl, + ); } return 2; } } - Future> fetchBuildTxData( - List utxosToUse, + Future> fetchBuildTxData( + List utxosToUse, ) async { // return data - Map results = {}; - Map> addressTxid = {}; - - // addresses to check - List addressesP2PKH = []; - List addressesP2SH = []; + List signingData = []; try { // Populating the addresses to check @@ -2902,164 +2589,111 @@ class BitcoinCashWallet extends CoinServiceAPI { for (final output in tx["vout"] as List) { final n = output["n"]; if (n != null && n == utxosToUse[i].vout) { - String address = output["scriptPubKey"]["addresses"][0] as String; - if (bitbox.Address.detectFormat(address) == + String address = + output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]["address"] as String; + if (bitbox.Address.detectFormat(address) != bitbox.Address.formatCashAddr) { - if (validateCashAddr(address)) { - address = bitbox.Address.toLegacyAddress(address); - } else { - throw Exception( - "Unsupported address found during fetchBuildTxData(): $address"); + try { + address = bitbox.Address.toCashAddress(address); + } catch (_) { + rethrow; } } - if (!addressTxid.containsKey(address)) { - addressTxid[address] = []; - } - (addressTxid[address] as List).add(txid); - switch (addressType(address: address)) { - case DerivePathType.bip44: - addressesP2PKH.add(address); - break; - case DerivePathType.bip49: - addressesP2SH.add(address); - break; - } + + utxosToUse[i] = utxosToUse[i].copyWith(address: address); } } + + final derivePathType = addressType(address: utxosToUse[i].address!); + + signingData.add( + SigningData( + derivePathType: derivePathType, + utxo: utxosToUse[i], + ), + ); } - // p2pkh / bip44 - final p2pkhLength = addressesP2PKH.length; - if (p2pkhLength > 0) { - final receiveDerivations = await _fetchDerivations( + Map> receiveDerivations = {}; + Map> changeDerivations = {}; + + for (final sd in signingData) { + String? pubKey; + String? wif; + + // fetch receiving derivations if null + receiveDerivations[sd.derivePathType] ??= await _fetchDerivations( chain: 0, - derivePathType: DerivePathType.bip44, + derivePathType: sd.derivePathType, ); - final changeDerivations = await _fetchDerivations( - chain: 1, - derivePathType: DerivePathType.bip44, - ); - for (int i = 0; i < p2pkhLength; i++) { - String address = addressesP2PKH[i]; + final receiveDerivation = + receiveDerivations[sd.derivePathType]![sd.utxo.address!]; - // receives - final receiveDerivation = receiveDerivations[address]; - // if a match exists it will not be null - if (receiveDerivation != null) { - final data = P2PKH( - data: PaymentData( - pubkey: Format.stringToUint8List( - receiveDerivation["pubKey"] as String)), - network: _network, - ).data; + if (receiveDerivation != null) { + pubKey = receiveDerivation["pubKey"] as String; + wif = receiveDerivation["wif"] as String; + } else { + // fetch change derivations if null + changeDerivations[sd.derivePathType] ??= await _fetchDerivations( + chain: 1, + derivePathType: sd.derivePathType, + ); + final changeDerivation = + changeDerivations[sd.derivePathType]![sd.utxo.address!]; + if (changeDerivation != null) { + pubKey = changeDerivation["pubKey"] as String; + wif = changeDerivation["wif"] as String; + } + } - for (String tx in addressTxid[address]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - receiveDerivation["wif"] as String, - network: _network, - ), - }; - } - } else { - // if its not a receive, check change - final changeDerivation = changeDerivations[address]; - // if a match exists it will not be null - if (changeDerivation != null) { - final data = P2PKH( + if (wif == null || pubKey == null) { + final address = await db.getAddress(walletId, sd.utxo.address!); + if (address?.derivationPath != null) { + final node = await Bip32Utils.getBip32Node( + (await mnemonicString)!, + (await mnemonicPassphrase)!, + _network, + address!.derivationPath!.value, + ); + + wif = node.toWIF(); + pubKey = Format.uint8listToString(node.publicKey); + } + } + + if (wif != null && pubKey != null) { + final PaymentData data; + final Uint8List? redeemScript; + + switch (sd.derivePathType) { + case DerivePathType.bip44: + case DerivePathType.bch44: + data = P2PKH( data: PaymentData( - pubkey: Format.stringToUint8List( - changeDerivation["pubKey"] as String)), + pubkey: Format.stringToUint8List(pubKey), + ), network: _network, ).data; + redeemScript = null; + break; - for (String tx in addressTxid[address]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - changeDerivation["wif"] as String, - network: _network, - ), - }; - } - } + default: + throw Exception("DerivePathType unsupported"); } + + final keyPair = ECPair.fromWIF( + wif, + network: _network, + ); + + sd.redeemScript = redeemScript; + sd.output = data.output; + sd.keyPair = keyPair; } } - // p2sh / bip49 - final p2shLength = addressesP2SH.length; - if (p2shLength > 0) { - final receiveDerivations = await _fetchDerivations( - chain: 0, - derivePathType: DerivePathType.bip49, - ); - final changeDerivations = await _fetchDerivations( - chain: 1, - derivePathType: DerivePathType.bip49, - ); - for (int i = 0; i < p2shLength; i++) { - // receives - final receiveDerivation = receiveDerivations[addressesP2SH[i]]; - // if a match exists it will not be null - if (receiveDerivation != null) { - final p2wpkh = P2WPKH( - data: PaymentData( - pubkey: Format.stringToUint8List( - receiveDerivation["pubKey"] as String)), - network: _network) - .data; - - final redeemScript = p2wpkh.output; - - final data = - P2SH(data: PaymentData(redeem: p2wpkh), network: _network).data; - - for (String tx in addressTxid[addressesP2SH[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - receiveDerivation["wif"] as String, - network: _network, - ), - "redeemScript": redeemScript, - }; - } - } else { - // if its not a receive, check change - final changeDerivation = changeDerivations[addressesP2SH[i]]; - // if a match exists it will not be null - if (changeDerivation != null) { - final p2wpkh = P2WPKH( - data: PaymentData( - pubkey: Format.stringToUint8List( - changeDerivation["pubKey"] as String)), - network: _network) - .data; - - final redeemScript = p2wpkh.output; - - final data = - P2SH(data: PaymentData(redeem: p2wpkh), network: _network) - .data; - - for (String tx in addressTxid[addressesP2SH[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - changeDerivation["wif"] as String, - network: _network, - ), - "redeemScript": redeemScript, - }; - } - } - } - } - } - - return results; + return signingData; } catch (e, s) { Logging.instance .log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error); @@ -3069,12 +2703,14 @@ class BitcoinCashWallet extends CoinServiceAPI { /// Builds and signs a transaction Future> buildTransaction({ - required List utxosToUse, - required Map utxoSigningData, + required List utxosToUse, + required List utxoSigningData, required List recipients, required List satoshiAmounts, }) async { - final builder = bitbox.Bitbox.transactionBuilder(); + final builder = bitbox.Bitbox.transactionBuilder( + testnet: coin == Coin.bitcoincashTestnet, + ); // retrieve address' utxos from the rest api List _utxos = @@ -3101,7 +2737,8 @@ class BitcoinCashWallet extends CoinServiceAPI { for (var utxo in _utxos) { // add the utxo as an input for the transaction builder.addInput(utxo.txid, utxo.vout); - final ec = utxoSigningData[utxo.txid]["keyPair"] as ECPair; + final ec = + utxoSigningData.firstWhere((e) => e.utxo.txid == utxo.txid).keyPair!; final bitboxEC = bitbox.ECPair.fromWIF(ec.toWIF()); @@ -3165,18 +2802,29 @@ class BitcoinCashWallet extends CoinServiceAPI { // clear cache await _cachedElectrumXClient.clearSharedTransactionCache(coin: coin); - // back up data - await _rescanBackup(); + // clear blockchain info + await db.deleteWalletBlockchainData(walletId); + await _deleteDerivations(); try { - final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic'); + final _mnemonic = await mnemonicString; + final _mnemonicPassphrase = await mnemonicPassphrase; + if (_mnemonicPassphrase == null) { + Logging.instance.log( + "Exception in fullRescan: mnemonic passphrase null, possible migration issue; if using internal builds, delete wallet and restore from seed, if using a release build, please file bug report", + level: LogLevel.Error); + } + await _recoverWalletFromBIP32SeedPhrase( - mnemonic: mnemonic!, + mnemonic: _mnemonic!, + mnemonicPassphrase: _mnemonicPassphrase!, maxUnusedAddressGap: maxUnusedAddressGap, maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + isRescan: true, ); longMutex = false; + await refresh(); Logging.instance.log("Full rescan complete!", level: LogLevel.Info); GlobalEventBus.instance.fire( WalletSyncStatusChangedEvent( @@ -3194,9 +2842,6 @@ class BitcoinCashWallet extends CoinServiceAPI { ), ); - // restore from backup - await _rescanRestore(); - longMutex = false; Logging.instance.log("Exception rethrown from fullRescan(): $e\n$s", level: LogLevel.Error); @@ -3204,259 +2849,26 @@ class BitcoinCashWallet extends CoinServiceAPI { } } - Future _rescanRestore() async { - Logging.instance.log("starting rescan restore", level: LogLevel.Info); - - // restore from backup - // p2pkh - final tempReceivingAddressesP2PKH = DB.instance - .get(boxName: walletId, key: 'receivingAddressesP2PKH_BACKUP'); - final tempChangeAddressesP2PKH = DB.instance - .get(boxName: walletId, key: 'changeAddressesP2PKH_BACKUP'); - final tempReceivingIndexP2PKH = DB.instance - .get(boxName: walletId, key: 'receivingIndexP2PKH_BACKUP'); - final tempChangeIndexP2PKH = DB.instance - .get(boxName: walletId, key: 'changeIndexP2PKH_BACKUP'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2PKH', - value: tempReceivingAddressesP2PKH); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2PKH', - value: tempChangeAddressesP2PKH); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2PKH', - value: tempReceivingIndexP2PKH); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2PKH', - value: tempChangeIndexP2PKH); - await DB.instance.delete( - key: 'receivingAddressesP2PKH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'changeAddressesP2PKH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'receivingIndexP2PKH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'changeIndexP2PKH_BACKUP', boxName: walletId); - - // p2Sh - final tempReceivingAddressesP2SH = DB.instance - .get(boxName: walletId, key: 'receivingAddressesP2SH_BACKUP'); - final tempChangeAddressesP2SH = DB.instance - .get(boxName: walletId, key: 'changeAddressesP2SH_BACKUP'); - final tempReceivingIndexP2SH = DB.instance - .get(boxName: walletId, key: 'receivingIndexP2SH_BACKUP'); - final tempChangeIndexP2SH = DB.instance - .get(boxName: walletId, key: 'changeIndexP2SH_BACKUP'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2SH', - value: tempReceivingAddressesP2SH); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2SH', - value: tempChangeAddressesP2SH); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2SH', - value: tempReceivingIndexP2SH); - await DB.instance.put( - boxName: walletId, key: 'changeIndexP2SH', value: tempChangeIndexP2SH); - await DB.instance.delete( - key: 'receivingAddressesP2SH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'changeAddressesP2SH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'receivingIndexP2SH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'changeIndexP2SH_BACKUP', boxName: walletId); - + Future _deleteDerivations() async { // P2PKH derivations - final p2pkhReceiveDerivationsString = await _secureStore.read( - key: "${walletId}_receiveDerivationsP2PKH_BACKUP"); - final p2pkhChangeDerivationsString = await _secureStore.read( - key: "${walletId}_changeDerivationsP2PKH_BACKUP"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2PKH", - value: p2pkhReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2PKH", - value: p2pkhChangeDerivationsString); - - await _secureStore.delete( - key: "${walletId}_receiveDerivationsP2PKH_BACKUP"); - await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH_BACKUP"); - - // P2SH derivations - final p2shReceiveDerivationsString = await _secureStore.read( - key: "${walletId}_receiveDerivationsP2SH_BACKUP"); - final p2shChangeDerivationsString = await _secureStore.read( - key: "${walletId}_changeDerivationsP2SH_BACKUP"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2SH", - value: p2shReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2SH", - value: p2shChangeDerivationsString); - - await _secureStore.delete(key: "${walletId}_receiveDerivationsP2SH_BACKUP"); - await _secureStore.delete(key: "${walletId}_changeDerivationsP2SH_BACKUP"); - - // UTXOs - final utxoData = DB.instance - .get(boxName: walletId, key: 'latest_utxo_model_BACKUP'); - await DB.instance.put( - boxName: walletId, key: 'latest_utxo_model', value: utxoData); - await DB.instance - .delete(key: 'latest_utxo_model_BACKUP', boxName: walletId); - - Logging.instance.log("rescan restore complete", level: LogLevel.Info); - } - - Future _rescanBackup() async { - Logging.instance.log("starting rescan backup", level: LogLevel.Info); - - // backup current and clear data - // p2pkh - final tempReceivingAddressesP2PKH = DB.instance - .get(boxName: walletId, key: 'receivingAddressesP2PKH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2PKH_BACKUP', - value: tempReceivingAddressesP2PKH); - await DB.instance - .delete(key: 'receivingAddressesP2PKH', boxName: walletId); - - final tempChangeAddressesP2PKH = DB.instance - .get(boxName: walletId, key: 'changeAddressesP2PKH'); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2PKH_BACKUP', - value: tempChangeAddressesP2PKH); - await DB.instance - .delete(key: 'changeAddressesP2PKH', boxName: walletId); - - final tempReceivingIndexP2PKH = - DB.instance.get(boxName: walletId, key: 'receivingIndexP2PKH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2PKH_BACKUP', - value: tempReceivingIndexP2PKH); - await DB.instance - .delete(key: 'receivingIndexP2PKH', boxName: walletId); - - final tempChangeIndexP2PKH = - DB.instance.get(boxName: walletId, key: 'changeIndexP2PKH'); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2PKH_BACKUP', - value: tempChangeIndexP2PKH); - await DB.instance - .delete(key: 'changeIndexP2PKH', boxName: walletId); - - // p2sh - final tempReceivingAddressesP2SH = DB.instance - .get(boxName: walletId, key: 'receivingAddressesP2SH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2SH_BACKUP', - value: tempReceivingAddressesP2SH); - await DB.instance - .delete(key: 'receivingAddressesP2SH', boxName: walletId); - - final tempChangeAddressesP2SH = - DB.instance.get(boxName: walletId, key: 'changeAddressesP2SH'); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2SH_BACKUP', - value: tempChangeAddressesP2SH); - await DB.instance - .delete(key: 'changeAddressesP2SH', boxName: walletId); - - final tempReceivingIndexP2SH = - DB.instance.get(boxName: walletId, key: 'receivingIndexP2SH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2SH_BACKUP', - value: tempReceivingIndexP2SH); - await DB.instance - .delete(key: 'receivingIndexP2SH', boxName: walletId); - - final tempChangeIndexP2SH = - DB.instance.get(boxName: walletId, key: 'changeIndexP2SH'); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2SH_BACKUP', - value: tempChangeIndexP2SH); - await DB.instance - .delete(key: 'changeIndexP2SH', boxName: walletId); - - // P2PKH derivations - final p2pkhReceiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivationsP2PKH"); - final p2pkhChangeDerivationsString = - await _secureStore.read(key: "${walletId}_changeDerivationsP2PKH"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2PKH_BACKUP", - value: p2pkhReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2PKH_BACKUP", - value: p2pkhChangeDerivationsString); - await _secureStore.delete(key: "${walletId}_receiveDerivationsP2PKH"); await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH"); // P2SH derivations - final p2shReceiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivationsP2SH"); - final p2shChangeDerivationsString = - await _secureStore.read(key: "${walletId}_changeDerivationsP2SH"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2SH_BACKUP", - value: p2shReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2SH_BACKUP", - value: p2shChangeDerivationsString); - await _secureStore.delete(key: "${walletId}_receiveDerivationsP2SH"); await _secureStore.delete(key: "${walletId}_changeDerivationsP2SH"); - - // UTXOs - final utxoData = - DB.instance.get(boxName: walletId, key: 'latest_utxo_model'); - await DB.instance.put( - boxName: walletId, key: 'latest_utxo_model_BACKUP', value: utxoData); - await DB.instance - .delete(key: 'latest_utxo_model', boxName: walletId); - - Logging.instance.log("rescan backup complete", level: LogLevel.Info); } @override set isFavorite(bool markFavorite) { - DB.instance.put( - boxName: walletId, key: "isFavorite", value: markFavorite); + _isFavorite = markFavorite; + updateCachedIsFavorite(markFavorite); } @override - bool get isFavorite { - try { - return DB.instance.get(boxName: walletId, key: "isFavorite") - as bool; - } catch (e, s) { - Logging.instance.log( - "isFavorite fetch failed (returning false by default): $e\n$s", - level: LogLevel.Error); - return false; - } - } + bool get isFavorite => _isFavorite ??= getCachedIsFavorite(); + + bool? _isFavorite; @override bool get isRefreshing => refreshMutex; @@ -3468,42 +2880,49 @@ class BitcoinCashWallet extends CoinServiceAPI { (isActive) => this.isActive = isActive; @override - Future estimateFeeFor(int satoshiAmount, int feeRate) async { - final available = - Format.decimalAmountToSatoshis(await availableBalance, coin); + Future estimateFeeFor(Amount amount, int feeRate) async { + final available = balance.spendable; - if (available == satoshiAmount) { - return satoshiAmount - sweepAllEstimate(feeRate); - } else if (satoshiAmount <= 0 || satoshiAmount > available) { + if (available == amount) { + return amount - (await sweepAllEstimate(feeRate)); + } else if (amount <= Amount.zero || amount > available) { return roughFeeEstimate(1, 2, feeRate); } - int runningBalance = 0; + Amount runningBalance = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); int inputCount = 0; - for (final output in outputsList) { - runningBalance += output.value; - inputCount++; - if (runningBalance > satoshiAmount) { - break; + for (final output in (await utxos)) { + if (!output.isBlocked) { + runningBalance += Amount( + rawValue: BigInt.from(output.value), + fractionDigits: coin.decimals, + ); + inputCount++; + if (runningBalance > amount) { + break; + } } } final oneOutPutFee = roughFeeEstimate(inputCount, 1, feeRate); final twoOutPutFee = roughFeeEstimate(inputCount, 2, feeRate); - if (runningBalance - satoshiAmount > oneOutPutFee) { - if (runningBalance - satoshiAmount > oneOutPutFee + DUST_LIMIT) { - final change = runningBalance - satoshiAmount - twoOutPutFee; + if (runningBalance - amount > oneOutPutFee) { + if (runningBalance - amount > oneOutPutFee + DUST_LIMIT) { + final change = runningBalance - amount - twoOutPutFee; if (change > DUST_LIMIT && - runningBalance - satoshiAmount - change == twoOutPutFee) { - return runningBalance - satoshiAmount - change; + runningBalance - amount - change == twoOutPutFee) { + return runningBalance - amount - change; } else { - return runningBalance - satoshiAmount; + return runningBalance - amount; } } else { - return runningBalance - satoshiAmount; + return runningBalance - amount; } - } else if (runningBalance - satoshiAmount == oneOutPutFee) { + } else if (runningBalance - amount == oneOutPutFee) { return oneOutPutFee; } else { return twoOutPutFee; @@ -3511,16 +2930,20 @@ class BitcoinCashWallet extends CoinServiceAPI { } // TODO: correct formula for bch? - int roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { - return ((181 * inputCount) + (34 * outputCount) + 10) * - (feeRatePerKB / 1000).ceil(); + Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { + return Amount( + rawValue: BigInt.from(((181 * inputCount) + (34 * outputCount) + 10) * + (feeRatePerKB / 1000).ceil()), + fractionDigits: coin.decimals, + ); } - int sweepAllEstimate(int feeRate) { + Future sweepAllEstimate(int feeRate) async { int available = 0; int inputCount = 0; - for (final output in outputsList) { - if (output.status.confirmed) { + for (final output in (await utxos)) { + if (!output.isBlocked && + output.isConfirmed(storedChainHeight, MINIMUM_CONFIRMATIONS)) { available += output.value; inputCount++; } @@ -3529,30 +2952,26 @@ class BitcoinCashWallet extends CoinServiceAPI { // transaction will only have 1 output minus the fee final estimatedFee = roughFeeEstimate(inputCount, 1, feeRate); - return available - estimatedFee; + return Amount( + rawValue: BigInt.from(available), + fractionDigits: coin.decimals, + ) - + estimatedFee; } @override Future generateNewAddress() async { try { - await _incrementAddressIndexForChain( - 0, DerivePathType.bip44); // First increment the receiving index - final newReceivingIndex = DB.instance.get( - boxName: walletId, - key: 'receivingIndexP2PKH') as int; // Check the new receiving index + final currentReceiving = await _currentReceivingAddress; + + final newReceivingIndex = currentReceiving.derivationIndex + 1; + + // Use new index to derive a new receiving address final newReceivingAddress = await _generateAddressForChain( - 0, - newReceivingIndex, - DerivePathType - .bip44); // Use new index to derive a new receiving address - await _addToAddressesArrayForChain( - newReceivingAddress, - 0, - DerivePathType - .bip44); // Add that new receiving address to the array of receiving addresses - var newaddr = await _getCurrentAddressForChain(0, DerivePathType.bip44); - _currentReceivingAddressP2PKH = Future( - () => newaddr); // Set the new receiving address that the service + 0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin)); + + // Add that new receiving address + await db.putAddress(newReceivingAddress); return true; } catch (e, s) { @@ -3562,6 +2981,17 @@ class BitcoinCashWallet extends CoinServiceAPI { return false; } } + + @override + Future get xpub async { + final node = await Bip32Utils.getBip32Root( + (await mnemonic).join(" "), + await mnemonicPassphrase ?? "", + _network, + ); + + return node.neutered().toBase58(); + } } // Bitcoincash Network diff --git a/lib/services/coins/coin_service.dart b/lib/services/coins/coin_service.dart index 41dbed42b..48fa59630 100644 --- a/lib/services/coins/coin_service.dart +++ b/lib/services/coins/coin_service.dart @@ -1,24 +1,27 @@ -import 'package:decimal/decimal.dart'; import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; -import 'package:stackwallet/models/models.dart'; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; import 'package:stackwallet/models/node_model.dart'; +import 'package:stackwallet/models/paymint/fee_object_model.dart'; import 'package:stackwallet/services/coins/bitcoin/bitcoin_wallet.dart'; import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart'; import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart'; +import 'package:stackwallet/services/coins/ecash/ecash_wallet.dart'; import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart'; +import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; +import 'package:stackwallet/services/coins/litecoin/litecoin_wallet.dart'; import 'package:stackwallet/services/coins/monero/monero_wallet.dart'; import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart'; import 'package:stackwallet/services/coins/particl/particl_wallet.dart'; import 'package:stackwallet/services/coins/wownero/wownero_wallet.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/prefs.dart'; -import 'litecoin/litecoin_wallet.dart'; - abstract class CoinServiceAPI { CoinServiceAPI(); @@ -173,12 +176,21 @@ abstract class CoinServiceAPI { // tracker: tracker, ); + case Coin.ethereum: + return EthereumWallet( + walletId: walletId, + walletName: walletName, + coin: coin, + secureStore: secureStorageInterface, + tracker: tracker, + ); + case Coin.monero: return MoneroWallet( walletId: walletId, walletName: walletName, coin: coin, - secureStore: secureStorageInterface, + secureStorage: secureStorageInterface, // tracker: tracker, ); @@ -197,7 +209,7 @@ abstract class CoinServiceAPI { walletId: walletId, walletName: walletName, coin: coin, - secureStore: secureStorageInterface, + secureStorage: secureStorageInterface, // tracker: tracker, ); @@ -222,6 +234,17 @@ abstract class CoinServiceAPI { cachedClient: cachedClient, tracker: tracker, ); + + case Coin.eCash: + return ECashWallet( + walletId: walletId, + walletName: walletName, + coin: coin, + secureStore: secureStorageInterface, + client: client, + cachedClient: cachedClient, + tracker: tracker, + ); } } @@ -234,36 +257,21 @@ abstract class CoinServiceAPI { Future> prepareSend({ required String address, - required int satoshiAmount, + required Amount amount, Map? args, }); Future confirmSend({required Map txData}); - /// create and submit tx to network - /// - /// Returns the txid of the sent tx - /// will throw exceptions on failure - Future send( - {required String toAddress, - required int amount, - Map args}); - Future get fees; Future get maxFee; Future get currentReceivingAddress; - // Future get currentLegacyReceivingAddress; - Future get availableBalance; - Future get pendingBalance; - Future get totalBalance; - Future get balanceMinusMaxFee; + Balance get balance; - Future> get allOwnAddresses; - - Future get transactionData; - Future> get unspentOutputs; + Future> get transactions; + Future> get utxos; Future refresh(); @@ -278,11 +286,14 @@ abstract class CoinServiceAPI { bool validateAddress(String address); Future> get mnemonic; + Future get mnemonicString; + Future get mnemonicPassphrase; Future testNetworkConnection(); Future recoverFromMnemonic({ required String mnemonic, + String? mnemonicPassphrase, required int maxUnusedAddressGap, required int maxNumberOfIndexesToCheck, required int height, @@ -301,10 +312,54 @@ abstract class CoinServiceAPI { bool get isConnected; - Future estimateFeeFor(int satoshiAmount, int feeRate); + Future estimateFeeFor(Amount amount, int feeRate); Future generateNewAddress(); // used for electrumx coins Future updateSentCachedTxData(Map txData); + + int get storedChainHeight; + + // Certain outputs return address as an array/list of strings like List ["addresses"][0], some return it as a string like String ["address"] + String? getAddress(dynamic output) { + // Julian's code from https://github.com/cypherstack/stack_wallet/blob/35a8172d35f1b5cdbd22f0d56c4db02f795fd032/lib/services/coins/coin_paynym_extension.dart#L170 wins codegolf for this, I'd love to commit it now but need to retest this section ... should make unit tests for this case + // final String? address = output["scriptPubKey"]?["addresses"]?[0] as String? ?? output["scriptPubKey"]?["address"] as String?; + String? address; + if (output.containsKey('scriptPubKey') as bool) { + // Make sure the key exists before using it + if (output["scriptPubKey"].containsKey('address') as bool) { + address = output["scriptPubKey"]["address"] as String?; + } else if (output["scriptPubKey"].containsKey('addresses') as bool) { + address = output["scriptPubKey"]["addresses"][0] as String?; + // TODO determine cases in which there are multiple addresses in the array + } + } /*else { + // TODO detect cases in which no scriptPubKey exists + Logging.instance.log("output type not detected; output: ${output}", + level: LogLevel.Info); + }*/ + + return address; + } + + // Firo wants an array/list of address strings like List + List? getAddresses(dynamic output) { + // Inspired by Julian's code as referenced above, need to test before committing + // final List? addresses = output["scriptPubKey"]?["addresses"] as List? ?? [output["scriptPubKey"]?["address"]] as List?; + List? addresses; + if (output.containsKey('scriptPubKey') as bool) { + if (output["scriptPubKey"].containsKey('addresses') as bool) { + addresses = output["scriptPubKey"]["addresses"] as List?; + } else if (output["scriptPubKey"].containsKey('address') as bool) { + addresses = [output["scriptPubKey"]["address"]]; + } + } /*else { + // TODO detect cases in which no scriptPubKey exists + Logging.instance.log("output type not detected; output: ${output}", + level: LogLevel.Info); + }*/ + + return addresses; + } } diff --git a/lib/services/coins/dogecoin/dogecoin_wallet.dart b/lib/services/coins/dogecoin/dogecoin_wallet.dart index 5b0da3fb3..05170a883 100644 --- a/lib/services/coins/dogecoin/dogecoin_wallet.dart +++ b/lib/services/coins/dogecoin/dogecoin_wallet.dart @@ -1,82 +1,72 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'dart:typed_data'; import 'package:bech32/bech32.dart'; import 'package:bip32/bip32.dart' as bip32; import 'package:bip39/bip39.dart' as bip39; import 'package:bitcoindart/bitcoindart.dart'; +import 'package:bitcoindart/bitcoindart.dart' as btc_dart; import 'package:bs58check/bs58check.dart' as bs58check; -import 'package:crypto/crypto.dart'; -import 'package:decimal/decimal.dart'; -import 'package:devicelocale/devicelocale.dart'; import 'package:flutter/foundation.dart'; -import 'package:http/http.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; -import 'package:stackwallet/hive/db.dart'; -import 'package:stackwallet/models/models.dart' as models; +import 'package:stackwallet/exceptions/electrumx/no_such_transaction.dart'; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; import 'package:stackwallet/models/paymint/fee_object_model.dart'; -import 'package:stackwallet/models/paymint/transactions_model.dart'; -import 'package:stackwallet/models/paymint/utxo_model.dart'; +import 'package:stackwallet/models/signing_data.dart'; import 'package:stackwallet/services/coins/coin_service.dart'; import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +import 'package:stackwallet/services/mixins/coin_control_interface.dart'; +import 'package:stackwallet/services/mixins/electrum_x_parsing.dart'; +import 'package:stackwallet/services/mixins/wallet_cache.dart'; +import 'package:stackwallet/services/mixins/wallet_db.dart'; +import 'package:stackwallet/services/mixins/xpubable.dart'; import 'package:stackwallet/services/node_service.dart'; -import 'package:stackwallet/services/notifications_api.dart'; -import 'package:stackwallet/services/price.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; -import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/address_utils.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/bip32_utils.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/default_nodes.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart'; import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; +import 'package:stackwallet/widgets/crypto_notifications.dart'; import 'package:tuple/tuple.dart'; import 'package:uuid/uuid.dart'; -const int MINIMUM_CONFIRMATIONS = 3; -const int DUST_LIMIT = 1000000; +const int MINIMUM_CONFIRMATIONS = 1; +final Amount DUST_LIMIT = Amount( + rawValue: BigInt.from(1000000), + fractionDigits: Coin.particl.decimals, +); const String GENESIS_HASH_MAINNET = "1a91e3dace36e2be3bf030a65679fe821aa1d6ef92e7c9902eb318182c355691"; const String GENESIS_HASH_TESTNET = "bb0a78264637406b6360aad926284d544d7049f45189db5664f3c4d07350559e"; -enum DerivePathType { bip44 } - -bip32.BIP32 getBip32Node(int chain, int index, String mnemonic, - NetworkType network, DerivePathType derivePathType) { - final root = getBip32Root(mnemonic, network); - - final node = getBip32NodeFromRoot(chain, index, root, derivePathType); - return node; -} - -/// wrapper for compute() -bip32.BIP32 getBip32NodeWrapper( - Tuple5 args, -) { - return getBip32Node( - args.item1, - args.item2, - args.item3, - args.item4, - args.item5, - ); -} - -bip32.BIP32 getBip32NodeFromRoot( - int chain, int index, bip32.BIP32 root, DerivePathType derivePathType) { +String constructDerivePath({ + required DerivePathType derivePathType, + required int networkWIF, + int account = 0, + required int chain, + required int index, +}) { String coinType; - switch (root.network.wif) { + switch (networkWIF) { case 0x9e: // doge mainnet wif coinType = "3"; // doge mainnet break; @@ -84,58 +74,87 @@ bip32.BIP32 getBip32NodeFromRoot( coinType = "1"; // doge testnet break; default: - throw Exception("Invalid Dogecoin network type used!"); + throw Exception("Invalid Dogecoin network wif used!"); } + + int purpose; switch (derivePathType) { case DerivePathType.bip44: - return root.derivePath("m/44'/$coinType'/0'/$chain/$index"); + purpose = 44; + break; default: - throw Exception("DerivePathType must not be null."); + throw Exception("DerivePathType $derivePathType not supported"); } + + return "m/$purpose'/$coinType'/$account'/$chain/$index"; } -/// wrapper for compute() -bip32.BIP32 getBip32NodeFromRootWrapper( - Tuple4 args, -) { - return getBip32NodeFromRoot( - args.item1, - args.item2, - args.item3, - args.item4, - ); -} +class DogecoinWallet extends CoinServiceAPI + with WalletCache, WalletDB, ElectrumXParsing, CoinControlInterface + implements XPubAble { + DogecoinWallet({ + required String walletId, + required String walletName, + required Coin coin, + required ElectrumX client, + required CachedElectrumX cachedClient, + required TransactionNotificationTracker tracker, + required SecureStorageInterface secureStore, + MainDB? mockableOverride, + }) { + txTracker = tracker; + _walletId = walletId; + _walletName = walletName; + _coin = coin; + _electrumXClient = client; + _cachedElectrumXClient = cachedClient; + _secureStore = secureStore; + initCache(walletId, coin); + initWalletDB(mockableOverride: mockableOverride); + initCoinControlInterface( + walletId: walletId, + walletName: walletName, + coin: coin, + db: db, + getChainHeight: () => chainHeight, + refreshedBalanceCallback: (balance) async { + _balance = balance; + await updateCachedBalance(_balance!); + }, + ); -bip32.BIP32 getBip32Root(String mnemonic, NetworkType network) { - final seed = bip39.mnemonicToSeed(mnemonic); - final networkType = bip32.NetworkType( - wif: network.wif, - bip32: bip32.Bip32Type( - public: network.bip32.public, - private: network.bip32.private, - ), - ); + // paynym stuff + // initPaynymWalletInterface( + // walletId: walletId, + // walletName: walletName, + // network: network, + // coin: coin, + // db: db, + // electrumXClient: electrumXClient, + // getMnemonic: () => mnemonic, + // getChainHeight: () => chainHeight, + // getCurrentChangeAddress: () => currentChangeAddress, + // estimateTxFee: estimateTxFee, + // prepareSend: prepareSend, + // getTxCount: getTxCount, + // fetchBuildTxData: fetchBuildTxData, + // refresh: refresh, + // checkChangeAddressForTransactions: checkChangeAddressForTransactions, + // addDerivation: addDerivation, + // addDerivations: addDerivations, + // ); + } - final root = bip32.BIP32.fromSeed(seed, networkType); - return root; -} - -/// wrapper for compute() -bip32.BIP32 getBip32RootWrapper(Tuple2 args) { - return getBip32Root(args.item1, args.item2); -} - -class DogecoinWallet extends CoinServiceAPI { static const integrationTestFlag = bool.fromEnvironment("IS_INTEGRATION_TEST"); final _prefs = Prefs.instance; Timer? timer; - late Coin _coin; + late final Coin _coin; late final TransactionNotificationTracker txTracker; - NetworkType get _network { + NetworkType get network { switch (coin) { case Coin.dogecoin: return dogecoin; @@ -146,66 +165,51 @@ class DogecoinWallet extends CoinServiceAPI { } } - List outputsList = []; + @override + Future> get utxos => db.getUTXOs(walletId).findAll(); + + @override + Future> get transactions => db + .getTransactions(walletId) + .filter() + .not() + .group((q) => q + .subTypeEqualTo(isar_models.TransactionSubType.bip47Notification) + .and() + .typeEqualTo(isar_models.TransactionType.incoming)) + .sortByTimestampDesc() + .findAll(); @override Coin get coin => _coin; @override - Future> get allOwnAddresses => - _allOwnAddresses ??= _fetchAllOwnAddresses(); - Future>? _allOwnAddresses; + Future get currentReceivingAddress async => + (await _currentReceivingAddress).value; - Future? _utxoData; - Future get utxoData => _utxoData ??= _fetchUtxoData(); + Future get _currentReceivingAddress async => + (await db + .getAddresses(walletId) + .filter() + .typeEqualTo(isar_models.AddressType.p2pkh) + .subTypeEqualTo(isar_models.AddressSubType.receiving) + .sortByDerivationIndexDesc() + .findFirst()) ?? + await _generateAddressForChain(0, 0, DerivePathTypeExt.primaryFor(coin)); - @override - Future> get unspentOutputs async => - (await utxoData).unspentOutputArray; + // @override + Future get currentChangeAddress async => + (await _currentChangeAddress).value; - @override - Future get availableBalance async { - final data = await utxoData; - return Format.satoshisToAmount( - data.satoshiBalance - data.satoshiBalanceUnconfirmed, - coin: coin); - } - - @override - Future get pendingBalance async { - final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalanceUnconfirmed, coin: coin); - } - - @override - Future get balanceMinusMaxFee async => - (await availableBalance) - - (Decimal.fromInt((await maxFee)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(); - - @override - Future get totalBalance async { - if (!isActive) { - final totalBalance = DB.instance - .get(boxName: walletId, key: 'totalBalance') as int?; - if (totalBalance == null) { - final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalance, coin: coin); - } else { - return Format.satoshisToAmount(totalBalance, coin: coin); - } - } - final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalance, coin: coin); - } - - @override - Future get currentReceivingAddress => - _currentReceivingAddressP2PKH ??= - _getCurrentAddressForChain(0, DerivePathType.bip44); - - Future? _currentReceivingAddressP2PKH; + Future get _currentChangeAddress async => + (await db + .getAddresses(walletId) + .filter() + .typeEqualTo(isar_models.AddressType.p2pkh) + .subTypeEqualTo(isar_models.AddressSubType.change) + .sortByDerivationIndexDesc() + .findFirst()) ?? + await _generateAddressForChain(1, 0, DerivePathTypeExt.primaryFor(coin)); @override Future exit() async { @@ -226,36 +230,44 @@ class DogecoinWallet extends CoinServiceAPI { @override Future get maxFee async { - final fee = (await fees).fast; - final satsFee = Format.satoshisToAmount(fee, coin: coin) * - Decimal.fromInt(Constants.satsPerCoin(coin)); - return satsFee.floor().toBigInt().toInt(); + throw UnimplementedError("Not used in dogecoin"); } @override Future> get mnemonic => _getMnemonicList(); + @override + Future get mnemonicString => + _secureStore.read(key: '${_walletId}_mnemonic'); + + @override + Future get mnemonicPassphrase => _secureStore.read( + key: '${_walletId}_mnemonicPassphrase', + ); + Future get chainHeight async { try { final result = await _electrumXClient.getBlockHeadTip(); - return result["height"] as int; + final height = result["height"] as int; + await updateCachedChainHeight(height); + if (height > storedChainHeight) { + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "Updated current chain height in $walletId $walletName!", + walletId, + ), + ); + } + return height; } catch (e, s) { Logging.instance.log("Exception caught in chainHeight: $e\n$s", level: LogLevel.Error); - return -1; + return storedChainHeight; } } - Future get storedChainHeight async { - final storedHeight = DB.instance - .get(boxName: walletId, key: "storedChainHeight") as int?; - return storedHeight ?? 0; - } - - Future updateStoredChainHeight({required int newHeight}) async { - await DB.instance.put( - boxName: walletId, key: "storedChainHeight", value: newHeight); - } + @override + int get storedChainHeight => getCachedChainHeight(); DerivePathType addressType({required String address}) { Uint8List? decodeBase58; @@ -266,7 +278,7 @@ class DogecoinWallet extends CoinServiceAPI { // Base58check decode fail } if (decodeBase58 != null) { - if (decodeBase58[0] == _network.pubKeyHash) { + if (decodeBase58[0] == network.pubKeyHash) { // P2PKH return DerivePathType.bip44; } @@ -277,7 +289,7 @@ class DogecoinWallet extends CoinServiceAPI { } catch (err) { // Bech32 decode fail } - if (_network.bech32 != decodeBech32!.hrp) { + if (network.bech32 != decodeBech32!.hrp) { throw ArgumentError('Invalid prefix or Network mismatch'); } if (decodeBech32.version != 0) { @@ -292,6 +304,7 @@ class DogecoinWallet extends CoinServiceAPI { @override Future recoverFromMnemonic({ required String mnemonic, + String? mnemonicPassphrase, required int maxUnusedAddressGap, required int maxNumberOfIndexesToCheck, required int height, @@ -322,14 +335,20 @@ class DogecoinWallet extends CoinServiceAPI { } // check to make sure we aren't overwriting a mnemonic // this should never fail - if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) { + if ((await mnemonicString) != null || + (await this.mnemonicPassphrase) != null) { longMutex = false; throw Exception("Attempted to overwrite mnemonic on restore!"); } await _secureStore.write( key: '${_walletId}_mnemonic', value: mnemonic.trim()); + await _secureStore.write( + key: '${_walletId}_mnemonicPassphrase', + value: mnemonicPassphrase ?? "", + ); await _recoverWalletFromBIP32SeedPhrase( mnemonic: mnemonic.trim(), + mnemonicPassphrase: mnemonicPassphrase ?? "", maxUnusedAddressGap: maxUnusedAddressGap, maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, ); @@ -354,8 +373,8 @@ class DogecoinWallet extends CoinServiceAPI { int txCountBatchSize, bip32.BIP32 root, DerivePathType type, - int account) async { - List addressArray = []; + int chain) async { + List addressArray = []; int returningIndex = -1; Map> derivations = {}; int gapCounter = 0; @@ -364,7 +383,7 @@ class DogecoinWallet extends CoinServiceAPI { index += txCountBatchSize) { List iterationsAddressArray = []; Logging.instance.log( - "index: $index, \t GapCounter $account ${type.name}: $gapCounter", + "index: $index, \t GapCounter $chain ${type.name}: $gapCounter", level: LogLevel.Info); final _id = "k_$index"; @@ -372,26 +391,35 @@ class DogecoinWallet extends CoinServiceAPI { final Map receivingNodes = {}; for (int j = 0; j < txCountBatchSize; j++) { - final node = await compute( - getBip32NodeFromRootWrapper, - Tuple4( - account, - index + j, - root, - type, - ), + final derivePath = constructDerivePath( + derivePathType: type, + networkWIF: root.network.wif, + chain: chain, + index: index + j, ); - String? address; + final node = await Bip32Utils.getBip32NodeFromRoot(root, derivePath); + + isar_models.Address address; switch (type) { case DerivePathType.bip44: - address = P2PKH( - data: PaymentData(pubkey: node.publicKey), - network: _network) + final addressString = P2PKH( + data: PaymentData(pubkey: node.publicKey), network: network) .data .address!; + address = isar_models.Address( + walletId: walletId, + value: addressString, + publicKey: node.publicKey, + type: isar_models.AddressType.p2pkh, + derivationIndex: index + j, + derivationPath: isar_models.DerivationPath()..value = derivePath, + subType: chain == 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change, + ); break; default: - throw Exception("No Path type $type exists"); + throw Exception("DerivePathType $type not supported"); } receivingNodes.addAll({ "${_id}_$j": { @@ -400,7 +428,7 @@ class DogecoinWallet extends CoinServiceAPI { } }); txCountCallArgs.addAll({ - "${_id}_$j": address, + "${_id}_$j": address.value, }); } @@ -412,15 +440,16 @@ class DogecoinWallet extends CoinServiceAPI { int count = counts["${_id}_$k"]!; if (count > 0) { final node = receivingNodes["${_id}_$k"]; + final address = node["address"] as isar_models.Address; // add address to array - addressArray.add(node["address"] as String); - iterationsAddressArray.add(node["address"] as String); + addressArray.add(address); + iterationsAddressArray.add(address.value); // set current index returningIndex = index + k; // reset counter gapCounter = 0; // add info to derivations - derivations[node["address"] as String] = { + derivations[address.value] = { "pubKey": Format.uint8listToString( (node["node"] as bip32.BIP32).publicKey), "wif": (node["node"] as bip32.BIP32).toWIF(), @@ -464,20 +493,26 @@ class DogecoinWallet extends CoinServiceAPI { Future _recoverWalletFromBIP32SeedPhrase({ required String mnemonic, + required String mnemonicPassphrase, int maxUnusedAddressGap = 20, int maxNumberOfIndexesToCheck = 1000, + bool isRescan = false, }) async { longMutex = true; Map> p2pkhReceiveDerivations = {}; Map> p2pkhChangeDerivations = {}; - final root = await compute(getBip32RootWrapper, Tuple2(mnemonic, _network)); + final root = await Bip32Utils.getBip32Root( + mnemonic, + mnemonicPassphrase, + network, + ); - List p2pkhReceiveAddressArray = []; + List p2pkhReceiveAddressArray = []; int p2pkhReceiveIndex = -1; - List p2pkhChangeAddressArray = []; + List p2pkhChangeAddressArray = []; int p2pkhChangeIndex = -1; // actual size is 12 due to p2pkh so 12x1 @@ -502,13 +537,13 @@ class DogecoinWallet extends CoinServiceAPI { ]); p2pkhReceiveAddressArray = - (await resultReceive44)['addressArray'] as List; + (await resultReceive44)['addressArray'] as List; p2pkhReceiveIndex = (await resultReceive44)['index'] as int; p2pkhReceiveDerivations = (await resultReceive44)['derivations'] as Map>; p2pkhChangeAddressArray = - (await resultChange44)['addressArray'] as List; + (await resultChange44)['addressArray'] as List; p2pkhChangeIndex = (await resultChange44)['index'] as int; p2pkhChangeDerivations = (await resultChange44)['derivations'] as Map>; @@ -533,7 +568,6 @@ class DogecoinWallet extends CoinServiceAPI { final address = await _generateAddressForChain(0, 0, DerivePathType.bip44); p2pkhReceiveAddressArray.add(address); - p2pkhReceiveIndex = 0; } // If restoring a wallet that never sent any funds with change, then set changeArray @@ -542,27 +576,38 @@ class DogecoinWallet extends CoinServiceAPI { final address = await _generateAddressForChain(1, 0, DerivePathType.bip44); p2pkhChangeAddressArray.add(address); - p2pkhChangeIndex = 0; + } + if (isRescan) { + await db.updateOrPutAddresses([ + ...p2pkhReceiveAddressArray, + ...p2pkhChangeAddressArray, + ]); + } else { + await db.putAddresses([ + ...p2pkhReceiveAddressArray, + ...p2pkhChangeAddressArray, + ]); } - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2PKH', - value: p2pkhReceiveAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2PKH', - value: p2pkhChangeAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2PKH', - value: p2pkhReceiveIndex); - await DB.instance.put( - boxName: walletId, key: 'changeIndexP2PKH', value: p2pkhChangeIndex); - await DB.instance - .put(boxName: walletId, key: "id", value: _walletId); - await DB.instance - .put(boxName: walletId, key: "isFavorite", value: false); + // paynym stuff + // // generate to ensure notification address is in db before refreshing transactions + // await getMyNotificationAddress(DerivePathType.bip44); + // + // // refresh transactions to pick up any received notification transactions + // await _refreshTransactions(); + // + // // restore paynym transactions + // await restoreAllHistory( + // maxUnusedAddressGap: maxUnusedAddressGap, + // maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + // ); + + await _updateUTXOs(); + + await Future.wait([ + updateCachedId(walletId), + updateCachedIsFavorite(false), + ]); longMutex = false; } catch (e, s) { @@ -595,8 +640,7 @@ class DogecoinWallet extends CoinServiceAPI { for (String txid in txnsToCheck) { final txn = await electrumXClient.getTransaction(txHash: txid); - var confirmations = txn["confirmations"]; - if (confirmations is! int) continue; + int confirmations = txn["confirmations"] as int? ?? 0; bool isUnconfirmed = confirmations < MINIMUM_CONFIRMATIONS; if (!isUnconfirmed) { // unconfirmedTxs = {}; @@ -605,12 +649,16 @@ class DogecoinWallet extends CoinServiceAPI { } } if (!needsRefresh) { - var allOwnAddresses = await _fetchAllOwnAddresses(); - List> allTxs = - await _fetchHistory(allOwnAddresses); - final txData = await transactionData; + final allOwnAddresses = await _fetchAllOwnAddresses(); + List> allTxs = await _fetchHistory( + allOwnAddresses.map((e) => e.value).toList(growable: false)); for (Map transaction in allTxs) { - if (txData.findTransaction(transaction['tx_hash'] as String) == + final txid = transaction['tx_hash'] as String; + if ((await db + .getTransactions(walletId) + .filter() + .txidMatches(txid) + .findFirst()) == null) { Logging.instance.log( " txid not found in address history already ${transaction['tx_hash']}", @@ -621,6 +669,13 @@ class DogecoinWallet extends CoinServiceAPI { } } return needsRefresh; + } on NoSuchTransactionException catch (e) { + // TODO: move direct transactions elsewhere + await db.isar.writeTxn(() async { + await db.isar.transactions.deleteByTxidWalletId(e.txid, walletId); + }); + await txTracker.deleteTransaction(e.txid); + return true; } catch (e, s) { Logging.instance.log( "Exception caught in refreshIfThereIsNewData: $e\n$s", @@ -629,17 +684,25 @@ class DogecoinWallet extends CoinServiceAPI { } } - Future getAllTxsToWatch( - TransactionData txData, - ) async { + Future getAllTxsToWatch() async { if (_hasCalledExit) return; - List unconfirmedTxnsToNotifyPending = []; - List unconfirmedTxnsToNotifyConfirmed = []; + List unconfirmedTxnsToNotifyPending = []; + List unconfirmedTxnsToNotifyConfirmed = []; - // Get all unconfirmed incoming transactions - for (final chunk in txData.txChunks) { - for (final tx in chunk.transactions) { - if (tx.confirmedStatus) { + final currentChainHeight = await chainHeight; + + final txCount = await db.getTransactions(walletId).count(); + + const paginateLimit = 50; + + for (int i = 0; i < txCount; i += paginateLimit) { + final transactions = await db + .getTransactions(walletId) + .offset(i) + .limit(paginateLimit) + .findAll(); + for (final tx in transactions) { + if (tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { if (txTracker.wasNotifiedPending(tx.txid) && !txTracker.wasNotifiedConfirmed(tx.txid)) { unconfirmedTxnsToNotifyConfirmed.add(tx); @@ -654,61 +717,74 @@ class DogecoinWallet extends CoinServiceAPI { // notify on new incoming transaction for (final tx in unconfirmedTxnsToNotifyPending) { - if (tx.txType == "Received") { - unawaited(NotificationApi.showNotification( - title: "Incoming transaction", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.now(), - shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS, - coinName: coin.name, - txid: tx.txid, - confirmations: tx.confirmations, - requiredConfirmations: MINIMUM_CONFIRMATIONS, - )); + final confirmations = tx.getConfirmations(currentChainHeight); + + if (tx.type == isar_models.TransactionType.incoming) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Incoming transaction", + walletId: walletId, + date: DateTime.now(), + shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, + txid: tx.txid, + confirmations: confirmations, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); + await txTracker.addNotifiedPending(tx.txid); - } else if (tx.txType == "Sent") { - unawaited(NotificationApi.showNotification( - title: "Sending transaction", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS, - coinName: coin.name, - txid: tx.txid, - confirmations: tx.confirmations, - requiredConfirmations: MINIMUM_CONFIRMATIONS, - )); + } else if (tx.type == isar_models.TransactionType.outgoing) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Sending transaction", + walletId: walletId, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, + txid: tx.txid, + confirmations: confirmations, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); + await txTracker.addNotifiedPending(tx.txid); } } // notify on confirmed for (final tx in unconfirmedTxnsToNotifyConfirmed) { - if (tx.txType == "Received") { - unawaited(NotificationApi.showNotification( - title: "Incoming transaction confirmed", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.now(), - shouldWatchForUpdates: false, - coinName: coin.name, - )); + if (tx.type == isar_models.TransactionType.incoming) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Incoming transaction confirmed", + walletId: walletId, + date: DateTime.now(), + shouldWatchForUpdates: false, + txid: tx.txid, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); await txTracker.addNotifiedConfirmed(tx.txid); - } else if (tx.txType == "Sent") { - unawaited(NotificationApi.showNotification( - title: "Outgoing transaction confirmed", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.now(), - shouldWatchForUpdates: false, - coinName: coin.name, - )); + } else if (tx.type == isar_models.TransactionType.outgoing) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Outgoing transaction confirmed", + walletId: walletId, + date: DateTime.now(), + shouldWatchForUpdates: false, + txid: tx.txid, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); + await txTracker.addNotifiedConfirmed(tx.txid); } } @@ -770,36 +846,34 @@ class DogecoinWallet extends CoinServiceAPI { .log("cached height: $storedHeight", level: LogLevel.Info); if (currentHeight != storedHeight) { - if (currentHeight != -1) { - // -1 failed to fetch current height - unawaited(updateStoredChainHeight(newHeight: currentHeight)); - } - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId)); - await _checkChangeAddressForTransactions(DerivePathType.bip44); + await checkChangeAddressForTransactions(); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId)); await _checkCurrentReceivingAddressesForTransactions(); - final newTxData = _fetchTransactionData(); + // paynym stuff + // await checkAllCurrentReceivingPaynymAddressesForTransactions(); + + final fetchFuture = _refreshTransactions(); + final utxosRefreshFuture = _updateUTXOs(); GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.50, walletId)); - final newUtxoData = _fetchUtxoData(); final feeObj = _getFees(); GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.60, walletId)); - _transactionData = Future(() => newTxData); - GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.70, walletId)); _feeObject = Future(() => feeObj); - _utxoData = Future(() => newUtxoData); + + await utxosRefreshFuture; GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.80, walletId)); - await getAllTxsToWatch(await newTxData); + await fetchFuture; + await getAllTxsToWatch(); GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.90, walletId)); } @@ -852,12 +926,13 @@ class DogecoinWallet extends CoinServiceAPI { @override Future> prepareSend({ required String address, - required int satoshiAmount, + required Amount amount, Map? args, }) async { try { final feeRateType = args?["feeRate"]; final feeRateAmount = args?["feeRateAmount"]; + final utxos = args?["UTXOs"] as Set?; if (feeRateType is FeeRateType || feeRateAmount is int) { late final int rate; if (feeRateType is FeeRateType) { @@ -880,15 +955,23 @@ class DogecoinWallet extends CoinServiceAPI { } // check for send all bool isSendAll = false; - final balance = - Format.decimalAmountToSatoshis(await availableBalance, coin); - if (satoshiAmount == balance) { + if (amount == balance.spendable) { isSendAll = true; } - final result = - await coinSelection(satoshiAmount, rate, address, isSendAll); - Logging.instance.log("SEND RESULT: $result", level: LogLevel.Info); + final bool coinControl = utxos != null; + + final result = await coinSelection( + satoshiAmountToSend: amount.raw.toInt(), + selectedTxFeeRate: rate, + recipientAddress: address, + isSendAll: isSendAll, + utxos: utxos?.toList(), + coinControl: coinControl, + ); + + Logging.instance + .log("PREPARE SEND RESULT: $result", level: LogLevel.Info); if (result is int) { switch (result) { case 1: @@ -934,6 +1017,12 @@ class DogecoinWallet extends CoinServiceAPI { final txHash = await _electrumXClient.broadcastTransaction( rawTx: txData["hex"] as String); Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info); + + final utxos = txData["usedUTXOs"] as List; + + // mark utxos as used + await db.putUTXOs(utxos.map((e) => e.copyWith(used: true)).toList()); + return txHash; } catch (e, s) { Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s", @@ -942,24 +1031,6 @@ class DogecoinWallet extends CoinServiceAPI { } } - @override - Future send({ - required String toAddress, - required int amount, - Map args = const {}, - }) async { - try { - final txData = await prepareSend( - address: toAddress, satoshiAmount: amount, args: args); - final txHash = await confirmSend(txData: txData); - return txHash; - } catch (e, s) { - Logging.instance - .log("Exception rethrown from send(): $e\n$s", level: LogLevel.Error); - rethrow; - } - } - @override Future testNetworkConnection() async { try { @@ -1012,7 +1083,7 @@ class DogecoinWallet extends CoinServiceAPI { Logging.instance .log("Generating new ${coin.prettyName} wallet.", level: LogLevel.Info); - if ((DB.instance.get(boxName: walletId, key: "id")) != null) { + if (getCachedId() != null) { throw Exception( "Attempted to initialize a new wallet using an existing wallet ID!"); } @@ -1024,92 +1095,73 @@ class DogecoinWallet extends CoinServiceAPI { level: LogLevel.Fatal); rethrow; } + await Future.wait([ - DB.instance.put(boxName: walletId, key: "id", value: _walletId), - DB.instance - .put(boxName: walletId, key: "isFavorite", value: false), + updateCachedId(walletId), + updateCachedIsFavorite(false), ]); } @override Future initializeExisting() async { - Logging.instance.log("Opening existing ${coin.prettyName} wallet.", + Logging.instance.log("initializeExisting() ${coin.prettyName} wallet.", level: LogLevel.Info); - if ((DB.instance.get(boxName: walletId, key: "id")) == null) { + if (getCachedId() == null) { throw Exception( "Attempted to initialize an existing wallet using an unknown wallet ID!"); } + await _prefs.init(); - final data = - DB.instance.get(boxName: walletId, key: "latest_tx_model") - as TransactionData?; - if (data != null) { - _transactionData = Future(() => data); - } + // await _checkCurrentChangeAddressesForTransactions(); + // await _checkCurrentReceivingAddressesForTransactions(); } - @override - Future get transactionData => - _transactionData ??= _fetchTransactionData(); - Future? _transactionData; - - TransactionData? cachedTxData; - // hack to add tx to txData before refresh completes // required based on current app architecture where we don't properly store // transactions locally in a good way @override Future updateSentCachedTxData(Map txData) async { - final priceData = - await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final locale = Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; - final String worthNow = Format.localizedStringAsFixed( - value: - ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2), - decimalPlaces: 2, - locale: locale!); - - final tx = models.Transaction( + final transaction = isar_models.Transaction( + walletId: walletId, txid: txData["txid"] as String, - confirmedStatus: false, timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, - txType: "Sent", - amount: txData["recipientAmt"] as int, - worthNow: worthNow, - worthAtBlockTimestamp: worthNow, - fees: txData["fee"] as int, - inputSize: 0, - outputSize: 0, + type: isar_models.TransactionType.outgoing, + subType: isar_models.TransactionSubType.none, + // precision may be lost here hence the following amountString + amount: (txData["recipientAmt"] as Amount).raw.toInt(), + amountString: (txData["recipientAmt"] as Amount).toJsonString(), + fee: txData["fee"] as int, + height: null, + isCancelled: false, + isLelantus: false, + otherData: null, + slateId: null, + nonce: null, inputs: [], outputs: [], - address: txData["address"] as String, - height: -1, - confirmations: 0, ); - if (cachedTxData == null) { - final data = await _fetchTransactionData(); - _transactionData = Future(() => data); - } + final address = txData["address"] is String + ? await db.getAddress(walletId, txData["address"] as String) + : null; - final transactions = cachedTxData!.getAllTransactions(); - transactions[tx.txid] = tx; - cachedTxData = models.TransactionData.fromMap(transactions); - _transactionData = Future(() => cachedTxData!); + await db.addNewTransactionData( + [ + Tuple2(transaction, address), + ], + walletId, + ); } @override bool validateAddress(String address) { - return Address.validateAddress(address, _network); + return btc_dart.Address.validateAddress(address, network); } @override String get walletId => _walletId; - late String _walletId; + late final String _walletId; @override String get walletName => _walletName; @@ -1129,29 +1181,6 @@ class DogecoinWallet extends CoinServiceAPI { late SecureStorageInterface _secureStore; - late PriceAPI _priceAPI; - - DogecoinWallet({ - required String walletId, - required String walletName, - required Coin coin, - required ElectrumX client, - required CachedElectrumX cachedClient, - required TransactionNotificationTracker tracker, - PriceAPI? priceAPI, - required SecureStorageInterface secureStore, - }) { - txTracker = tracker; - _walletId = walletId; - _walletName = walletName; - _coin = coin; - _electrumXClient = client; - _cachedElectrumXClient = cachedClient; - - _priceAPI = priceAPI ?? PriceAPI(Client()); - _secureStore = secureStore; - } - @override Future updateNode(bool shouldRefresh) async { final failovers = NodeService(secureStorageInterface: _secureStore) @@ -1182,12 +1211,11 @@ class DogecoinWallet extends CoinServiceAPI { } Future> _getMnemonicList() async { - final mnemonicString = - await _secureStore.read(key: '${_walletId}_mnemonic'); - if (mnemonicString == null) { + final _mnemonicString = await mnemonicString; + if (_mnemonicString == null) { return []; } - final List data = mnemonicString.split(' '); + final List data = _mnemonicString.split(' '); return data; } @@ -1205,35 +1233,16 @@ class DogecoinWallet extends CoinServiceAPI { ); } - Future> _fetchAllOwnAddresses() async { - final List allAddresses = []; - - final receivingAddressesP2PKH = DB.instance.get( - boxName: walletId, key: 'receivingAddressesP2PKH') as List; - final changeAddressesP2PKH = - DB.instance.get(boxName: walletId, key: 'changeAddressesP2PKH') - as List; - - // for (var i = 0; i < receivingAddresses.length; i++) { - // if (!allAddresses.contains(receivingAddresses[i])) { - // allAddresses.add(receivingAddresses[i]); - // } - // } - // for (var i = 0; i < changeAddresses.length; i++) { - // if (!allAddresses.contains(changeAddresses[i])) { - // allAddresses.add(changeAddresses[i]); - // } - // } - for (var i = 0; i < receivingAddressesP2PKH.length; i++) { - if (!allAddresses.contains(receivingAddressesP2PKH[i])) { - allAddresses.add(receivingAddressesP2PKH[i] as String); - } - } - for (var i = 0; i < changeAddressesP2PKH.length; i++) { - if (!allAddresses.contains(changeAddressesP2PKH[i])) { - allAddresses.add(changeAddressesP2PKH[i] as String); - } - } + Future> _fetchAllOwnAddresses() async { + final allAddresses = await db + .getAddresses(walletId) + .filter() + .not() + .typeEqualTo(isar_models.AddressType.nonWallet) + .and() + .not() + .subTypeEqualTo(isar_models.AddressSubType.nonWallet) + .findAll(); return allAddresses; } @@ -1250,9 +1259,18 @@ class DogecoinWallet extends CoinServiceAPI { numberOfBlocksFast: f, numberOfBlocksAverage: m, numberOfBlocksSlow: s, - fast: Format.decimalAmountToSatoshis(fast, coin), - medium: Format.decimalAmountToSatoshis(medium, coin), - slow: Format.decimalAmountToSatoshis(slow, coin), + fast: Amount.fromDecimal( + fast, + fractionDigits: coin.decimals, + ).raw.toInt(), + medium: Amount.fromDecimal( + medium, + fractionDigits: coin.decimals, + ).raw.toInt(), + slow: Amount.fromDecimal( + slow, + fractionDigits: coin.decimals, + ).raw.toInt(), ); Logging.instance.log("fetched fees: $feeObject", level: LogLevel.Info); @@ -1294,51 +1312,28 @@ class DogecoinWallet extends CoinServiceAPI { } // this should never fail - if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) { + if ((await mnemonicString) != null || (await mnemonicPassphrase) != null) { throw Exception( "Attempted to overwrite mnemonic on generate new wallet!"); } await _secureStore.write( key: '${_walletId}_mnemonic', value: bip39.generateMnemonic(strength: 256)); + await _secureStore.write( + key: '${_walletId}_mnemonicPassphrase', + value: "", + ); - // Set relevant indexes - await DB.instance - .put(boxName: walletId, key: "receivingIndexP2PKH", value: 0); - await DB.instance - .put(boxName: walletId, key: "changeIndexP2PKH", value: 0); - await DB.instance.put( - boxName: walletId, - key: 'blocked_tx_hashes', - value: ["0xdefault"], - ); // A list of transaction hashes to represent frozen utxos in wallet - // initialize address book entries - await DB.instance.put( - boxName: walletId, - key: 'addressBookEntries', - value: {}); - - // Generate and add addresses to relevant arrays - // final initialReceivingAddress = - // await _generateAddressForChain(0, 0, DerivePathType.bip44); - // final initialChangeAddress = - // await _generateAddressForChain(1, 0, DerivePathType.bip44); + // Generate and add addresses final initialReceivingAddressP2PKH = await _generateAddressForChain(0, 0, DerivePathType.bip44); final initialChangeAddressP2PKH = await _generateAddressForChain(1, 0, DerivePathType.bip44); - // await _addToAddressesArrayForChain( - // initialReceivingAddress, 0, DerivePathType.bip44); - // await _addToAddressesArrayForChain( - // initialChangeAddress, 1, DerivePathType.bip44); - await _addToAddressesArrayForChain( - initialReceivingAddressP2PKH, 0, DerivePathType.bip44); - await _addToAddressesArrayForChain( - initialChangeAddressP2PKH, 1, DerivePathType.bip44); - - // this._currentReceivingAddress = Future(() => initialReceivingAddress); - _currentReceivingAddressP2PKH = Future(() => initialReceivingAddressP2PKH); + await db.putAddresses([ + initialReceivingAddressP2PKH, + initialChangeAddressP2PKH, + ]); Logging.instance.log("_generateNewWalletFinished", level: LogLevel.Info); } @@ -1346,32 +1341,41 @@ class DogecoinWallet extends CoinServiceAPI { /// Generates a new internal or external chain address for the wallet using a BIP44 derivation path. /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! /// [index] - This can be any integer >= 0 - Future _generateAddressForChain( + Future _generateAddressForChain( int chain, int index, DerivePathType derivePathType, ) async { - final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic'); - final node = await compute( - getBip32NodeWrapper, - Tuple5( - chain, - index, - mnemonic!, - _network, - derivePathType, - ), + final _mnemonic = await mnemonicString; + final _mnemonicPassphrase = await mnemonicPassphrase; + if (_mnemonicPassphrase == null) { + Logging.instance.log( + "Exception in _generateAddressForChain: mnemonic passphrase null, possible migration issue; if using internal builds, delete wallet and restore from seed, if using a release build, please file bug report", + level: LogLevel.Error); + } + + final derivePath = constructDerivePath( + derivePathType: derivePathType, + networkWIF: network.wif, + chain: chain, + index: index, ); + final node = await Bip32Utils.getBip32Node( + _mnemonic!, + _mnemonicPassphrase!, + network, + derivePath, + ); + final data = PaymentData(pubkey: node.publicKey); String address; switch (derivePathType) { case DerivePathType.bip44: - address = P2PKH(data: data, network: _network).data.address!; + address = P2PKH(data: data, network: network).data.address!; break; - // default: - // // should never hit this due to all enum cases handled - // return null; + default: + throw Exception("Unsupported DerivePathType"); } // add generated address & info to derivations @@ -1382,81 +1386,45 @@ class DogecoinWallet extends CoinServiceAPI { wif: node.toWIF(), derivePathType: derivePathType, ); - - return address; - } - - /// Increases the index for either the internal or external chain, depending on [chain]. - /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! - Future _incrementAddressIndexForChain( - int chain, DerivePathType derivePathType) async { - // Here we assume chain == 1 if it isn't 0 - String indexKey = chain == 0 ? "receivingIndex" : "changeIndex"; - switch (derivePathType) { - case DerivePathType.bip44: - indexKey += "P2PKH"; - break; - } - - final newIndex = - (DB.instance.get(boxName: walletId, key: indexKey)) + 1; - await DB.instance - .put(boxName: walletId, key: indexKey, value: newIndex); - } - - /// Adds [address] to the relevant chain's address array, which is determined by [chain]. - /// [address] - Expects a standard native segwit address - /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! - Future _addToAddressesArrayForChain( - String address, int chain, DerivePathType derivePathType) async { - String chainArray = ''; - if (chain == 0) { - chainArray = 'receivingAddresses'; - } else { - chainArray = 'changeAddresses'; - } - switch (derivePathType) { - case DerivePathType.bip44: - chainArray += "P2PKH"; - break; - } - - final addressArray = - DB.instance.get(boxName: walletId, key: chainArray); - if (addressArray == null) { - Logging.instance.log( - 'Attempting to add the following to $chainArray array for chain $chain:${[ - address - ]}', - level: LogLevel.Info); - await DB.instance - .put(boxName: walletId, key: chainArray, value: [address]); - } else { - // Make a deep copy of the existing list - final List newArray = []; - addressArray - .forEach((dynamic _address) => newArray.add(_address as String)); - newArray.add(address); // Add the address passed into the method - await DB.instance - .put(boxName: walletId, key: chainArray, value: newArray); - } + return isar_models.Address( + walletId: walletId, + value: address, + publicKey: node.publicKey, + type: isar_models.AddressType.p2pkh, + derivationIndex: index, + derivationPath: isar_models.DerivationPath()..value = derivePath, + subType: chain == 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change, + ); } /// Returns the latest receiving/change (external/internal) address for the wallet depending on [chain] /// and /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! Future _getCurrentAddressForChain( - int chain, DerivePathType derivePathType) async { - // Here, we assume that chain == 1 if it isn't 0 - String arrayKey = chain == 0 ? "receivingAddresses" : "changeAddresses"; + int chain, + DerivePathType derivePathType, + ) async { + final subType = chain == 0 // Here, we assume that chain == 1 if it isn't 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change; + + isar_models.Address? address; switch (derivePathType) { case DerivePathType.bip44: - arrayKey += "P2PKH"; + address = await db + .getAddresses(walletId) + .filter() + .typeEqualTo(isar_models.AddressType.p2pkh) + .subTypeEqualTo(subType) + .sortByDerivationIndexDesc() + .findFirst(); break; + default: + throw Exception("Unsupported DerivePathType"); } - final internalChainArray = - DB.instance.get(boxName: walletId, key: arrayKey); - return internalChainArray.last as String; + return address!.value; } String _buildDerivationStorageKey( @@ -1467,6 +1435,8 @@ class DogecoinWallet extends CoinServiceAPI { case DerivePathType.bip44: key = "${walletId}_${chainId}DerivationsP2PKH"; break; + default: + throw Exception("Unsupported DerivePathType"); } return key; } @@ -1588,8 +1558,8 @@ class DogecoinWallet extends CoinServiceAPI { return allTransactions; } - Future _fetchUtxoData() async { - final List allAddresses = await _fetchAllOwnAddresses(); + Future _updateUTXOs() async { + final allAddresses = await _fetchAllOwnAddresses(); try { final fetchedUtxoList = >>[]; @@ -1601,7 +1571,8 @@ class DogecoinWallet extends CoinServiceAPI { if (batches[batchNumber] == null) { batches[batchNumber] = {}; } - final scripthash = _convertToScriptHash(allAddresses[i], _network); + final scripthash = + AddressUtils.convertToScriptHash(allAddresses[i].value, network); batches[batchNumber]!.addAll({ scripthash: [scripthash] }); @@ -1620,148 +1591,139 @@ class DogecoinWallet extends CoinServiceAPI { } } - final priceData = - await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final List> outputArray = []; - int satoshiBalance = 0; - int satoshiBalancePending = 0; + final List outputArray = []; for (int i = 0; i < fetchedUtxoList.length; i++) { for (int j = 0; j < fetchedUtxoList[i].length; j++) { - int value = fetchedUtxoList[i][j]["value"] as int; - satoshiBalance += value; + final jsonUTXO = fetchedUtxoList[i][j]; final txn = await cachedElectrumXClient.getTransaction( - txHash: fetchedUtxoList[i][j]["tx_hash"] as String, + txHash: jsonUTXO["tx_hash"] as String, verbose: true, coin: coin, ); - final Map utxo = {}; - final int confirmations = txn["confirmations"] as int? ?? 0; - final bool confirmed = txn["confirmations"] == null - ? false - : txn["confirmations"] as int >= MINIMUM_CONFIRMATIONS; - if (!confirmed) { - satoshiBalancePending += value; + // fetch stored tx to see if paynym notification tx and block utxo + final storedTx = await db.getTransaction( + walletId, + jsonUTXO["tx_hash"] as String, + ); + + bool shouldBlock = false; + String? blockReason; + String? label; + + if (storedTx?.subType == + isar_models.TransactionSubType.bip47Notification) { + if (storedTx?.type == isar_models.TransactionType.incoming) { + shouldBlock = true; + blockReason = "Incoming paynym notification transaction."; + } else if (storedTx?.type == isar_models.TransactionType.outgoing) { + shouldBlock = false; + blockReason = "Paynym notification change output. Incautious " + "handling of change outputs from notification transactions " + "may cause unintended loss of privacy."; + label = blockReason; + } } - utxo["txid"] = txn["txid"]; - utxo["vout"] = fetchedUtxoList[i][j]["tx_pos"]; - utxo["value"] = value; + final vout = jsonUTXO["tx_pos"] as int; - utxo["status"] = {}; - utxo["status"]["confirmed"] = confirmed; - utxo["status"]["confirmations"] = confirmations; - utxo["status"]["block_height"] = fetchedUtxoList[i][j]["height"]; - utxo["status"]["block_hash"] = txn["blockhash"]; - utxo["status"]["block_time"] = txn["blocktime"]; + final outputs = txn["vout"] as List; + + String? utxoOwnerAddress; + // get UTXO owner address + for (final output in outputs) { + if (output["n"] == vout) { + utxoOwnerAddress = + output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]?["address"] as String?; + } + } + + final utxo = isar_models.UTXO( + walletId: walletId, + txid: txn["txid"] as String, + vout: vout, + value: jsonUTXO["value"] as int, + name: label ?? "", + isBlocked: shouldBlock, + blockedReason: blockReason, + isCoinbase: txn["is_coinbase"] as bool? ?? false, + blockHash: txn["blockhash"] as String?, + blockHeight: jsonUTXO["height"] as int?, + blockTime: txn["blocktime"] as int?, + address: utxoOwnerAddress, + ); - final fiatValue = ((Decimal.fromInt(value) * currentPrice) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2); - utxo["rawWorth"] = fiatValue; - utxo["fiatWorth"] = fiatValue.toString(); outputArray.add(utxo); } } - Decimal currencyBalanceRaw = - ((Decimal.fromInt(satoshiBalance) * currentPrice) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2); - - final Map result = { - "total_user_currency": currencyBalanceRaw.toString(), - "total_sats": satoshiBalance, - "total_btc": (Decimal.fromInt(satoshiBalance) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal( - scaleOnInfinitePrecision: Constants.decimalPlacesForCoin(coin)) - .toString(), - "outputArray": outputArray, - "unconfirmed": satoshiBalancePending, - }; - - final dataModel = UtxoData.fromJson(result); - - final List allOutputs = dataModel.unspentOutputArray; Logging.instance - .log('Outputs fetched: $allOutputs', level: LogLevel.Info); - await _sortOutputs(allOutputs); - await DB.instance.put( - boxName: walletId, key: 'latest_utxo_model', value: dataModel); - await DB.instance.put( - boxName: walletId, - key: 'totalBalance', - value: dataModel.satoshiBalance); - return dataModel; + .log('Outputs fetched: $outputArray', level: LogLevel.Info); + + await db.updateUTXOs(walletId, outputArray); + + // finally update balance + await _updateBalance(); } catch (e, s) { Logging.instance .log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error); - final latestTxModel = - DB.instance.get(boxName: walletId, key: 'latest_utxo_model'); - - if (latestTxModel == null) { - final emptyModel = { - "total_user_currency": "0.00", - "total_sats": 0, - "total_btc": "0", - "outputArray": [] - }; - return UtxoData.fromJson(emptyModel); - } else { - Logging.instance - .log("Old output model located", level: LogLevel.Warning); - return latestTxModel as models.UtxoData; - } } } - /// Takes in a list of UtxoObjects and adds a name (dependent on object index within list) - /// and checks for the txid associated with the utxo being blocked and marks it accordingly. - /// Now also checks for output labeling. - Future _sortOutputs(List utxos) async { - final blockedHashArray = - DB.instance.get(boxName: walletId, key: 'blocked_tx_hashes') - as List?; - final List lst = []; - if (blockedHashArray != null) { - for (var hash in blockedHashArray) { - lst.add(hash as String); - } - } - final labels = - DB.instance.get(boxName: walletId, key: 'labels') as Map? ?? - {}; - - outputsList = []; - - for (var i = 0; i < utxos.length; i++) { - if (labels[utxos[i].txid] != null) { - utxos[i].txName = labels[utxos[i].txid] as String? ?? ""; - } else { - utxos[i].txName = 'Output #$i'; - } - - if (utxos[i].status.confirmed == false) { - outputsList.add(utxos[i]); - } else { - if (lst.contains(utxos[i].txid)) { - utxos[i].blocked = true; - outputsList.add(utxos[i]); - } else if (!lst.contains(utxos[i].txid)) { - outputsList.add(utxos[i]); - } - } - } + Future _updateBalance() async { + await refreshBalance(); } + @override + Balance get balance => _balance ??= getCachedBalance(); + Balance? _balance; + + // /// Takes in a list of UtxoObjects and adds a name (dependent on object index within list) + // /// and checks for the txid associated with the utxo being blocked and marks it accordingly. + // /// Now also checks for output labeling. + // Future _sortOutputs(List utxos) async { + // final blockedHashArray = + // DB.instance.get(boxName: walletId, key: 'blocked_tx_hashes') + // as List?; + // final List lst = []; + // if (blockedHashArray != null) { + // for (var hash in blockedHashArray) { + // lst.add(hash as String); + // } + // } + // final labels = + // DB.instance.get(boxName: walletId, key: 'labels') as Map? ?? + // {}; + // + // outputsList = []; + // + // for (var i = 0; i < utxos.length; i++) { + // if (labels[utxos[i].txid] != null) { + // utxos[i].txName = labels[utxos[i].txid] as String? ?? ""; + // } else { + // utxos[i].txName = 'Output #$i'; + // } + // + // if (utxos[i].status.confirmed == false) { + // outputsList.add(utxos[i]); + // } else { + // if (lst.contains(utxos[i].txid)) { + // utxos[i].blocked = true; + // outputsList.add(utxos[i]); + // } else if (!lst.contains(utxos[i].txid)) { + // outputsList.add(utxos[i]); + // } + // } + // } + // } + Future getTxCount({required String address}) async { String? scripthash; try { - scripthash = _convertToScriptHash(address, _network); + scripthash = AddressUtils.convertToScriptHash(address, network); final transactions = await electrumXClient.getHistory(scripthash: scripthash); return transactions.length; @@ -1779,7 +1741,9 @@ class DogecoinWallet extends CoinServiceAPI { try { final Map> args = {}; for (final entry in addresses.entries) { - args[entry.key] = [_convertToScriptHash(entry.value, _network)]; + args[entry.key] = [ + AddressUtils.convertToScriptHash(entry.value, network) + ]; } final response = await electrumXClient.getBatchHistory(args: args); @@ -1796,93 +1760,85 @@ class DogecoinWallet extends CoinServiceAPI { } } - Future _checkReceivingAddressForTransactions( - DerivePathType derivePathType) async { + Future _checkReceivingAddressForTransactions() async { try { - final String currentExternalAddr = - await _getCurrentAddressForChain(0, derivePathType); - final int txCount = await getTxCount(address: currentExternalAddr); + final currentReceiving = await _currentReceivingAddress; + + final int txCount = await getTxCount(address: currentReceiving.value); Logging.instance.log( - 'Number of txs for current receiving address $currentExternalAddr: $txCount', + 'Number of txs for current receiving address $currentReceiving: $txCount', level: LogLevel.Info); - if (txCount >= 1) { + if (txCount >= 1 || currentReceiving.derivationIndex < 0) { // First increment the receiving index - await _incrementAddressIndexForChain(0, derivePathType); - - // Check the new receiving index - String indexKey = "receivingIndex"; - switch (derivePathType) { - case DerivePathType.bip44: - indexKey += "P2PKH"; - break; - } - final newReceivingIndex = - DB.instance.get(boxName: walletId, key: indexKey) as int; + final newReceivingIndex = currentReceiving.derivationIndex + 1; // Use new index to derive a new receiving address final newReceivingAddress = await _generateAddressForChain( - 0, newReceivingIndex, derivePathType); + 0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin)); - // Add that new receiving address to the array of receiving addresses - await _addToAddressesArrayForChain( - newReceivingAddress, 0, derivePathType); - - // Set the new receiving address that the service - - switch (derivePathType) { - case DerivePathType.bip44: - _currentReceivingAddressP2PKH = Future(() => newReceivingAddress); - break; + final existing = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(newReceivingAddress.value) + .findFirst(); + if (existing == null) { + // Add that new change address + await db.putAddress(newReceivingAddress); + } else { + // we need to update the address + await db.updateAddress(existing, newReceivingAddress); } + // keep checking until address with no tx history is set as current + await _checkReceivingAddressForTransactions(); } } on SocketException catch (se, s) { Logging.instance.log( - "SocketException caught in _checkReceivingAddressForTransactions($derivePathType): $se\n$s", + "SocketException caught in _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $se\n$s", level: LogLevel.Error); return; } catch (e, s) { Logging.instance.log( - "Exception rethrown from _checkReceivingAddressForTransactions($derivePathType): $e\n$s", + "Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s", level: LogLevel.Error); rethrow; } } - Future _checkChangeAddressForTransactions( - DerivePathType derivePathType) async { + Future checkChangeAddressForTransactions() async { try { - final String currentExternalAddr = - await _getCurrentAddressForChain(1, derivePathType); - final int txCount = await getTxCount(address: currentExternalAddr); + final currentChange = await _currentChangeAddress; + final int txCount = await getTxCount(address: currentChange.value); Logging.instance.log( - 'Number of txs for current change address $currentExternalAddr: $txCount', + 'Number of txs for current change address $currentChange: $txCount', level: LogLevel.Info); - if (txCount >= 1) { + if (txCount >= 1 || currentChange.derivationIndex < 0) { // First increment the change index - await _incrementAddressIndexForChain(1, derivePathType); - - // Check the new change index - String indexKey = "changeIndex"; - switch (derivePathType) { - case DerivePathType.bip44: - indexKey += "P2PKH"; - break; - } - final newChangeIndex = - DB.instance.get(boxName: walletId, key: indexKey) as int; + final newChangeIndex = currentChange.derivationIndex + 1; // Use new index to derive a new change address - final newChangeAddress = - await _generateAddressForChain(1, newChangeIndex, derivePathType); + final newChangeAddress = await _generateAddressForChain( + 1, newChangeIndex, DerivePathTypeExt.primaryFor(coin)); - // Add that new receiving address to the array of change addresses - await _addToAddressesArrayForChain(newChangeAddress, 1, derivePathType); + final existing = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(newChangeAddress.value) + .findFirst(); + if (existing == null) { + // Add that new change address + await db.putAddress(newChangeAddress); + } else { + // we need to update the address + await db.updateAddress(existing, newChangeAddress); + } + // keep checking until address with no tx history is set as current + await checkChangeAddressForTransactions(); } } catch (e, s) { Logging.instance.log( - "Exception rethrown from _checkChangeAddressForTransactions($derivePathType): $e\n$s", + "Exception rethrown from _checkChangeAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s", level: LogLevel.Error); rethrow; } @@ -1890,9 +1846,9 @@ class DogecoinWallet extends CoinServiceAPI { Future _checkCurrentReceivingAddressesForTransactions() async { try { - for (final type in DerivePathType.values) { - await _checkReceivingAddressForTransactions(type); - } + // for (final type in DerivePathType.values) { + await _checkReceivingAddressForTransactions(); + // } } catch (e, s) { Logging.instance.log( "Exception rethrown from _checkCurrentReceivingAddressesForTransactions(): $e\n$s", @@ -1914,9 +1870,9 @@ class DogecoinWallet extends CoinServiceAPI { Future _checkCurrentChangeAddressesForTransactions() async { try { - for (final type in DerivePathType.values) { - await _checkChangeAddressForTransactions(type); - } + // for (final type in DerivePathType.values) { + await checkChangeAddressForTransactions(); + // } } catch (e, s) { Logging.instance.log( "Exception rethrown from _checkCurrentChangeAddressesForTransactions(): $e\n$s", @@ -1936,28 +1892,6 @@ class DogecoinWallet extends CoinServiceAPI { } } - /// attempts to convert a string to a valid scripthash - /// - /// Returns the scripthash or throws an exception on invalid dogecoin address - String _convertToScriptHash(String dogecoinAddress, NetworkType network) { - try { - final output = Address.addressToOutputScript(dogecoinAddress, network); - final hash = sha256.convert(output.toList(growable: false)).toString(); - - final chars = hash.split(""); - final reversedPairs = []; - var i = chars.length - 1; - while (i > 0) { - reversedPairs.add(chars[i - 1]); - reversedPairs.add(chars[i]); - i -= 2; - } - return reversedPairs.join(""); - } catch (e) { - rethrow; - } - } - Future>> _fetchHistory( List allAddresses) async { try { @@ -1971,7 +1905,8 @@ class DogecoinWallet extends CoinServiceAPI { if (batches[batchNumber] == null) { batches[batchNumber] = {}; } - final scripthash = _convertToScriptHash(allAddresses[i], _network); + final scripthash = + AddressUtils.convertToScriptHash(allAddresses[i], network); final id = Logger.isTestEnv ? "$i" : const Uuid().v1(); requestIdToAddressMap[id] = allAddresses[i]; batches[batchNumber]!.addAll({ @@ -2012,75 +1947,84 @@ class DogecoinWallet extends CoinServiceAPI { return false; } - Future _fetchTransactionData() async { - final List allAddresses = await _fetchAllOwnAddresses(); + // Future migrate() async { + // final receivingAddressesP2PKH = DB.instance.get( + // boxName: walletId, key: 'receivingAddressesP2PKH') as List; + // + // final changeAddressesP2PKH = + // DB.instance.get(boxName: walletId, key: 'changeAddressesP2PKH') + // as List; + // + // await isar.writeTxn(() async { + // for (var i = 0; i < receivingAddressesP2PKH.length; i++) { + // await isar.address.put( + // isar_models.Address() + // ..type = isar_models.AddressType.p2pkh + // ..subType = isar_models.AddressSubType.receiving + // ..publicKey = [] + // ..derivationIndex = i + // ..value = receivingAddressesP2PKH[i] as String, + // ); + // } + // for (var i = 0; i < changeAddressesP2PKH.length; i++) { + // await isar.address.put( + // isar_models.Address() + // ..type = isar_models.AddressType.p2pkh + // ..subType = isar_models.AddressSubType.change + // ..publicKey = [] + // ..derivationIndex = i + // ..value = changeAddressesP2PKH[i] as String, + // ); + // } + // }); + // + // await DB.instance.put( + // boxName: walletId, key: "receivingAddressesP2PKH", value: []); + // await DB.instance.put( + // boxName: walletId, key: "changeAddressesP2PKH", value: []); + // } - final changeAddressesP2PKH = - DB.instance.get(boxName: walletId, key: 'changeAddressesP2PKH') - as List; + Future _refreshTransactions() async { + final List allAddresses = + await _fetchAllOwnAddresses(); final List> allTxHashes = - await _fetchHistory(allAddresses); + await _fetchHistory(allAddresses.map((e) => e.value).toList()); - final cachedTransactions = - DB.instance.get(boxName: walletId, key: 'latest_tx_model') - as TransactionData?; - int latestTxnBlockHeight = - DB.instance.get(boxName: walletId, key: "storedTxnDataHeight") - as int? ?? - 0; + List hashes = + allTxHashes.map((e) => e['tx_hash'] as String).toList(growable: false); - final unconfirmedCachedTransactions = - cachedTransactions?.getAllTransactions() ?? {}; - unconfirmedCachedTransactions - .removeWhere((key, value) => value.confirmedStatus); + await fastFetch(hashes); + List> allTransactions = []; + final currentHeight = await chainHeight; - if (cachedTransactions != null) { - for (final tx in allTxHashes.toList(growable: false)) { - final txHeight = tx["height"] as int; - if (txHeight > 0 && - txHeight < latestTxnBlockHeight - MINIMUM_CONFIRMATIONS) { - if (unconfirmedCachedTransactions[tx["tx_hash"] as String] == null) { - allTxHashes.remove(tx); - } + for (final txHash in allTxHashes) { + final storedTx = await db + .getTransactions(walletId) + .filter() + .txidEqualTo(txHash["tx_hash"] as String) + .findFirst(); + + if (storedTx == null || + !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)) { + final tx = await cachedElectrumXClient.getTransaction( + txHash: txHash["tx_hash"] as String, + verbose: true, + coin: coin, + ); + + if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { + tx["address"] = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(txHash["address"] as String) + .findFirst(); + tx["height"] = txHash["height"]; + allTransactions.add(tx); } } } - List hashes = []; - for (var element in allTxHashes) { - hashes.add(element['tx_hash'] as String); - } - await fastFetch(hashes); - List> allTransactions = []; - - for (final txHash in allTxHashes) { - final tx = await cachedElectrumXClient.getTransaction( - txHash: txHash["tx_hash"] as String, - verbose: true, - coin: coin, - ); - - // Logging.instance.log("TRANSACTION: ${jsonEncode(tx)}"); - // TODO fix this for sent to self transactions? - if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { - tx["address"] = txHash["address"]; - tx["height"] = txHash["height"]; - allTransactions.add(tx); - } - } - - Logging.instance.log("addAddresses: $allAddresses", level: LogLevel.Info); - Logging.instance.log("allTxHashes: $allTxHashes", level: LogLevel.Info); - - Logging.instance.log("allTransactions length: ${allTransactions.length}", - level: LogLevel.Info); - - final priceData = - await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final List> midSortedArray = []; - Set vHashes = {}; for (final txObject in allTransactions) { for (int i = 0; i < (txObject["vin"] as List).length; i++) { @@ -2091,248 +2035,33 @@ class DogecoinWallet extends CoinServiceAPI { } await fastFetch(vHashes.toList()); + final List> txns = []; + for (final txObject in allTransactions) { - List sendersArray = []; - List recipientsArray = []; + final txn = await parseTransaction( + txObject, + cachedElectrumXClient, + allAddresses, + coin, + MINIMUM_CONFIRMATIONS, + walletId, + ); - // Usually only has value when txType = 'Send' - int inputAmtSentFromWallet = 0; - // Usually has value regardless of txType due to change addresses - int outputAmtAddressedToWallet = 0; - int fee = 0; - - Map midSortedTx = {}; - - for (int i = 0; i < (txObject["vin"] as List).length; i++) { - final input = txObject["vin"][i] as Map; - final prevTxid = input["txid"] as String; - final prevOut = input["vout"] as int; - - final tx = await _cachedElectrumXClient.getTransaction( - txHash: prevTxid, coin: coin); - - for (final out in tx["vout"] as List) { - if (prevOut == out["n"]) { - final address = out["scriptPubKey"]["addresses"][0] as String?; - if (address != null) { - sendersArray.add(address); - } - } - } - } - - Logging.instance.log("sendersArray: $sendersArray", level: LogLevel.Info); - - for (final output in txObject["vout"] as List) { - final address = output["scriptPubKey"]["addresses"][0] as String?; - if (address != null) { - recipientsArray.add(address); - } - } - - Logging.instance - .log("recipientsArray: $recipientsArray", level: LogLevel.Info); - - final foundInSenders = - allAddresses.any((element) => sendersArray.contains(element)); - Logging.instance - .log("foundInSenders: $foundInSenders", level: LogLevel.Info); - - // If txType = Sent, then calculate inputAmtSentFromWallet - if (foundInSenders) { - int totalInput = 0; - for (int i = 0; i < (txObject["vin"] as List).length; i++) { - final input = txObject["vin"][i] as Map; - final prevTxid = input["txid"] as String; - final prevOut = input["vout"] as int; - final tx = await _cachedElectrumXClient.getTransaction( - txHash: prevTxid, - coin: coin, - ); - - for (final out in tx["vout"] as List) { - if (prevOut == out["n"]) { - inputAmtSentFromWallet += - (Decimal.parse(out["value"].toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - } - } - } - totalInput = inputAmtSentFromWallet; - int totalOutput = 0; - - for (final output in txObject["vout"] as List) { - final address = output["scriptPubKey"]["addresses"][0]; - final value = output["value"]; - final _value = (Decimal.parse(value.toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - totalOutput += _value; - if (changeAddressesP2PKH.contains(address)) { - inputAmtSentFromWallet -= _value; - } else { - // change address from 'sent from' to the 'sent to' address - txObject["address"] = address; - } - } - // calculate transaction fee - fee = totalInput - totalOutput; - // subtract fee from sent to calculate correct value of sent tx - inputAmtSentFromWallet -= fee; - } else { - // counters for fee calculation - int totalOut = 0; - int totalIn = 0; - - // add up received tx value - for (final output in txObject["vout"] as List) { - final address = output["scriptPubKey"]["addresses"][0]; - if (address != null) { - final value = (Decimal.parse(output["value"].toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - totalOut += value; - if (allAddresses.contains(address)) { - outputAmtAddressedToWallet += value; - } - } - } - - // calculate fee for received tx - for (int i = 0; i < (txObject["vin"] as List).length; i++) { - final input = txObject["vin"][i] as Map; - final prevTxid = input["txid"] as String; - final prevOut = input["vout"] as int; - final tx = await _cachedElectrumXClient.getTransaction( - txHash: prevTxid, - coin: coin, - ); - - for (final out in tx["vout"] as List) { - if (prevOut == out["n"]) { - totalIn += (Decimal.parse(out["value"].toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - } - } - } - fee = totalIn - totalOut; - } - - // create final tx map - midSortedTx["txid"] = txObject["txid"]; - midSortedTx["confirmed_status"] = (txObject["confirmations"] != null) && - (txObject["confirmations"] as int >= MINIMUM_CONFIRMATIONS); - midSortedTx["confirmations"] = txObject["confirmations"] ?? 0; - midSortedTx["timestamp"] = txObject["blocktime"] ?? - (DateTime.now().millisecondsSinceEpoch ~/ 1000); - - if (foundInSenders) { - midSortedTx["txType"] = "Sent"; - midSortedTx["amount"] = inputAmtSentFromWallet; - final String worthNow = - ((currentPrice * Decimal.fromInt(inputAmtSentFromWallet)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2) - .toStringAsFixed(2); - midSortedTx["worthNow"] = worthNow; - midSortedTx["worthAtBlockTimestamp"] = worthNow; - } else { - midSortedTx["txType"] = "Received"; - midSortedTx["amount"] = outputAmtAddressedToWallet; - final worthNow = - ((currentPrice * Decimal.fromInt(outputAmtAddressedToWallet)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2) - .toStringAsFixed(2); - midSortedTx["worthNow"] = worthNow; - } - midSortedTx["aliens"] = []; - midSortedTx["fees"] = fee; - midSortedTx["address"] = txObject["address"]; - midSortedTx["inputSize"] = txObject["vin"].length; - midSortedTx["outputSize"] = txObject["vout"].length; - midSortedTx["inputs"] = txObject["vin"]; - midSortedTx["outputs"] = txObject["vout"]; - - final int height = txObject["height"] as int; - midSortedTx["height"] = height; - - if (height >= latestTxnBlockHeight) { - latestTxnBlockHeight = height; - } - - midSortedArray.add(midSortedTx); + txns.add(txn); } - // sort by date ---- //TODO not sure if needed - // shouldn't be any issues with a null timestamp but I got one at some point? - midSortedArray - .sort((a, b) => (b["timestamp"] as int) - (a["timestamp"] as int)); - // { - // final aT = a["timestamp"]; - // final bT = b["timestamp"]; - // - // if (aT == null && bT == null) { - // return 0; - // } else if (aT == null) { - // return -1; - // } else if (bT == null) { - // return 1; - // } else { - // return bT - aT; - // } - // }); + await db.addNewTransactionData(txns, walletId); - // buildDateTimeChunks - final Map result = {"dateTimeChunks": []}; - final dateArray = []; - - for (int i = 0; i < midSortedArray.length; i++) { - final txObject = midSortedArray[i]; - final date = extractDateFromTimestamp(txObject["timestamp"] as int); - final txTimeArray = [txObject["timestamp"], date]; - - if (dateArray.contains(txTimeArray[1])) { - result["dateTimeChunks"].forEach((dynamic chunk) { - if (extractDateFromTimestamp(chunk["timestamp"] as int) == - txTimeArray[1]) { - if (chunk["transactions"] == null) { - chunk["transactions"] = >[]; - } - chunk["transactions"].add(txObject); - } - }); - } else { - dateArray.add(txTimeArray[1]); - final chunk = { - "timestamp": txTimeArray[0], - "transactions": [txObject], - }; - result["dateTimeChunks"].add(chunk); - } + // quick hack to notify manager to call notifyListeners if + // transactions changed + if (txns.isNotEmpty) { + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "Transactions updated/added for: $walletId $walletName ", + walletId, + ), + ); } - - final transactionsMap = cachedTransactions?.getAllTransactions() ?? {}; - transactionsMap - .addAll(TransactionData.fromJson(result).getAllTransactions()); - - final txModel = TransactionData.fromMap(transactionsMap); - - await DB.instance.put( - boxName: walletId, - key: 'storedTxnDataHeight', - value: latestTxnBlockHeight); - await DB.instance.put( - boxName: walletId, key: 'latest_tx_model', value: txModel); - - cachedTxData = txModel; - return txModel; } int estimateTxFee({required int vSize, required int feeRatePerKB}) { @@ -2343,27 +2072,43 @@ class DogecoinWallet extends CoinServiceAPI { /// with [satoshiAmountToSend] and [selectedTxFeeRate]. If so, it will call buildTrasaction() and return /// a map containing the tx hex along with other important information. If not, then it will return /// an integer (1 or 2) - dynamic coinSelection(int satoshiAmountToSend, int selectedTxFeeRate, - String _recipientAddress, bool isSendAll, - {int additionalOutputs = 0, List? utxos}) async { + dynamic coinSelection({ + required int satoshiAmountToSend, + required int selectedTxFeeRate, + required String recipientAddress, + required bool coinControl, + required bool isSendAll, + int additionalOutputs = 0, + List? utxos, + }) async { Logging.instance .log("Starting coinSelection ----------", level: LogLevel.Info); - final List availableOutputs = utxos ?? outputsList; - final List spendableOutputs = []; + final List availableOutputs = utxos ?? await this.utxos; + final currentChainHeight = await chainHeight; + final List spendableOutputs = []; int spendableSatoshiValue = 0; // Build list of spendable outputs and totaling their satoshi amount - for (var i = 0; i < availableOutputs.length; i++) { - if (availableOutputs[i].blocked == false && - availableOutputs[i].status.confirmed == true) { - spendableOutputs.add(availableOutputs[i]); - spendableSatoshiValue += availableOutputs[i].value; + for (final utxo in availableOutputs) { + if (utxo.isBlocked == false && + utxo.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) && + utxo.used != true) { + spendableOutputs.add(utxo); + spendableSatoshiValue += utxo.value; } } - // sort spendable by age (oldest first) - spendableOutputs.sort( - (a, b) => b.status.confirmations.compareTo(a.status.confirmations)); + if (coinControl) { + if (spendableOutputs.length < availableOutputs.length) { + throw ArgumentError("Attempted to use an unavailable utxo"); + } + } + + // don't care about sorting if using all utxos + if (!coinControl) { + // sort spendable by age (oldest first) + spendableOutputs.sort((a, b) => b.blockTime!.compareTo(a.blockTime!)); + } Logging.instance.log("spendableOutputs.length: ${spendableOutputs.length}", level: LogLevel.Info); @@ -2390,21 +2135,29 @@ class DogecoinWallet extends CoinServiceAPI { // Possible situation right here int satoshisBeingUsed = 0; int inputsBeingConsumed = 0; - List utxoObjectsToUse = []; + List utxoObjectsToUse = []; - for (var i = 0; - satoshisBeingUsed < satoshiAmountToSend && i < spendableOutputs.length; - i++) { - utxoObjectsToUse.add(spendableOutputs[i]); - satoshisBeingUsed += spendableOutputs[i].value; - inputsBeingConsumed += 1; - } - for (int i = 0; - i < additionalOutputs && inputsBeingConsumed < spendableOutputs.length; - i++) { - utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]); - satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value; - inputsBeingConsumed += 1; + if (!coinControl) { + for (var i = 0; + satoshisBeingUsed < satoshiAmountToSend && + i < spendableOutputs.length; + i++) { + utxoObjectsToUse.add(spendableOutputs[i]); + satoshisBeingUsed += spendableOutputs[i].value; + inputsBeingConsumed += 1; + } + for (int i = 0; + i < additionalOutputs && + inputsBeingConsumed < spendableOutputs.length; + i++) { + utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]); + satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value; + inputsBeingConsumed += 1; + } + } else { + satoshisBeingUsed = spendableSatoshiValue; + utxoObjectsToUse = spendableOutputs; + inputsBeingConsumed = spendableOutputs.length; } Logging.instance @@ -2417,7 +2170,7 @@ class DogecoinWallet extends CoinServiceAPI { .log('satoshiAmountToSend $satoshiAmountToSend', level: LogLevel.Info); // numberOfOutputs' length must always be equal to that of recipientsArray and recipientsAmtArray - List recipientsArray = [_recipientAddress]; + List recipientsArray = [recipientAddress]; List recipientsAmtArray = [satoshiAmountToSend]; // gather required signing data @@ -2428,9 +2181,8 @@ class DogecoinWallet extends CoinServiceAPI { .log("Attempting to send all $coin", level: LogLevel.Info); final int vSizeForOneOutput = (await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, - recipients: [_recipientAddress], + recipients: [recipientAddress], satoshiAmounts: [satoshisBeingUsed - 1], ))["vSize"] as int; int feeForOneOutput = estimateTxFee( @@ -2443,7 +2195,6 @@ class DogecoinWallet extends CoinServiceAPI { final int amount = satoshiAmountToSend - feeForOneOutput; dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: [amount], @@ -2451,25 +2202,27 @@ class DogecoinWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": amount, + "recipientAmt": Amount( + rawValue: BigInt.from(amount), + fractionDigits: coin.decimals, + ), "fee": feeForOneOutput, "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), }; return transactionObject; } final int vSizeForOneOutput = (await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, - recipients: [_recipientAddress], + recipients: [recipientAddress], satoshiAmounts: [satoshisBeingUsed - 1], ))["vSize"] as int; final int vSizeForTwoOutPuts = (await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: [ - _recipientAddress, - await _getCurrentAddressForChain(1, DerivePathType.bip44), + recipientAddress, + await _getCurrentAddressForChain(1, DerivePathTypeExt.primaryFor(coin)), ], satoshiAmounts: [ satoshiAmountToSend, @@ -2509,7 +2262,7 @@ class DogecoinWallet extends CoinServiceAPI { if (satoshisBeingUsed - satoshiAmountToSend > feeForOneOutput) { if (satoshisBeingUsed - satoshiAmountToSend > - feeForOneOutput + DUST_LIMIT) { + feeForOneOutput + DUST_LIMIT.raw.toInt()) { // Here, we know that theoretically, we may be able to include another output(change) but we first need to // factor in the value of this output in satoshis. int changeOutputSize = @@ -2517,13 +2270,13 @@ class DogecoinWallet extends CoinServiceAPI { // We check to see if the user can pay for the new transaction with 2 outputs instead of one. If they can and // the second output's size > 546 satoshis, we perform the mechanics required to properly generate and use a new // change address. - if (changeOutputSize > DUST_LIMIT && + if (changeOutputSize > DUST_LIMIT.raw.toInt() && satoshisBeingUsed - satoshiAmountToSend - changeOutputSize == feeForTwoOutputs) { // generate new change address if current change address has been used - await _checkChangeAddressForTransactions(DerivePathType.bip44); - final String newChangeAddress = - await _getCurrentAddressForChain(1, DerivePathType.bip44); + await checkChangeAddressForTransactions(); + final String newChangeAddress = await _getCurrentAddressForChain( + 1, DerivePathTypeExt.primaryFor(coin)); int feeBeingPaid = satoshisBeingUsed - satoshiAmountToSend - changeOutputSize; @@ -2545,7 +2298,6 @@ class DogecoinWallet extends CoinServiceAPI { Logging.instance .log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info); dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -2573,7 +2325,6 @@ class DogecoinWallet extends CoinServiceAPI { Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs', level: LogLevel.Info); txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -2583,9 +2334,13 @@ class DogecoinWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": feeBeingPaid, "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), }; return transactionObject; } else { @@ -2602,7 +2357,6 @@ class DogecoinWallet extends CoinServiceAPI { Logging.instance .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -2610,9 +2364,13 @@ class DogecoinWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": satoshisBeingUsed - satoshiAmountToSend, "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), }; return transactionObject; } @@ -2631,7 +2389,6 @@ class DogecoinWallet extends CoinServiceAPI { Logging.instance .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -2639,9 +2396,13 @@ class DogecoinWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": satoshisBeingUsed - satoshiAmountToSend, "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), }; return transactionObject; } @@ -2660,7 +2421,6 @@ class DogecoinWallet extends CoinServiceAPI { Logging.instance .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -2668,13 +2428,17 @@ class DogecoinWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": feeForOneOutput, "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), }; return transactionObject; } else { - // Remember that returning 2 indicates that the user does not have a sufficient balance to + // Remember that returning 2 iTndicates that the user does not have a sufficient balance to // pay for the transaction fee. Ideally, at this stage, we should check if the user has any // additional outputs they're able to spend and then recalculate fees. Logging.instance.log( @@ -2682,109 +2446,134 @@ class DogecoinWallet extends CoinServiceAPI { level: LogLevel.Warning); // try adding more outputs if (spendableOutputs.length > inputsBeingConsumed) { - return coinSelection(satoshiAmountToSend, selectedTxFeeRate, - _recipientAddress, isSendAll, - additionalOutputs: additionalOutputs + 1, utxos: utxos); + return coinSelection( + satoshiAmountToSend: satoshiAmountToSend, + selectedTxFeeRate: selectedTxFeeRate, + recipientAddress: recipientAddress, + isSendAll: isSendAll, + additionalOutputs: additionalOutputs + 1, + utxos: utxos, + coinControl: coinControl, + ); } return 2; } } - Future> fetchBuildTxData( - List utxosToUse, + Future> fetchBuildTxData( + List utxosToUse, ) async { // return data - Map results = {}; - Map> addressTxid = {}; - - // addresses to check - List addressesP2PKH = []; + List signingData = []; try { // Populating the addresses to check for (var i = 0; i < utxosToUse.length; i++) { - final txid = utxosToUse[i].txid; - final tx = await _cachedElectrumXClient.getTransaction( - txHash: txid, - coin: coin, - ); - - for (final output in tx["vout"] as List) { - final n = output["n"]; - if (n != null && n == utxosToUse[i].vout) { - final address = output["scriptPubKey"]["addresses"][0] as String; - if (!addressTxid.containsKey(address)) { - addressTxid[address] = []; - } - (addressTxid[address] as List).add(txid); - switch (addressType(address: address)) { - case DerivePathType.bip44: - addressesP2PKH.add(address); - break; + if (utxosToUse[i].address == null) { + final txid = utxosToUse[i].txid; + final tx = await _cachedElectrumXClient.getTransaction( + txHash: txid, + coin: coin, + ); + for (final output in tx["vout"] as List) { + final n = output["n"]; + if (n != null && n == utxosToUse[i].vout) { + utxosToUse[i] = utxosToUse[i].copyWith( + address: output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]["address"] as String, + ); } } } + + final derivePathType = addressType(address: utxosToUse[i].address!); + + signingData.add( + SigningData( + derivePathType: derivePathType, + utxo: utxosToUse[i], + ), + ); } - // p2pkh / bip44 - final p2pkhLength = addressesP2PKH.length; - if (p2pkhLength > 0) { - final receiveDerivations = await _fetchDerivations( + Map> receiveDerivations = {}; + Map> changeDerivations = {}; + + for (final sd in signingData) { + String? pubKey; + String? wif; + + // fetch receiving derivations if null + receiveDerivations[sd.derivePathType] ??= await _fetchDerivations( chain: 0, - derivePathType: DerivePathType.bip44, + derivePathType: sd.derivePathType, ); - final changeDerivations = await _fetchDerivations( - chain: 1, - derivePathType: DerivePathType.bip44, - ); - for (int i = 0; i < p2pkhLength; i++) { - // receives - final receiveDerivation = receiveDerivations[addressesP2PKH[i]]; - // if a match exists it will not be null - if (receiveDerivation != null) { - final data = P2PKH( - data: PaymentData( - pubkey: Format.stringToUint8List( - receiveDerivation["pubKey"] as String)), - network: _network, - ).data; + final receiveDerivation = + receiveDerivations[sd.derivePathType]![sd.utxo.address!]; - for (String tx in addressTxid[addressesP2PKH[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - receiveDerivation["wif"] as String, - network: _network, - ), - }; - } - } else { - // if its not a receive, check change - final changeDerivation = changeDerivations[addressesP2PKH[i]]; - // if a match exists it will not be null - if (changeDerivation != null) { - final data = P2PKH( - data: PaymentData( - pubkey: Format.stringToUint8List( - changeDerivation["pubKey"] as String)), - network: _network, - ).data; - - for (String tx in addressTxid[addressesP2PKH[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - changeDerivation["wif"] as String, - network: _network, - ), - }; - } - } + if (receiveDerivation != null) { + pubKey = receiveDerivation["pubKey"] as String; + wif = receiveDerivation["wif"] as String; + } else { + // fetch change derivations if null + changeDerivations[sd.derivePathType] ??= await _fetchDerivations( + chain: 1, + derivePathType: sd.derivePathType, + ); + final changeDerivation = + changeDerivations[sd.derivePathType]![sd.utxo.address!]; + if (changeDerivation != null) { + pubKey = changeDerivation["pubKey"] as String; + wif = changeDerivation["wif"] as String; } } + + if (wif == null || pubKey == null) { + final address = await db.getAddress(walletId, sd.utxo.address!); + if (address?.derivationPath != null) { + final node = await Bip32Utils.getBip32Node( + (await mnemonicString)!, + (await mnemonicPassphrase)!, + network, + address!.derivationPath!.value, + ); + + wif = node.toWIF(); + pubKey = Format.uint8listToString(node.publicKey); + } + } + + if (wif != null && pubKey != null) { + final PaymentData data; + final Uint8List? redeemScript; + + switch (sd.derivePathType) { + case DerivePathType.bip44: + data = P2PKH( + data: PaymentData( + pubkey: Format.stringToUint8List(pubKey), + ), + network: network, + ).data; + redeemScript = null; + break; + + default: + throw Exception("DerivePathType unsupported"); + } + + final keyPair = ECPair.fromWIF( + wif, + network: network, + ); + + sd.redeemScript = redeemScript; + sd.output = data.output; + sd.keyPair = keyPair; + } } - return results; + return signingData; } catch (e, s) { Logging.instance .log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error); @@ -2794,22 +2583,25 @@ class DogecoinWallet extends CoinServiceAPI { /// Builds and signs a transaction Future> buildTransaction({ - required List utxosToUse, - required Map utxoSigningData, + required List utxoSigningData, required List recipients, required List satoshiAmounts, }) async { Logging.instance .log("Starting buildTransaction ----------", level: LogLevel.Info); - final txb = TransactionBuilder(network: _network); + final txb = TransactionBuilder(network: network); txb.setVersion(1); // Add transaction inputs - for (var i = 0; i < utxosToUse.length; i++) { - final txid = utxosToUse[i].txid; - txb.addInput(txid, utxosToUse[i].vout, null, - utxoSigningData[txid]["output"] as Uint8List); + for (var i = 0; i < utxoSigningData.length; i++) { + final txid = utxoSigningData[i].utxo.txid; + txb.addInput( + txid, + utxoSigningData[i].utxo.vout, + null, + utxoSigningData[i].output!, + ); } // Add transaction output @@ -2819,13 +2611,12 @@ class DogecoinWallet extends CoinServiceAPI { try { // Sign the transaction accordingly - for (var i = 0; i < utxosToUse.length; i++) { - final txid = utxosToUse[i].txid; + for (var i = 0; i < utxoSigningData.length; i++) { txb.sign( vin: i, - keyPair: utxoSigningData[txid]["keyPair"] as ECPair, - witnessValue: utxosToUse[i].value, - redeemScript: utxoSigningData[txid]["redeemScript"] as Uint8List?, + keyPair: utxoSigningData[i].keyPair!, + witnessValue: utxoSigningData[i].utxo.value, + redeemScript: utxoSigningData[i].redeemScript, ); } } catch (e, s) { @@ -2859,17 +2650,31 @@ class DogecoinWallet extends CoinServiceAPI { await _cachedElectrumXClient.clearSharedTransactionCache(coin: coin); // back up data - await _rescanBackup(); + // await _rescanBackup(); + + // clear blockchain info + await db.deleteWalletBlockchainData(walletId); + await _deleteDerivations(); try { - final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic'); + final _mnemonic = await mnemonicString; + final _mnemonicPassphrase = await mnemonicPassphrase; + if (_mnemonicPassphrase == null) { + Logging.instance.log( + "Exception in fullRescan: mnemonic passphrase null, possible migration issue; if using internal builds, delete wallet and restore from seed, if using a release build, please file bug report", + level: LogLevel.Error); + } + await _recoverWalletFromBIP32SeedPhrase( - mnemonic: mnemonic!, + mnemonic: _mnemonic!, + mnemonicPassphrase: _mnemonicPassphrase!, maxUnusedAddressGap: maxUnusedAddressGap, maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + isRescan: true, ); longMutex = false; + await refresh(); Logging.instance.log("Full rescan complete!", level: LogLevel.Info); GlobalEventBus.instance.fire( WalletSyncStatusChangedEvent( @@ -2888,7 +2693,7 @@ class DogecoinWallet extends CoinServiceAPI { ); // restore from backup - await _rescanRestore(); + // await _rescanRestore(); longMutex = false; Logging.instance.log("Exception rethrown from fullRescan(): $e\n$s", @@ -2897,158 +2702,156 @@ class DogecoinWallet extends CoinServiceAPI { } } - Future _rescanRestore() async { - Logging.instance.log("starting rescan restore", level: LogLevel.Info); - - // restore from backup - // p2pkh - final tempReceivingAddressesP2PKH = DB.instance - .get(boxName: walletId, key: 'receivingAddressesP2PKH_BACKUP'); - final tempChangeAddressesP2PKH = DB.instance - .get(boxName: walletId, key: 'changeAddressesP2PKH_BACKUP'); - final tempReceivingIndexP2PKH = DB.instance - .get(boxName: walletId, key: 'receivingIndexP2PKH_BACKUP'); - final tempChangeIndexP2PKH = DB.instance - .get(boxName: walletId, key: 'changeIndexP2PKH_BACKUP'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2PKH', - value: tempReceivingAddressesP2PKH); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2PKH', - value: tempChangeAddressesP2PKH); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2PKH', - value: tempReceivingIndexP2PKH); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2PKH', - value: tempChangeIndexP2PKH); - await DB.instance.delete( - key: 'receivingAddressesP2PKH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'changeAddressesP2PKH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'receivingIndexP2PKH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'changeIndexP2PKH_BACKUP', boxName: walletId); - + Future _deleteDerivations() async { // P2PKH derivations - final p2pkhReceiveDerivationsString = await _secureStore.read( - key: "${walletId}_receiveDerivationsP2PKH_BACKUP"); - final p2pkhChangeDerivationsString = await _secureStore.read( - key: "${walletId}_changeDerivationsP2PKH_BACKUP"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2PKH", - value: p2pkhReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2PKH", - value: p2pkhChangeDerivationsString); - - await _secureStore.delete( - key: "${walletId}_receiveDerivationsP2PKH_BACKUP"); - await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH_BACKUP"); - - // UTXOs - final utxoData = DB.instance - .get(boxName: walletId, key: 'latest_utxo_model_BACKUP'); - await DB.instance.put( - boxName: walletId, key: 'latest_utxo_model', value: utxoData); - await DB.instance - .delete(key: 'latest_utxo_model_BACKUP', boxName: walletId); - - Logging.instance.log("rescan restore complete", level: LogLevel.Info); - } - - Future _rescanBackup() async { - Logging.instance.log("starting rescan backup", level: LogLevel.Info); - - // backup current and clear data - // p2pkh - final tempReceivingAddressesP2PKH = DB.instance - .get(boxName: walletId, key: 'receivingAddressesP2PKH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2PKH_BACKUP', - value: tempReceivingAddressesP2PKH); - await DB.instance - .delete(key: 'receivingAddressesP2PKH', boxName: walletId); - - final tempChangeAddressesP2PKH = DB.instance - .get(boxName: walletId, key: 'changeAddressesP2PKH'); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2PKH_BACKUP', - value: tempChangeAddressesP2PKH); - await DB.instance - .delete(key: 'changeAddressesP2PKH', boxName: walletId); - - final tempReceivingIndexP2PKH = - DB.instance.get(boxName: walletId, key: 'receivingIndexP2PKH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2PKH_BACKUP', - value: tempReceivingIndexP2PKH); - await DB.instance - .delete(key: 'receivingIndexP2PKH', boxName: walletId); - - final tempChangeIndexP2PKH = - DB.instance.get(boxName: walletId, key: 'changeIndexP2PKH'); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2PKH_BACKUP', - value: tempChangeIndexP2PKH); - await DB.instance - .delete(key: 'changeIndexP2PKH', boxName: walletId); - - // P2PKH derivations - final p2pkhReceiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivationsP2PKH"); - final p2pkhChangeDerivationsString = - await _secureStore.read(key: "${walletId}_changeDerivationsP2PKH"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2PKH_BACKUP", - value: p2pkhReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2PKH_BACKUP", - value: p2pkhChangeDerivationsString); - await _secureStore.delete(key: "${walletId}_receiveDerivationsP2PKH"); await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH"); - - // UTXOs - final utxoData = - DB.instance.get(boxName: walletId, key: 'latest_utxo_model'); - await DB.instance.put( - boxName: walletId, key: 'latest_utxo_model_BACKUP', value: utxoData); - await DB.instance - .delete(key: 'latest_utxo_model', boxName: walletId); - - Logging.instance.log("rescan backup complete", level: LogLevel.Info); } + // Future _rescanRestore() async { + // Logging.instance.log("starting rescan restore", level: LogLevel.Info); + // + // // restore from backup + // // p2pkh + // final tempReceivingAddressesP2PKH = DB.instance + // .get(boxName: walletId, key: 'receivingAddressesP2PKH_BACKUP'); + // final tempChangeAddressesP2PKH = DB.instance + // .get(boxName: walletId, key: 'changeAddressesP2PKH_BACKUP'); + // final tempReceivingIndexP2PKH = DB.instance + // .get(boxName: walletId, key: 'receivingIndexP2PKH_BACKUP'); + // final tempChangeIndexP2PKH = DB.instance + // .get(boxName: walletId, key: 'changeIndexP2PKH_BACKUP'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingAddressesP2PKH', + // value: tempReceivingAddressesP2PKH); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeAddressesP2PKH', + // value: tempChangeAddressesP2PKH); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingIndexP2PKH', + // value: tempReceivingIndexP2PKH); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeIndexP2PKH', + // value: tempChangeIndexP2PKH); + // await DB.instance.delete( + // key: 'receivingAddressesP2PKH_BACKUP', boxName: walletId); + // await DB.instance + // .delete(key: 'changeAddressesP2PKH_BACKUP', boxName: walletId); + // await DB.instance + // .delete(key: 'receivingIndexP2PKH_BACKUP', boxName: walletId); + // await DB.instance + // .delete(key: 'changeIndexP2PKH_BACKUP', boxName: walletId); + // + // // P2PKH derivations + // final p2pkhReceiveDerivationsString = await _secureStore.read( + // key: "${walletId}_receiveDerivationsP2PKH_BACKUP"); + // final p2pkhChangeDerivationsString = await _secureStore.read( + // key: "${walletId}_changeDerivationsP2PKH_BACKUP"); + // + // await _secureStore.write( + // key: "${walletId}_receiveDerivationsP2PKH", + // value: p2pkhReceiveDerivationsString); + // await _secureStore.write( + // key: "${walletId}_changeDerivationsP2PKH", + // value: p2pkhChangeDerivationsString); + // + // await _secureStore.delete( + // key: "${walletId}_receiveDerivationsP2PKH_BACKUP"); + // await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH_BACKUP"); + // + // // UTXOs + // final utxoData = DB.instance + // .get(boxName: walletId, key: 'latest_utxo_model_BACKUP'); + // await DB.instance.put( + // boxName: walletId, key: 'latest_utxo_model', value: utxoData); + // await DB.instance + // .delete(key: 'latest_utxo_model_BACKUP', boxName: walletId); + // + // Logging.instance.log("rescan restore complete", level: LogLevel.Info); + // } + // + // Future _rescanBackup() async { + // Logging.instance.log("starting rescan backup", level: LogLevel.Info); + // + // // backup current and clear data + // // p2pkh + // final tempReceivingAddressesP2PKH = DB.instance + // .get(boxName: walletId, key: 'receivingAddressesP2PKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingAddressesP2PKH_BACKUP', + // value: tempReceivingAddressesP2PKH); + // await DB.instance + // .delete(key: 'receivingAddressesP2PKH', boxName: walletId); + // + // final tempChangeAddressesP2PKH = DB.instance + // .get(boxName: walletId, key: 'changeAddressesP2PKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeAddressesP2PKH_BACKUP', + // value: tempChangeAddressesP2PKH); + // await DB.instance + // .delete(key: 'changeAddressesP2PKH', boxName: walletId); + // + // final tempReceivingIndexP2PKH = + // DB.instance.get(boxName: walletId, key: 'receivingIndexP2PKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingIndexP2PKH_BACKUP', + // value: tempReceivingIndexP2PKH); + // await DB.instance + // .delete(key: 'receivingIndexP2PKH', boxName: walletId); + // + // final tempChangeIndexP2PKH = + // DB.instance.get(boxName: walletId, key: 'changeIndexP2PKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeIndexP2PKH_BACKUP', + // value: tempChangeIndexP2PKH); + // await DB.instance + // .delete(key: 'changeIndexP2PKH', boxName: walletId); + // + // // P2PKH derivations + // final p2pkhReceiveDerivationsString = + // await _secureStore.read(key: "${walletId}_receiveDerivationsP2PKH"); + // final p2pkhChangeDerivationsString = + // await _secureStore.read(key: "${walletId}_changeDerivationsP2PKH"); + // + // await _secureStore.write( + // key: "${walletId}_receiveDerivationsP2PKH_BACKUP", + // value: p2pkhReceiveDerivationsString); + // await _secureStore.write( + // key: "${walletId}_changeDerivationsP2PKH_BACKUP", + // value: p2pkhChangeDerivationsString); + // + // await _secureStore.delete(key: "${walletId}_receiveDerivationsP2PKH"); + // await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH"); + // + // // UTXOs + // final utxoData = + // DB.instance.get(boxName: walletId, key: 'latest_utxo_model'); + // await DB.instance.put( + // boxName: walletId, key: 'latest_utxo_model_BACKUP', value: utxoData); + // await DB.instance + // .delete(key: 'latest_utxo_model', boxName: walletId); + // + // Logging.instance.log("rescan backup complete", level: LogLevel.Info); + // } + @override set isFavorite(bool markFavorite) { - DB.instance.put( - boxName: walletId, key: "isFavorite", value: markFavorite); + _isFavorite = markFavorite; + updateCachedIsFavorite(markFavorite); } @override - bool get isFavorite { - try { - return DB.instance.get(boxName: walletId, key: "isFavorite") - as bool; - } catch (e, s) { - Logging.instance.log( - "isFavorite fetch failed (returning false by default): $e\n$s", - level: LogLevel.Error); - return false; - } - } + bool get isFavorite => _isFavorite ??= getCachedIsFavorite(); + + bool? _isFavorite; @override bool get isRefreshing => refreshMutex; @@ -3060,42 +2863,50 @@ class DogecoinWallet extends CoinServiceAPI { (isActive) => this.isActive = isActive; @override - Future estimateFeeFor(int satoshiAmount, int feeRate) async { - final available = - Format.decimalAmountToSatoshis(await availableBalance, coin); + Future estimateFeeFor(Amount amount, int feeRate) async { + final available = balance.spendable; - if (available == satoshiAmount) { - return satoshiAmount - sweepAllEstimate(feeRate); - } else if (satoshiAmount <= 0 || satoshiAmount > available) { + if (available == amount) { + return amount - (await sweepAllEstimate(feeRate)); + } else if (amount <= Amount.zero || amount > available) { return roughFeeEstimate(1, 2, feeRate); } - int runningBalance = 0; + Amount runningBalance = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); int inputCount = 0; - for (final output in outputsList) { - runningBalance += output.value; - inputCount++; - if (runningBalance > satoshiAmount) { - break; + for (final output in (await utxos)) { + if (!output.isBlocked) { + runningBalance = runningBalance + + Amount( + rawValue: BigInt.from(output.value), + fractionDigits: coin.decimals, + ); + inputCount++; + if (runningBalance > amount) { + break; + } } } final oneOutPutFee = roughFeeEstimate(inputCount, 1, feeRate); final twoOutPutFee = roughFeeEstimate(inputCount, 2, feeRate); - if (runningBalance - satoshiAmount > oneOutPutFee) { - if (runningBalance - satoshiAmount > oneOutPutFee + DUST_LIMIT) { - final change = runningBalance - satoshiAmount - twoOutPutFee; + if (runningBalance - amount > oneOutPutFee) { + if (runningBalance - amount > oneOutPutFee + DUST_LIMIT) { + final change = runningBalance - amount - twoOutPutFee; if (change > DUST_LIMIT && - runningBalance - satoshiAmount - change == twoOutPutFee) { - return runningBalance - satoshiAmount - change; + runningBalance - amount - change == twoOutPutFee) { + return runningBalance - amount - change; } else { - return runningBalance - satoshiAmount; + return runningBalance - amount; } } else { - return runningBalance - satoshiAmount; + return runningBalance - amount; } - } else if (runningBalance - satoshiAmount == oneOutPutFee) { + } else if (runningBalance - amount == oneOutPutFee) { return oneOutPutFee; } else { return twoOutPutFee; @@ -3103,16 +2914,20 @@ class DogecoinWallet extends CoinServiceAPI { } // TODO: correct formula for doge? - int roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { - return ((181 * inputCount) + (34 * outputCount) + 10) * - (feeRatePerKB / 1000).ceil(); + Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { + return Amount( + rawValue: BigInt.from(((181 * inputCount) + (34 * outputCount) + 10) * + (feeRatePerKB / 1000).ceil()), + fractionDigits: coin.decimals, + ); } - int sweepAllEstimate(int feeRate) { + Future sweepAllEstimate(int feeRate) async { int available = 0; int inputCount = 0; - for (final output in outputsList) { - if (output.status.confirmed) { + for (final output in (await utxos)) { + if (!output.isBlocked && + output.isConfirmed(storedChainHeight, MINIMUM_CONFIRMATIONS)) { available += output.value; inputCount++; } @@ -3121,29 +2936,26 @@ class DogecoinWallet extends CoinServiceAPI { // transaction will only have 1 output minus the fee final estimatedFee = roughFeeEstimate(inputCount, 1, feeRate); - return available - estimatedFee; + return Amount( + rawValue: BigInt.from(available), + fractionDigits: coin.decimals, + ) - + estimatedFee; } @override Future generateNewAddress() async { try { - await _incrementAddressIndexForChain( - 0, DerivePathType.bip44); // First increment the receiving index - final newReceivingIndex = DB.instance.get( - boxName: walletId, - key: 'receivingIndexP2PKH') as int; // Check the new receiving index + final currentReceiving = await _currentReceivingAddress; + + final newReceivingIndex = currentReceiving.derivationIndex + 1; + + // Use new index to derive a new receiving address final newReceivingAddress = await _generateAddressForChain( - 0, - newReceivingIndex, - DerivePathType - .bip44); // Use new index to derive a new receiving address - await _addToAddressesArrayForChain( - newReceivingAddress, - 0, - DerivePathType - .bip44); // Add that new receiving address to the array of receiving addresses - _currentReceivingAddressP2PKH = Future(() => - newReceivingAddress); // Set the new receiving address that the service + 0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin)); + + // Add that new receiving address + await db.putAddress(newReceivingAddress); return true; } catch (e, s) { @@ -3153,12 +2965,23 @@ class DogecoinWallet extends CoinServiceAPI { return false; } } + + @override + Future get xpub async { + final node = await Bip32Utils.getBip32Root( + (await mnemonic).join(" "), + await mnemonicPassphrase ?? "", + network, + ); + + return node.neutered().toBase58(); + } } // Dogecoin Network final dogecoin = NetworkType( messagePrefix: '\x18Dogecoin Signed Message:\n', - bech32: 'bc', + // bech32: 'bc', bip32: Bip32Type(public: 0x02facafd, private: 0x02fac398), pubKeyHash: 0x1e, scriptHash: 0x16, @@ -3166,7 +2989,7 @@ final dogecoin = NetworkType( final dogecointestnet = NetworkType( messagePrefix: '\x18Dogecoin Signed Message:\n', - bech32: 'tb', + // bech32: 'tb', bip32: Bip32Type(public: 0x043587cf, private: 0x04358394), pubKeyHash: 0x71, scriptHash: 0xc4, diff --git a/lib/services/coins/ecash/ecash_wallet.dart b/lib/services/coins/ecash/ecash_wallet.dart new file mode 100644 index 000000000..5bed07bc4 --- /dev/null +++ b/lib/services/coins/ecash/ecash_wallet.dart @@ -0,0 +1,3139 @@ +import 'dart:async'; +import 'dart:io'; +import 'dart:math'; + +import 'package:bech32/bech32.dart'; +import 'package:bip32/bip32.dart' as bip32; +import 'package:bip39/bip39.dart' as bip39; +import 'package:bitbox/bitbox.dart' as bitbox; +import 'package:bitcoindart/bitcoindart.dart'; +import 'package:bs58check/bs58check.dart' as bs58check; +import 'package:decimal/decimal.dart'; +import 'package:flutter/foundation.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart'; +import 'package:stackwallet/electrumx_rpc/electrumx.dart'; +import 'package:stackwallet/exceptions/electrumx/no_such_transaction.dart'; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; +import 'package:stackwallet/models/paymint/fee_object_model.dart'; +import 'package:stackwallet/models/signing_data.dart'; +import 'package:stackwallet/services/coins/coin_service.dart'; +import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; +import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart'; +import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; +import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; +import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +import 'package:stackwallet/services/mixins/coin_control_interface.dart'; +import 'package:stackwallet/services/mixins/electrum_x_parsing.dart'; +import 'package:stackwallet/services/mixins/wallet_cache.dart'; +import 'package:stackwallet/services/mixins/wallet_db.dart'; +import 'package:stackwallet/services/mixins/xpubable.dart'; +import 'package:stackwallet/services/node_service.dart'; +import 'package:stackwallet/services/transaction_notification_tracker.dart'; +import 'package:stackwallet/utilities/address_utils.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/bip32_utils.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/default_nodes.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart'; +import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/prefs.dart'; +import 'package:stackwallet/widgets/crypto_notifications.dart'; +import 'package:tuple/tuple.dart'; +import 'package:uuid/uuid.dart'; + +const int MINIMUM_CONFIRMATIONS = 1; + +const String GENESIS_HASH_MAINNET = + "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"; +const String GENESIS_HASH_TESTNET = + "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"; + +final Amount DUST_LIMIT = Amount( + rawValue: BigInt.from(546), + fractionDigits: Coin.eCash.decimals, +); + +final eCashNetwork = NetworkType( + messagePrefix: '\x18Bitcoin Signed Message:\n', + bech32: 'bc', + bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4), + pubKeyHash: 0x00, + scriptHash: 0x05, + wif: 0x80, +); + +final eCashNetworkTestnet = NetworkType( + messagePrefix: '\x18Bitcoin Signed Message:\n', + bech32: 'tb', + bip32: Bip32Type(public: 0x043587cf, private: 0x04358394), + pubKeyHash: 0x6f, + scriptHash: 0xc4, + wif: 0xef, +); + +String constructDerivePath({ + required DerivePathType derivePathType, + required int networkWIF, + int account = 0, + required int chain, + required int index, +}) { + String coinType; + switch (networkWIF) { + case 0x80: // mainnet wif + switch (derivePathType) { + case DerivePathType.bip44: + coinType = "145"; + break; + case DerivePathType.eCash44: + coinType = "899"; + break; + default: + throw Exception( + "DerivePathType $derivePathType not supported for coinType"); + } + break; + case 0xef: // testnet wif + throw Exception( + "DerivePathType $derivePathType not supported for coinType"); + default: + throw Exception("Invalid ECash network wif used!"); + } + + int purpose; + switch (derivePathType) { + case DerivePathType.bip44: + case DerivePathType.eCash44: + purpose = 44; + break; + default: + throw Exception("DerivePathType $derivePathType not supported"); + } + + return "m/$purpose'/$coinType'/$account'/$chain/$index"; +} + +class ECashWallet extends CoinServiceAPI + with WalletCache, WalletDB, ElectrumXParsing, CoinControlInterface + implements XPubAble { + ECashWallet({ + required String walletId, + required String walletName, + required Coin coin, + required ElectrumX client, + required CachedElectrumX cachedClient, + required TransactionNotificationTracker tracker, + required SecureStorageInterface secureStore, + MainDB? mockableOverride, + }) { + txTracker = tracker; + _walletId = walletId; + _walletName = walletName; + _coin = coin; + _electrumXClient = client; + _cachedElectrumXClient = cachedClient; + _secureStore = secureStore; + initCache(walletId, coin); + initWalletDB(mockableOverride: mockableOverride); + initCoinControlInterface( + walletId: walletId, + walletName: walletName, + coin: coin, + db: db, + getChainHeight: () => chainHeight, + refreshedBalanceCallback: (balance) async { + _balance = balance; + await updateCachedBalance(_balance!); + }, + ); + } + + static const integrationTestFlag = + bool.fromEnvironment("IS_INTEGRATION_TEST"); + + final _prefs = Prefs.instance; + + Timer? timer; + late final Coin _coin; + + late final TransactionNotificationTracker txTracker; + + NetworkType get _network { + switch (coin) { + case Coin.eCash: + return eCashNetwork; + // case Coin.bitcoinTestNet: + // return testnet; + default: + throw Exception("Invalid network type!"); + } + } + + @override + set isFavorite(bool markFavorite) { + _isFavorite = markFavorite; + updateCachedIsFavorite(markFavorite); + } + + @override + bool get isFavorite => _isFavorite ??= getCachedIsFavorite(); + + bool? _isFavorite; + + @override + Coin get coin => _coin; + + @override + Future> get utxos => db.getUTXOs(walletId).findAll(); + + @override + Future> get transactions => db + .getTransactions(walletId) + .filter() + .not() + .group((q) => q + .subTypeEqualTo(isar_models.TransactionSubType.bip47Notification) + .and() + .typeEqualTo(isar_models.TransactionType.incoming)) + .sortByTimestampDesc() + .findAll(); + + @override + Future get currentReceivingAddress async => + (await _currentReceivingAddress).value; + + Future get _currentReceivingAddress async => + (await db + .getAddresses(walletId) + .filter() + .typeEqualTo(isar_models.AddressType.p2pkh) + .subTypeEqualTo(isar_models.AddressSubType.receiving) + .derivationPath((q) => q.valueStartsWith("m/44'/899")) + .sortByDerivationIndexDesc() + .findFirst()) ?? + await _generateAddressForChain(0, 0, DerivePathTypeExt.primaryFor(coin)); + + Future get currentChangeAddress async => + (await _currentChangeAddress).value; + + Future get _currentChangeAddress async => + (await db + .getAddresses(walletId) + .filter() + .typeEqualTo(isar_models.AddressType.p2pkh) + .subTypeEqualTo(isar_models.AddressSubType.change) + .derivationPath((q) => q.valueStartsWith("m/44'/899")) + .sortByDerivationIndexDesc() + .findFirst()) ?? + await _generateAddressForChain(1, 0, DerivePathTypeExt.primaryFor(coin)); + + @override + Future exit() async { + _hasCalledExit = true; + timer?.cancel(); + timer = null; + stopNetworkAlivePinging(); + } + + bool _hasCalledExit = false; + + @override + bool get hasCalledExit => _hasCalledExit; + + @override + Future get fees => _feeObject ??= _getFees(); + Future? _feeObject; + + @override + Future get maxFee async { + final fee = (await fees).fast as String; + final satsFee = + Decimal.parse(fee) * Decimal.fromInt(Constants.satsPerCoin(coin)); + return satsFee.floor().toBigInt().toInt(); + } + + @override + Future> get mnemonic => _getMnemonicList(); + + @override + Future get mnemonicString => + _secureStore.read(key: '${_walletId}_mnemonic'); + + @override + Future get mnemonicPassphrase => _secureStore.read( + key: '${_walletId}_mnemonicPassphrase', + ); + + Future get chainHeight async { + try { + final result = await _electrumXClient.getBlockHeadTip(); + final height = result["height"] as int; + await updateCachedChainHeight(height); + if (height > storedChainHeight) { + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "Updated current chain height in $walletId $walletName!", + walletId, + ), + ); + } + return height; + } catch (e, s) { + Logging.instance.log("Exception caught in chainHeight: $e\n$s", + level: LogLevel.Error); + return storedChainHeight; + } + } + + @override + int get storedChainHeight => getCachedChainHeight(); + + DerivePathType addressType({required String address}) { + Uint8List? decodeBase58; + Segwit? decodeBech32; + try { + if (bitbox.Address.detectFormat(address) == + bitbox.Address.formatCashAddr) { + if (validateCashAddr(address)) { + address = bitbox.Address.toLegacyAddress(address); + } else { + throw ArgumentError('$address is not currently supported'); + } + } + } catch (_) { + // invalid cash addr format + } + try { + decodeBase58 = bs58check.decode(address); + } catch (err) { + // Base58check decode fail + } + if (decodeBase58 != null) { + if (decodeBase58[0] == _network.pubKeyHash) { + // P2PKH + return DerivePathType.bip44; + } + if (decodeBase58[0] == _network.scriptHash) { + // P2SH + return DerivePathType.bip49; + } + throw ArgumentError('Invalid version or Network mismatch'); + } else { + try { + decodeBech32 = segwit.decode(address); + } catch (err) { + // Bech32 decode fail + } + if (_network.bech32 != decodeBech32!.hrp) { + throw ArgumentError('Invalid prefix or Network mismatch'); + } + if (decodeBech32.version != 0) { + throw ArgumentError('Invalid address version'); + } + // P2WPKH + return DerivePathType.bip84; + } + } + + bool longMutex = false; + + bool validateCashAddr(String cashAddr) { + String addr = cashAddr; + if (cashAddr.contains(":")) { + addr = cashAddr.split(":").last; + } + + return addr.startsWith("q"); + } + + @override + bool validateAddress(String address) { + try { + // 0 for bitcoincash: address scheme, 1 for legacy address + final format = bitbox.Address.detectFormat(address); + if (kDebugMode) { + print("format $format"); + } + + if (_coin == Coin.bitcoincashTestnet) { + return true; + } + + if (format == bitbox.Address.formatCashAddr) { + return validateCashAddr(address); + } else { + return address.startsWith("1"); + } + } catch (e) { + return false; + } + } + + @override + String get walletId => _walletId; + late final String _walletId; + + @override + String get walletName => _walletName; + late String _walletName; + + // setter for updating on rename + @override + set walletName(String newName) => _walletName = newName; + + late ElectrumX _electrumXClient; + + ElectrumX get electrumXClient => _electrumXClient; + + late CachedElectrumX _cachedElectrumXClient; + + CachedElectrumX get cachedElectrumXClient => _cachedElectrumXClient; + + late SecureStorageInterface _secureStore; + + @override + Future updateNode(bool shouldRefresh) async { + final failovers = NodeService(secureStorageInterface: _secureStore) + .failoverNodesFor(coin: coin) + .map((e) => ElectrumXNode( + address: e.host, + port: e.port, + name: e.name, + id: e.id, + useSSL: e.useSSL, + )) + .toList(); + final newNode = await getCurrentNode(); + _cachedElectrumXClient = CachedElectrumX.from( + node: newNode, + prefs: _prefs, + failovers: failovers, + ); + _electrumXClient = ElectrumX.from( + node: newNode, + prefs: _prefs, + failovers: failovers, + ); + + if (shouldRefresh) { + unawaited(refresh()); + } + } + + Future> _getMnemonicList() async { + final _mnemonicString = await mnemonicString; + if (_mnemonicString == null) { + return []; + } + final List data = _mnemonicString.split(' '); + return data; + } + + Future getCurrentNode() async { + final node = NodeService(secureStorageInterface: _secureStore) + .getPrimaryNodeFor(coin: coin) ?? + DefaultNodes.getNodeFor(coin); + + return ElectrumXNode( + address: node.host, + port: node.port, + name: node.name, + useSSL: node.useSSL, + id: node.id, + ); + } + + Future> _fetchAllOwnAddresses() async { + final allAddresses = await db + .getAddresses(walletId) + .filter() + .not() + .typeEqualTo(isar_models.AddressType.nonWallet) + .and() + .not() + .subTypeEqualTo(isar_models.AddressSubType.nonWallet) + .findAll(); + return allAddresses; + } + + Future _getFees() async { + try { + //TODO adjust numbers for different speeds? + const int f = 1, m = 5, s = 20; + + final fast = await electrumXClient.estimateFee(blocks: f); + final medium = await electrumXClient.estimateFee(blocks: m); + final slow = await electrumXClient.estimateFee(blocks: s); + + final feeObject = FeeObject( + numberOfBlocksFast: f, + numberOfBlocksAverage: m, + numberOfBlocksSlow: s, + fast: Amount.fromDecimal( + fast, + fractionDigits: coin.decimals, + ).raw.toInt(), + medium: Amount.fromDecimal( + medium, + fractionDigits: coin.decimals, + ).raw.toInt(), + slow: Amount.fromDecimal( + slow, + fractionDigits: coin.decimals, + ).raw.toInt(), + ); + + Logging.instance.log("fetched fees: $feeObject", level: LogLevel.Info); + return feeObject; + } catch (e) { + Logging.instance + .log("Exception rethrown from _getFees(): $e", level: LogLevel.Error); + rethrow; + } + } + + Future _generateNewWallet() async { + Logging.instance + .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); + if (!integrationTestFlag) { + try { + final features = await electrumXClient + .getServerFeatures() + .timeout(const Duration(seconds: 3)); + Logging.instance.log("features: $features", level: LogLevel.Info); + + _serverVersion = + _parseServerVersion(features["server_version"] as String); + + switch (coin) { + case Coin.eCash: + if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { + throw Exception("genesis hash does not match main net!"); + } + break; + // case Coin.e: + // if (features['genesis_hash'] != GENESIS_HASH_TESTNET) { + // throw Exception("genesis hash does not match test net!"); + // } + // break; + default: + throw Exception( + "Attempted to generate a ECashWallet using a non eCash coin type: ${coin.name}"); + } + } catch (e, s) { + Logging.instance.log("$e/n$s", level: LogLevel.Info); + } + } + + // this should never fail + if ((await mnemonicString) != null || (await mnemonicPassphrase) != null) { + throw Exception( + "Attempted to overwrite mnemonic on generate new wallet!"); + } + await _secureStore.write( + key: '${_walletId}_mnemonic', + value: bip39.generateMnemonic(strength: 256)); + await _secureStore.write(key: '${_walletId}_mnemonicPassphrase', value: ""); + + const int startingIndex = 0; + const int receiveChain = 0; + const int changeChain = 1; + + // Generate and add addresses to relevant arrays + final initialAddresses = await Future.wait([ + // P2PKH + _generateAddressForChain( + receiveChain, + startingIndex, + DerivePathType.eCash44, + ), + _generateAddressForChain( + changeChain, + startingIndex, + DerivePathType.eCash44, + ), + // _generateAddressForChain( + // receiveChain, + // startingIndex, + // DerivePathType.bip44, + // ), + // _generateAddressForChain( + // changeChain, + // startingIndex, + // DerivePathType.bip44, + // ), + ]); + + await db.putAddresses(initialAddresses); + + Logging.instance.log("_generateNewWalletFinished", level: LogLevel.Info); + } + + /// Generates a new internal or external chain address for the wallet using a BIP84, BIP44, or BIP49 derivation path. + /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! + /// [index] - This can be any integer >= 0 + Future _generateAddressForChain( + int chain, + int index, + DerivePathType derivePathType, + ) async { + final _mnemonic = await mnemonicString; + final _mnemonicPassphrase = await mnemonicPassphrase; + if (_mnemonicPassphrase == null) { + Logging.instance.log( + "Exception in _generateAddressForChain: mnemonic passphrase null, possible migration issue; if using internal builds, delete wallet and restore from seed, if using a release build, please file bug report", + level: LogLevel.Error); + } + + final derivePath = constructDerivePath( + derivePathType: derivePathType, + networkWIF: _network.wif, + chain: chain, + index: index, + ); + final node = await Bip32Utils.getBip32Node( + _mnemonic!, + _mnemonicPassphrase!, + _network, + derivePath, + ); + + final data = PaymentData(pubkey: node.publicKey); + String address; + isar_models.AddressType addrType; + + switch (derivePathType) { + case DerivePathType.bip44: + case DerivePathType.eCash44: + address = P2PKH(data: data, network: _network).data.address!; + addrType = isar_models.AddressType.p2pkh; + address = bitbox.Address.toECashAddress(address); + break; + default: + throw Exception("DerivePathType $derivePathType not supported"); + } + + // add generated address & info to derivations + // await addDerivation( + // chain: chain, + // address: address, + // pubKey: Format.uint8listToString(node.publicKey), + // wif: node.toWIF(), + // derivePathType: derivePathType, + // ); + + return isar_models.Address( + walletId: walletId, + value: address, + publicKey: node.publicKey, + type: addrType, + derivationIndex: index, + derivationPath: isar_models.DerivationPath()..value = derivePath, + subType: chain == 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change, + ); + } + + /// Returns the latest receiving/change (external/internal) address for the wallet depending on [chain] + /// and + /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! + Future _getCurrentAddressForChain( + int chain, + DerivePathType derivePathType, + ) async { + final subType = chain == 0 // Here, we assume that chain == 1 if it isn't 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change; + + isar_models.AddressType type; + String coinType; + String purpose; + switch (derivePathType) { + case DerivePathType.bip44: + type = isar_models.AddressType.p2pkh; + coinType = "0"; + purpose = "44"; + break; + case DerivePathType.eCash44: + type = isar_models.AddressType.p2pkh; + coinType = "899"; + purpose = "44"; + break; + default: + throw Exception("DerivePathType unsupported"); + } + final address = await db + .getAddresses(walletId) + .filter() + .typeEqualTo(type) + .subTypeEqualTo(subType) + .derivationPath((q) => q.valueStartsWith("m/$purpose'/$coinType")) + .sortByDerivationIndexDesc() + .findFirst(); + return address!.value; + } + + Future>> fastFetch(List allTxHashes) async { + List> allTransactions = []; + + const futureLimit = 30; + List>> transactionFutures = []; + int currentFutureCount = 0; + for (final txHash in allTxHashes) { + Future> transactionFuture = + cachedElectrumXClient.getTransaction( + txHash: txHash, + verbose: true, + coin: coin, + ); + transactionFutures.add(transactionFuture); + currentFutureCount++; + if (currentFutureCount > futureLimit) { + currentFutureCount = 0; + await Future.wait(transactionFutures); + for (final fTx in transactionFutures) { + final tx = await fTx; + + allTransactions.add(tx); + } + } + } + if (currentFutureCount != 0) { + currentFutureCount = 0; + await Future.wait(transactionFutures); + for (final fTx in transactionFutures) { + final tx = await fTx; + + allTransactions.add(tx); + } + } + return allTransactions; + } + + double? _serverVersion; + bool get serverCanBatch => _serverVersion != null && _serverVersion! >= 1.6; + + // stupid + fragile + double? _parseServerVersion(String version) { + double? result; + try { + final list = version.split(" "); + if (list.isNotEmpty) { + final numberStrings = list.last.split("."); + final major = numberStrings.removeAt(0); + + result = double.tryParse("$major.${numberStrings.join("")}"); + } + } catch (_) {} + + Logging.instance.log( + "$walletName _parseServerVersion($version) => $result", + level: LogLevel.Info, + ); + return result; + } + + /// attempts to convert a string to a valid scripthash + /// + /// Returns the scripthash or throws an exception on invalid bch address + String _convertToScriptHash(String address, NetworkType network) { + try { + if (bitbox.Address.detectFormat(address) == + bitbox.Address.formatCashAddr && + validateCashAddr(address)) { + final addressLegacy = bitbox.Address.toLegacyAddress(address); + return AddressUtils.convertToScriptHash(addressLegacy, network); + } + return AddressUtils.convertToScriptHash(address, network); + } catch (e) { + rethrow; + } + } + + Future _updateUTXOs() async { + final allAddresses = await _fetchAllOwnAddresses(); + + try { + final fetchedUtxoList = >>[]; + + if (serverCanBatch) { + final Map>> batches = {}; + const batchSizeMax = 100; + int batchNumber = 0; + for (int i = 0; i < allAddresses.length; i++) { + if (batches[batchNumber] == null) { + batches[batchNumber] = {}; + } + final scriptHash = _convertToScriptHash( + allAddresses[i].value, + _network, + ); + batches[batchNumber]!.addAll({ + scriptHash: [scriptHash] + }); + if (i % batchSizeMax == batchSizeMax - 1) { + batchNumber++; + } + } + + for (int i = 0; i < batches.length; i++) { + final response = await _electrumXClient.getBatchUTXOs( + args: batches[i]!, + ); + for (final entry in response.entries) { + if (entry.value.isNotEmpty) { + fetchedUtxoList.add(entry.value); + } + } + } + } else { + for (int i = 0; i < allAddresses.length; i++) { + final scriptHash = _convertToScriptHash( + allAddresses[i].value, + _network, + ); + + final utxos = await electrumXClient.getUTXOs(scripthash: scriptHash); + if (utxos.isNotEmpty) { + fetchedUtxoList.add(utxos); + } + } + } + + final List outputArray = []; + + for (int i = 0; i < fetchedUtxoList.length; i++) { + for (int j = 0; j < fetchedUtxoList[i].length; j++) { + final jsonUTXO = fetchedUtxoList[i][j]; + + final txn = await cachedElectrumXClient.getTransaction( + txHash: jsonUTXO["tx_hash"] as String, + verbose: true, + coin: coin, + ); + + bool shouldBlock = false; + String? blockReason; + String? label; + + final vout = jsonUTXO["tx_pos"] as int; + + final outputs = txn["vout"] as List; + + String? utxoOwnerAddress; + // get UTXO owner address + for (final output in outputs) { + if (output["n"] == vout) { + utxoOwnerAddress = + output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]?["address"] as String?; + } + } + + final utxo = isar_models.UTXO( + walletId: walletId, + txid: txn["txid"] as String, + vout: vout, + value: jsonUTXO["value"] as int, + name: label ?? "", + isBlocked: shouldBlock, + blockedReason: blockReason, + isCoinbase: txn["is_coinbase"] as bool? ?? false, + blockHash: txn["blockhash"] as String?, + blockHeight: jsonUTXO["height"] as int?, + blockTime: txn["blocktime"] as int?, + address: utxoOwnerAddress, + ); + + outputArray.add(utxo); + } + } + + Logging.instance + .log('Outputs fetched: $outputArray', level: LogLevel.Info); + + await db.updateUTXOs(walletId, outputArray); + + // finally update balance + await _updateBalance(); + } catch (e, s) { + Logging.instance + .log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error); + } + } + + Future _updateBalance() async { + await refreshBalance(); + } + + @override + Balance get balance => _balance ??= getCachedBalance(); + Balance? _balance; + + Future getTxCount({required String address}) async { + String? scripthash; + try { + scripthash = _convertToScriptHash(address, _network); + final transactions = + await electrumXClient.getHistory(scripthash: scripthash); + return transactions.length; + } catch (e) { + Logging.instance.log( + "Exception rethrown in _getTxCount(address: $address, scripthash: $scripthash): $e", + level: LogLevel.Error); + rethrow; + } + } + + Future _getTxCount({required isar_models.Address address}) async { + try { + return await getTxCount(address: address.value); + } catch (e, s) { + Logging.instance.log( + "Exception rethrown in _getTxCount(address: $address: $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + Future> _getBatchTxCount({ + required Map addresses, + }) async { + try { + final Map> args = {}; + for (final entry in addresses.entries) { + args[entry.key] = [_convertToScriptHash(entry.value, _network)]; + } + final response = await electrumXClient.getBatchHistory(args: args); + + final Map result = {}; + for (final entry in response.entries) { + result[entry.key] = entry.value.length; + } + return result; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown in _getBatchTxCount(address: $addresses: $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + Future _checkReceivingAddressForTransactions() async { + try { + final currentReceiving = await _currentReceivingAddress; + + final int txCount = await getTxCount(address: currentReceiving.value); + Logging.instance.log( + 'Number of txs for current receiving address $currentReceiving: $txCount', + level: LogLevel.Info); + + if (txCount >= 1 || currentReceiving.derivationIndex < 0) { + // First increment the receiving index + final newReceivingIndex = currentReceiving.derivationIndex + 1; + + // Use new index to derive a new receiving address + final newReceivingAddress = await _generateAddressForChain( + 0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin)); + + final existing = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(newReceivingAddress.value) + .findFirst(); + if (existing == null) { + // Add that new change address + await db.putAddress(newReceivingAddress); + } else { + // we need to update the address + await db.updateAddress(existing, newReceivingAddress); + } + // keep checking until address with no tx history is set as current + await _checkReceivingAddressForTransactions(); + } + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + Future _checkChangeAddressForTransactions() async { + try { + final currentChange = await _currentChangeAddress; + final int txCount = await getTxCount(address: currentChange.value); + Logging.instance.log( + 'Number of txs for current change address $currentChange: $txCount', + level: LogLevel.Info); + + if (txCount >= 1 || currentChange.derivationIndex < 0) { + // First increment the change index + final newChangeIndex = currentChange.derivationIndex + 1; + + // Use new index to derive a new change address + final newChangeAddress = await _generateAddressForChain( + 1, newChangeIndex, DerivePathTypeExt.primaryFor(coin)); + + final existing = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(newChangeAddress.value) + .findFirst(); + if (existing == null) { + // Add that new change address + await db.putAddress(newChangeAddress); + } else { + // we need to update the address + await db.updateAddress(existing, newChangeAddress); + } + // keep checking until address with no tx history is set as current + await _checkChangeAddressForTransactions(); + } + } on SocketException catch (se, s) { + Logging.instance.log( + "SocketException caught in _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $se\n$s", + level: LogLevel.Error); + return; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + Future _checkCurrentReceivingAddressesForTransactions() async { + try { + await _checkReceivingAddressForTransactions(); + // await _checkReceivingAddressForTransactionsP2PKH(); + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _checkCurrentReceivingAddressesForTransactions(): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + Future _checkCurrentChangeAddressesForTransactions() async { + try { + await _checkChangeAddressForTransactions(); + // await _checkP2PKHChangeAddressForTransactions(); + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _checkCurrentChangeAddressesForTransactions(): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + Future>> _fetchHistory( + List allAddresses, + ) async { + try { + List> allTxHashes = []; + + if (serverCanBatch) { + final Map>> batches = {}; + final Map requestIdToAddressMap = {}; + const batchSizeMax = 100; + int batchNumber = 0; + for (int i = 0; i < allAddresses.length; i++) { + if (batches[batchNumber] == null) { + batches[batchNumber] = {}; + } + final scriptHash = _convertToScriptHash( + allAddresses[i], + _network, + ); + final id = Logger.isTestEnv ? "$i" : const Uuid().v1(); + requestIdToAddressMap[id] = allAddresses[i]; + batches[batchNumber]!.addAll({ + id: [scriptHash] + }); + if (i % batchSizeMax == batchSizeMax - 1) { + batchNumber++; + } + } + + for (int i = 0; i < batches.length; i++) { + final response = + await _electrumXClient.getBatchHistory(args: batches[i]!); + for (final entry in response.entries) { + for (int j = 0; j < entry.value.length; j++) { + entry.value[j]["address"] = requestIdToAddressMap[entry.key]; + if (!allTxHashes.contains(entry.value[j])) { + allTxHashes.add(entry.value[j]); + } + } + } + } + } else { + for (int i = 0; i < allAddresses.length; i++) { + final scriptHash = _convertToScriptHash( + allAddresses[i], + _network, + ); + + final response = await electrumXClient.getHistory( + scripthash: scriptHash, + ); + + for (int j = 0; j < response.length; j++) { + response[j]["address"] = allAddresses[i]; + if (!allTxHashes.contains(response[j])) { + allTxHashes.add(response[j]); + } + } + } + } + + return allTxHashes; + } catch (e, s) { + Logging.instance.log("_fetchHistory: $e\n$s", level: LogLevel.Error); + rethrow; + } + } + + bool _duplicateTxCheck( + List> allTransactions, String txid) { + for (int i = 0; i < allTransactions.length; i++) { + if (allTransactions[i]["txid"] == txid) { + return true; + } + } + return false; + } + + Future _refreshTransactions() async { + List allAddressesOld = await _fetchAllOwnAddresses(); + + Set receivingAddresses = allAddressesOld + .where((e) => e.subType == isar_models.AddressSubType.receiving) + .map((e) { + if (bitbox.Address.detectFormat(e.value) == bitbox.Address.formatLegacy && + (addressType(address: e.value) == DerivePathType.bip44 || + addressType(address: e.value) == DerivePathType.eCash44)) { + return bitbox.Address.toECashAddress(e.value); + } else { + return e.value; + } + }).toSet(); + + Set changeAddresses = allAddressesOld + .where((e) => e.subType == isar_models.AddressSubType.change) + .map((e) { + if (bitbox.Address.detectFormat(e.value) == bitbox.Address.formatLegacy && + (addressType(address: e.value) == DerivePathType.bip44 || + addressType(address: e.value) == DerivePathType.eCash44)) { + return bitbox.Address.toECashAddress(e.value); + } else { + return e.value; + } + }).toSet(); + + final List> allTxHashes = + await _fetchHistory([...receivingAddresses, ...changeAddresses]); + + List> allTransactions = []; + + for (final txHash in allTxHashes) { + final storedTx = await db + .getTransactions(walletId) + .filter() + .txidEqualTo(txHash["tx_hash"] as String) + .findFirst(); + + if (storedTx == null || + storedTx.address.value == null || + storedTx.height == null || + (storedTx.height != null && storedTx.height! <= 0)) { + final tx = await cachedElectrumXClient.getTransaction( + txHash: txHash["tx_hash"] as String, + verbose: true, + coin: coin, + ); + + if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { + tx["address"] = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(txHash["address"] as String) + .findFirst(); + tx["height"] = txHash["height"]; + allTransactions.add(tx); + } + } + } + + final List> txns = []; + + for (final txData in allTransactions) { + Set inputAddresses = {}; + Set outputAddresses = {}; + + Logging.instance.log(txData, level: LogLevel.Fatal); + + Amount totalInputValue = Amount( + rawValue: BigInt.from(0), + fractionDigits: coin.decimals, + ); + Amount totalOutputValue = Amount( + rawValue: BigInt.from(0), + fractionDigits: coin.decimals, + ); + + Amount amountSentFromWallet = Amount( + rawValue: BigInt.from(0), + fractionDigits: coin.decimals, + ); + Amount amountReceivedInWallet = Amount( + rawValue: BigInt.from(0), + fractionDigits: coin.decimals, + ); + Amount changeAmount = Amount( + rawValue: BigInt.from(0), + fractionDigits: coin.decimals, + ); + + // parse inputs + for (final input in txData["vin"] as List) { + final prevTxid = input["txid"] as String; + final prevOut = input["vout"] as int; + + // fetch input tx to get address + final inputTx = await cachedElectrumXClient.getTransaction( + txHash: prevTxid, + coin: coin, + ); + + for (final output in inputTx["vout"] as List) { + // check matching output + if (prevOut == output["n"]) { + // get value + final value = Amount.fromDecimal( + Decimal.parse(output["value"].toString()), + fractionDigits: coin.decimals, + ); + + // add value to total + totalInputValue = totalInputValue + value; + + // get input(prevOut) address + final address = + output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]?["address"] as String?; + + if (address != null) { + inputAddresses.add(address); + + // if input was from my wallet, add value to amount sent + if (receivingAddresses.contains(address) || + changeAddresses.contains(address)) { + amountSentFromWallet = amountSentFromWallet + value; + } + } + } + } + } + + // parse outputs + for (final output in txData["vout"] as List) { + // get value + final value = Amount.fromDecimal( + Decimal.parse(output["value"].toString()), + fractionDigits: coin.decimals, + ); + + // add value to total + totalOutputValue += value; + + // get output address + final address = output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]?["address"] as String?; + if (address != null) { + outputAddresses.add(address); + + // if output was to my wallet, add value to amount received + if (receivingAddresses.contains(address)) { + amountReceivedInWallet += value; + } else if (changeAddresses.contains(address)) { + changeAmount += value; + } + } + } + + final mySentFromAddresses = [ + ...receivingAddresses.intersection(inputAddresses), + ...changeAddresses.intersection(inputAddresses) + ]; + final myReceivedOnAddresses = + receivingAddresses.intersection(outputAddresses); + final myChangeReceivedOnAddresses = + changeAddresses.intersection(outputAddresses); + + final fee = totalInputValue - totalOutputValue; + + // this is the address initially used to fetch the txid + isar_models.Address transactionAddress = + txData["address"] as isar_models.Address; + + isar_models.TransactionType type; + Amount amount; + if (mySentFromAddresses.isNotEmpty && myReceivedOnAddresses.isNotEmpty) { + // tx is sent to self + type = isar_models.TransactionType.sentToSelf; + amount = + amountSentFromWallet - amountReceivedInWallet - fee - changeAmount; + } else if (mySentFromAddresses.isNotEmpty) { + // outgoing tx + type = isar_models.TransactionType.outgoing; + amount = amountSentFromWallet - changeAmount - fee; + final possible = + outputAddresses.difference(myChangeReceivedOnAddresses).first; + + if (transactionAddress.value != possible) { + transactionAddress = isar_models.Address( + walletId: walletId, + value: possible, + publicKey: [], + type: isar_models.AddressType.nonWallet, + derivationIndex: -1, + derivationPath: null, + subType: isar_models.AddressSubType.nonWallet, + ); + } + } else { + // incoming tx + type = isar_models.TransactionType.incoming; + amount = amountReceivedInWallet; + } + + List inputs = []; + List outputs = []; + + for (final json in txData["vin"] as List) { + bool isCoinBase = json['coinbase'] != null; + final input = isar_models.Input( + txid: json['txid'] as String, + vout: json['vout'] as int? ?? -1, + scriptSig: json['scriptSig']?['hex'] as String?, + scriptSigAsm: json['scriptSig']?['asm'] as String?, + isCoinbase: isCoinBase ? isCoinBase : json['is_coinbase'] as bool?, + sequence: json['sequence'] as int?, + innerRedeemScriptAsm: json['innerRedeemscriptAsm'] as String?, + ); + inputs.add(input); + } + + for (final json in txData["vout"] as List) { + final output = isar_models.Output( + scriptPubKey: json['scriptPubKey']?['hex'] as String?, + scriptPubKeyAsm: json['scriptPubKey']?['asm'] as String?, + scriptPubKeyType: json['scriptPubKey']?['type'] as String?, + scriptPubKeyAddress: + json["scriptPubKey"]?["addresses"]?[0] as String? ?? + json['scriptPubKey']['type'] as String, + value: Amount.fromDecimal( + Decimal.parse(json["value"].toString()), + fractionDigits: coin.decimals, + ).raw.toInt(), + ); + outputs.add(output); + } + + final tx = isar_models.Transaction( + walletId: walletId, + txid: txData["txid"] as String, + timestamp: txData["blocktime"] as int? ?? + (DateTime.now().millisecondsSinceEpoch ~/ 1000), + type: type, + subType: isar_models.TransactionSubType.none, + amount: amount.raw.toInt(), + amountString: amount.toJsonString(), + fee: fee.raw.toInt(), + height: txData["height"] as int?, + isCancelled: false, + isLelantus: false, + slateId: null, + otherData: null, + nonce: null, + inputs: inputs, + outputs: outputs, + ); + + txns.add(Tuple2(tx, transactionAddress)); + } + + await db.addNewTransactionData(txns, walletId); + + // quick hack to notify manager to call notifyListeners if + // transactions changed + if (txns.isNotEmpty) { + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "Transactions updated/added for: $walletId $walletName ", + walletId, + ), + ); + } + } + + int estimateTxFee({required int vSize, required int feeRatePerKB}) { + return vSize * (feeRatePerKB / 1000).ceil(); + } + + /// The coinselection algorithm decides whether or not the user is eligible to make the transaction + /// with [satoshiAmountToSend] and [selectedTxFeeRate]. If so, it will call buildTrasaction() and return + /// a map containing the tx hex along with other important information. If not, then it will return + /// an integer (1 or 2) + dynamic coinSelection({ + required int satoshiAmountToSend, + required int selectedTxFeeRate, + required String recipientAddress, + required bool coinControl, + required bool isSendAll, + int additionalOutputs = 0, + List? utxos, + }) async { + Logging.instance + .log("Starting coinSelection ----------", level: LogLevel.Info); + final List availableOutputs = utxos ?? await this.utxos; + final currentChainHeight = await chainHeight; + final List spendableOutputs = []; + int spendableSatoshiValue = 0; + + // Build list of spendable outputs and totaling their satoshi amount + for (final utxo in availableOutputs) { + if (utxo.isBlocked == false && + utxo.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) && + utxo.used != true) { + spendableOutputs.add(utxo); + spendableSatoshiValue += utxo.value; + } + } + + if (coinControl) { + if (spendableOutputs.length < availableOutputs.length) { + throw ArgumentError("Attempted to use an unavailable utxo"); + } + } + + // don't care about sorting if using all utxos + if (!coinControl) { + // sort spendable by age (oldest first) + spendableOutputs.sort((a, b) => b.blockTime!.compareTo(a.blockTime!)); + } + + Logging.instance.log("spendableOutputs.length: ${spendableOutputs.length}", + level: LogLevel.Info); + Logging.instance.log("availableOutputs.length: ${availableOutputs.length}", + level: LogLevel.Info); + Logging.instance + .log("spendableOutputs: $spendableOutputs", level: LogLevel.Info); + Logging.instance.log("spendableSatoshiValue: $spendableSatoshiValue", + level: LogLevel.Info); + Logging.instance + .log("satoshiAmountToSend: $satoshiAmountToSend", level: LogLevel.Info); + // If the amount the user is trying to send is smaller than the amount that they have spendable, + // then return 1, which indicates that they have an insufficient balance. + if (spendableSatoshiValue < satoshiAmountToSend) { + return 1; + // If the amount the user wants to send is exactly equal to the amount they can spend, then return + // 2, which indicates that they are not leaving enough over to pay the transaction fee + } else if (spendableSatoshiValue == satoshiAmountToSend && !isSendAll) { + return 2; + } + // If neither of these statements pass, we assume that the user has a spendable balance greater + // than the amount they're attempting to send. Note that this value still does not account for + // the added transaction fee, which may require an extra input and will need to be checked for + // later on. + + // Possible situation right here + int satoshisBeingUsed = 0; + int inputsBeingConsumed = 0; + List utxoObjectsToUse = []; + + if (!coinControl) { + for (var i = 0; + satoshisBeingUsed < satoshiAmountToSend && + i < spendableOutputs.length; + i++) { + utxoObjectsToUse.add(spendableOutputs[i]); + satoshisBeingUsed += spendableOutputs[i].value; + inputsBeingConsumed += 1; + } + for (int i = 0; + i < additionalOutputs && + inputsBeingConsumed < spendableOutputs.length; + i++) { + utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]); + satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value; + inputsBeingConsumed += 1; + } + } else { + satoshisBeingUsed = spendableSatoshiValue; + utxoObjectsToUse = spendableOutputs; + inputsBeingConsumed = spendableOutputs.length; + } + + Logging.instance + .log("satoshisBeingUsed: $satoshisBeingUsed", level: LogLevel.Info); + Logging.instance + .log("inputsBeingConsumed: $inputsBeingConsumed", level: LogLevel.Info); + Logging.instance + .log('utxoObjectsToUse: $utxoObjectsToUse', level: LogLevel.Info); + + // numberOfOutputs' length must always be equal to that of recipientsArray and recipientsAmtArray + List recipientsArray = [recipientAddress]; + List recipientsAmtArray = [satoshiAmountToSend]; + + // gather required signing data + final utxoSigningData = await fetchBuildTxData(utxoObjectsToUse); + + if (isSendAll) { + Logging.instance + .log("Attempting to send all $coin", level: LogLevel.Info); + + final int vSizeForOneOutput = (await buildTransaction( + utxosToUse: utxoObjectsToUse, + utxoSigningData: utxoSigningData, + recipients: [recipientAddress], + satoshiAmounts: [satoshisBeingUsed - 1], + ))["vSize"] as int; + int feeForOneOutput = estimateTxFee( + vSize: vSizeForOneOutput, + feeRatePerKB: selectedTxFeeRate, + ); + + final int roughEstimate = roughFeeEstimate( + spendableOutputs.length, + 1, + selectedTxFeeRate, + ).raw.toInt(); + if (feeForOneOutput < roughEstimate) { + feeForOneOutput = roughEstimate; + } + + final int amount = satoshiAmountToSend - feeForOneOutput; + dynamic txn = await buildTransaction( + utxosToUse: utxoObjectsToUse, + utxoSigningData: utxoSigningData, + recipients: recipientsArray, + satoshiAmounts: [amount], + ); + Map transactionObject = { + "hex": txn["hex"], + "recipient": recipientsArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(amount), + fractionDigits: coin.decimals, + ), + "fee": feeForOneOutput, + "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), + }; + return transactionObject; + } + + final int vSizeForOneOutput; + try { + vSizeForOneOutput = (await buildTransaction( + utxosToUse: utxoObjectsToUse, + utxoSigningData: utxoSigningData, + recipients: [recipientAddress], + satoshiAmounts: [satoshisBeingUsed - 1], + ))["vSize"] as int; + } catch (e) { + Logging.instance.log("vSizeForOneOutput: $e", level: LogLevel.Error); + rethrow; + } + + final int vSizeForTwoOutPuts; + try { + vSizeForTwoOutPuts = (await buildTransaction( + utxosToUse: utxoObjectsToUse, + utxoSigningData: utxoSigningData, + recipients: [ + recipientAddress, + await _getCurrentAddressForChain( + 1, DerivePathTypeExt.primaryFor(coin)), + ], + satoshiAmounts: [ + satoshiAmountToSend, + max(0, satoshisBeingUsed - satoshiAmountToSend - 1), + ], + ))["vSize"] as int; + } catch (e) { + Logging.instance.log("vSizeForTwoOutPuts: $e", level: LogLevel.Error); + rethrow; + } + + // Assume 1 output, only for recipient and no change + final feeForOneOutput = estimateTxFee( + vSize: vSizeForOneOutput, + feeRatePerKB: selectedTxFeeRate, + ); + // Assume 2 outputs, one for recipient and one for change + final feeForTwoOutputs = estimateTxFee( + vSize: vSizeForTwoOutPuts, + feeRatePerKB: selectedTxFeeRate, + ); + + Logging.instance + .log("feeForTwoOutputs: $feeForTwoOutputs", level: LogLevel.Info); + Logging.instance + .log("feeForOneOutput: $feeForOneOutput", level: LogLevel.Info); + + if (satoshisBeingUsed - satoshiAmountToSend > feeForOneOutput) { + if (satoshisBeingUsed - satoshiAmountToSend > + feeForOneOutput + DUST_LIMIT.raw.toInt()) { + // Here, we know that theoretically, we may be able to include another output(change) but we first need to + // factor in the value of this output in satoshis. + int changeOutputSize = + satoshisBeingUsed - satoshiAmountToSend - feeForTwoOutputs; + // We check to see if the user can pay for the new transaction with 2 outputs instead of one. If they can and + // the second output's size > DUST_LIMIT satoshis, we perform the mechanics required to properly generate and use a new + // change address. + if (changeOutputSize > DUST_LIMIT.raw.toInt() && + satoshisBeingUsed - satoshiAmountToSend - changeOutputSize == + feeForTwoOutputs) { + // generate new change address if current change address has been used + await _checkChangeAddressForTransactions(); + final String newChangeAddress = await _getCurrentAddressForChain( + 1, DerivePathTypeExt.primaryFor(coin)); + + int feeBeingPaid = + satoshisBeingUsed - satoshiAmountToSend - changeOutputSize; + + recipientsArray.add(newChangeAddress); + recipientsAmtArray.add(changeOutputSize); + // At this point, we have the outputs we're going to use, the amounts to send along with which addresses + // we intend to send these amounts to. We have enough to send instructions to build the transaction. + Logging.instance.log('2 outputs in tx', level: LogLevel.Info); + Logging.instance + .log('Input size: $satoshisBeingUsed', level: LogLevel.Info); + Logging.instance.log('Recipient output size: $satoshiAmountToSend', + level: LogLevel.Info); + Logging.instance.log('Change Output Size: $changeOutputSize', + level: LogLevel.Info); + Logging.instance.log( + 'Difference (fee being paid): $feeBeingPaid sats', + level: LogLevel.Info); + Logging.instance + .log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info); + dynamic txn = await buildTransaction( + utxosToUse: utxoObjectsToUse, + utxoSigningData: utxoSigningData, + recipients: recipientsArray, + satoshiAmounts: recipientsAmtArray, + ); + + // make sure minimum fee is accurate if that is being used + if (txn["vSize"] - feeBeingPaid == 1) { + int changeOutputSize = + satoshisBeingUsed - satoshiAmountToSend - (txn["vSize"] as int); + feeBeingPaid = + satoshisBeingUsed - satoshiAmountToSend - changeOutputSize; + recipientsAmtArray.removeLast(); + recipientsAmtArray.add(changeOutputSize); + Logging.instance.log('Adjusted Input size: $satoshisBeingUsed', + level: LogLevel.Info); + Logging.instance.log( + 'Adjusted Recipient output size: $satoshiAmountToSend', + level: LogLevel.Info); + Logging.instance.log( + 'Adjusted Change Output Size: $changeOutputSize', + level: LogLevel.Info); + Logging.instance.log( + 'Adjusted Difference (fee being paid): $feeBeingPaid sats', + level: LogLevel.Info); + Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs', + level: LogLevel.Info); + txn = await buildTransaction( + utxosToUse: utxoObjectsToUse, + utxoSigningData: utxoSigningData, + recipients: recipientsArray, + satoshiAmounts: recipientsAmtArray, + ); + } + + Map transactionObject = { + "hex": txn["hex"], + "recipient": recipientsArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), + "fee": feeBeingPaid, + "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), + }; + return transactionObject; + } else { + // Something went wrong here. It either overshot or undershot the estimated fee amount or the changeOutputSize + // is smaller than or equal to DUST_LIMIT. Revert to single output transaction. + Logging.instance.log('1 output in tx', level: LogLevel.Info); + Logging.instance + .log('Input size: $satoshisBeingUsed', level: LogLevel.Info); + Logging.instance.log('Recipient output size: $satoshiAmountToSend', + level: LogLevel.Info); + Logging.instance.log( + 'Difference (fee being paid): ${satoshisBeingUsed - satoshiAmountToSend} sats', + level: LogLevel.Info); + Logging.instance + .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); + dynamic txn = await buildTransaction( + utxosToUse: utxoObjectsToUse, + utxoSigningData: utxoSigningData, + recipients: recipientsArray, + satoshiAmounts: recipientsAmtArray, + ); + Map transactionObject = { + "hex": txn["hex"], + "recipient": recipientsArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), + "fee": satoshisBeingUsed - satoshiAmountToSend, + "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), + }; + return transactionObject; + } + } else { + // No additional outputs needed since adding one would mean that it'd be smaller than DUST_LIMIT sats + // which makes it uneconomical to add to the transaction. Here, we pass data directly to instruct + // the wallet to begin crafting the transaction that the user requested. + Logging.instance.log('1 output in tx', level: LogLevel.Info); + Logging.instance + .log('Input size: $satoshisBeingUsed', level: LogLevel.Info); + Logging.instance.log('Recipient output size: $satoshiAmountToSend', + level: LogLevel.Info); + Logging.instance.log( + 'Difference (fee being paid): ${satoshisBeingUsed - satoshiAmountToSend} sats', + level: LogLevel.Info); + Logging.instance + .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); + dynamic txn = await buildTransaction( + utxosToUse: utxoObjectsToUse, + utxoSigningData: utxoSigningData, + recipients: recipientsArray, + satoshiAmounts: recipientsAmtArray, + ); + Map transactionObject = { + "hex": txn["hex"], + "recipient": recipientsArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), + "fee": satoshisBeingUsed - satoshiAmountToSend, + "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), + }; + return transactionObject; + } + } else if (satoshisBeingUsed - satoshiAmountToSend == feeForOneOutput) { + // In this scenario, no additional change output is needed since inputs - outputs equal exactly + // what we need to pay for fees. Here, we pass data directly to instruct the wallet to begin + // crafting the transaction that the user requested. + Logging.instance.log('1 output in tx', level: LogLevel.Info); + Logging.instance + .log('Input size: $satoshisBeingUsed', level: LogLevel.Info); + Logging.instance.log('Recipient output size: $satoshiAmountToSend', + level: LogLevel.Info); + Logging.instance.log( + 'Fee being paid: ${satoshisBeingUsed - satoshiAmountToSend} sats', + level: LogLevel.Info); + Logging.instance + .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); + dynamic txn = await buildTransaction( + utxosToUse: utxoObjectsToUse, + utxoSigningData: utxoSigningData, + recipients: recipientsArray, + satoshiAmounts: recipientsAmtArray, + ); + Map transactionObject = { + "hex": txn["hex"], + "recipient": recipientsArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), + "fee": feeForOneOutput, + "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), + }; + return transactionObject; + } else { + // Remember that returning 2 indicates that the user does not have a sufficient balance to + // pay for the transaction fee. Ideally, at this stage, we should check if the user has any + // additional outputs they're able to spend and then recalculate fees. + Logging.instance.log( + 'Cannot pay tx fee - checking for more outputs and trying again', + level: LogLevel.Warning); + // try adding more outputs + if (spendableOutputs.length > inputsBeingConsumed) { + return coinSelection( + satoshiAmountToSend: satoshiAmountToSend, + selectedTxFeeRate: selectedTxFeeRate, + recipientAddress: recipientAddress, + isSendAll: isSendAll, + additionalOutputs: additionalOutputs + 1, + utxos: utxos, + coinControl: coinControl, + ); + } + return 2; + } + } + + Future> fetchBuildTxData( + List utxosToUse, + ) async { + // return data + List signingData = []; + + try { + // Populating the addresses to check + for (var i = 0; i < utxosToUse.length; i++) { + final txid = utxosToUse[i].txid; + final tx = await _cachedElectrumXClient.getTransaction( + txHash: txid, + coin: coin, + ); + + for (final output in tx["vout"] as List) { + final n = output["n"]; + if (n != null && n == utxosToUse[i].vout) { + String address = + output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]["address"] as String; + if (bitbox.Address.detectFormat(address) != + bitbox.Address.formatCashAddr) { + try { + address = bitbox.Address.toECashAddress(address); + } catch (_) { + rethrow; + } + } + + utxosToUse[i] = utxosToUse[i].copyWith(address: address); + } + } + + final derivePathType = addressType(address: utxosToUse[i].address!); + + signingData.add( + SigningData( + derivePathType: derivePathType, + utxo: utxosToUse[i], + ), + ); + } + + final root = await Bip32Utils.getBip32Root( + (await mnemonicString)!, + (await mnemonicPassphrase)!, + _network, + ); + + for (final sd in signingData) { + final address = await db.getAddress(walletId, sd.utxo.address!); + final node = await Bip32Utils.getBip32NodeFromRoot( + root, + address!.derivationPath!.value, + ); + + final paymentData = PaymentData(pubkey: node.publicKey); + + final PaymentData data; + final Uint8List? redeemScript; + + switch (sd.derivePathType) { + case DerivePathType.bip44: + case DerivePathType.eCash44: + data = P2PKH( + data: paymentData, + network: _network, + ).data; + redeemScript = null; + break; + + default: + throw Exception("DerivePathType unsupported"); + } + + final keyPair = ECPair.fromWIF( + node.toWIF(), + network: _network, + ); + + sd.redeemScript = redeemScript; + sd.output = data.output; + sd.keyPair = keyPair; + } + + return signingData; + } catch (e, s) { + Logging.instance + .log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error); + rethrow; + } + } + + /// Builds and signs a transaction + Future> buildTransaction({ + required List utxosToUse, + required List utxoSigningData, + required List recipients, + required List satoshiAmounts, + }) async { + final builder = bitbox.Bitbox.transactionBuilder( + testnet: coin == Coin.bitcoincashTestnet, + ); + + // retrieve address' utxos from the rest api + List _utxos = + []; // await Bitbox.Address.utxo(address) as List; + for (var element in utxosToUse) { + _utxos.add(bitbox.Utxo( + element.txid, + element.vout, + bitbox.BitcoinCash.fromSatoshi(element.value), + element.value, + 0, + MINIMUM_CONFIRMATIONS + 1)); + } + Logger.print("bch utxos: $_utxos"); + + // placeholder for input signatures + final List> signatures = []; + + // placeholder for total input balance + // int totalBalance = 0; + + // iterate through the list of address _utxos and use them as inputs for the + // withdrawal transaction + for (var utxo in _utxos) { + // add the utxo as an input for the transaction + builder.addInput(utxo.txid, utxo.vout); + final ec = + utxoSigningData.firstWhere((e) => e.utxo.txid == utxo.txid).keyPair!; + + final bitboxEC = bitbox.ECPair.fromWIF(ec.toWIF()); + + // add a signature to the list to be used later + signatures.add({ + "vin": signatures.length, + "key_pair": bitboxEC, + "original_amount": utxo.satoshis + }); + + // totalBalance += utxo.satoshis; + } + + // calculate the fee based on number of inputs and one expected output + // final fee = + // bitbox.BitcoinCash.getByteCount(signatures.length, recipients.length); + + // calculate how much balance will be left over to spend after the fee + // final sendAmount = totalBalance - fee; + + // add the output based on the address provided in the testing data + for (int i = 0; i < recipients.length; i++) { + String recipient = recipients[i]; + int satoshiAmount = satoshiAmounts[i]; + builder.addOutput(recipient, satoshiAmount); + } + + // sign all inputs + for (var signature in signatures) { + builder.sign( + signature["vin"] as int, + signature["key_pair"] as bitbox.ECPair, + signature["original_amount"] as int); + } + + // build the transaction + final tx = builder.build(); + final txHex = tx.toHex(); + final vSize = tx.virtualSize(); + //todo: check if print needed + Logger.print("ecash raw hex: $txHex"); + + return {"hex": txHex, "vSize": vSize}; + } + + @override + Future fullRescan( + int maxUnusedAddressGap, + int maxNumberOfIndexesToCheck, + ) async { + Logging.instance.log("Starting full rescan!", level: LogLevel.Info); + longMutex = true; + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.syncing, + walletId, + coin, + ), + ); + + // clear cache + await _cachedElectrumXClient.clearSharedTransactionCache(coin: coin); + + // back up data + // await _rescanBackup(); + + await db.deleteWalletBlockchainData(walletId); + + try { + final _mnemonic = await mnemonicString; + final _mnemonicPassphrase = await mnemonicPassphrase; + if (_mnemonicPassphrase == null) { + Logging.instance.log( + "Exception in fullRescan: mnemonic passphrase null, possible migration issue; if using internal builds, delete wallet and restore from seed, if using a release build, please file bug report", + level: LogLevel.Error); + } + + await _recoverWalletFromBIP32SeedPhrase( + mnemonic: _mnemonic!, + mnemonicPassphrase: _mnemonicPassphrase!, + maxUnusedAddressGap: maxUnusedAddressGap, + maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + isRescan: true, + ); + + longMutex = false; + await refresh(); + Logging.instance.log("Full rescan complete!", level: LogLevel.Info); + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.synced, + walletId, + coin, + ), + ); + } catch (e, s) { + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.unableToSync, + walletId, + coin, + ), + ); + + // restore from backup + // await _rescanRestore(); + + longMutex = false; + Logging.instance.log("Exception rethrown from fullRescan(): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + Future _recoverWalletFromBIP32SeedPhrase({ + required String mnemonic, + required String mnemonicPassphrase, + int maxUnusedAddressGap = 20, + int maxNumberOfIndexesToCheck = 1000, + bool isRescan = false, + }) async { + longMutex = true; + + final root = await Bip32Utils.getBip32Root( + mnemonic, + mnemonicPassphrase, + _network, + ); + + final deriveTypes = [ + DerivePathType.eCash44, + DerivePathType.bip44, + ]; + + final List, DerivePathType>>> + receiveFutures = []; + final List, DerivePathType>>> + changeFutures = []; + + const txCountBatchSize = 12; + const receiveChain = 0; + const changeChain = 1; + const indexZero = 0; + + try { + // receiving addresses + Logging.instance.log( + "checking receiving addresses...", + level: LogLevel.Info, + ); + + if (serverCanBatch) { + for (final type in deriveTypes) { + receiveFutures.add( + _checkGapsBatched( + maxNumberOfIndexesToCheck, + maxUnusedAddressGap, + txCountBatchSize, + root, + type, + receiveChain, + ), + ); + } + } else { + for (final type in deriveTypes) { + receiveFutures.add( + _checkGaps( + maxNumberOfIndexesToCheck, + maxUnusedAddressGap, + root, + type, + receiveChain, + ), + ); + } + } + + Logging.instance.log( + "checking change addresses...", + level: LogLevel.Info, + ); + // change addresses + + if (serverCanBatch) { + for (final type in deriveTypes) { + changeFutures.add( + _checkGapsBatched( + maxNumberOfIndexesToCheck, + maxUnusedAddressGap, + txCountBatchSize, + root, + type, + changeChain, + ), + ); + } + } else { + for (final type in deriveTypes) { + changeFutures.add( + _checkGaps( + maxNumberOfIndexesToCheck, + maxUnusedAddressGap, + root, + type, + changeChain, + ), + ); + } + } + + // io limitations may require running these linearly instead + final futuresResult = await Future.wait([ + Future.wait(receiveFutures), + Future.wait(changeFutures), + ]); + + final receiveResults = futuresResult[0]; + final changeResults = futuresResult[1]; + + final List addressesToStore = []; + + // If restoring a wallet that never received any funds, then set receivingArray manually + // If we didn't do this, it'd store an empty array + for (final tuple in receiveResults) { + if (tuple.item1.isEmpty) { + final address = await _generateAddressForChain( + receiveChain, + indexZero, + tuple.item2, + ); + addressesToStore.add(address); + } else { + addressesToStore.addAll(tuple.item1); + } + } + + // If restoring a wallet that never sent any funds with change, then set changeArray + // manually. If we didn't do this, it'd store an empty array. + for (final tuple in changeResults) { + if (tuple.item1.isEmpty) { + final address = await _generateAddressForChain( + changeChain, + indexZero, + tuple.item2, + ); + addressesToStore.add(address); + } else { + addressesToStore.addAll(tuple.item1); + } + } + + if (isRescan) { + await db.updateOrPutAddresses(addressesToStore); + } else { + await db.putAddresses(addressesToStore); + } + + await _updateUTXOs(); + + await Future.wait([ + updateCachedId(walletId), + updateCachedIsFavorite(false), + ]); + + longMutex = false; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _recoverWalletFromBIP32SeedPhrase(): $e\n$s", + level: LogLevel.Info); + + longMutex = false; + rethrow; + } + } + + Future, DerivePathType>> _checkGaps( + int maxNumberOfIndexesToCheck, + int maxUnusedAddressGap, + bip32.BIP32 root, + DerivePathType type, + int chain, + ) async { + List addressArray = []; + int gapCounter = 0; + for (int index = 0; + index < maxNumberOfIndexesToCheck && gapCounter < maxUnusedAddressGap; + index++) { + Logging.instance.log( + "index: $index, \t GapCounter chain=$chain ${type.name}: $gapCounter", + level: LogLevel.Info); + + final derivePath = constructDerivePath( + derivePathType: type, + networkWIF: root.network.wif, + chain: chain, + index: index, + ); + final node = await Bip32Utils.getBip32NodeFromRoot(root, derivePath); + + String addressString; + final data = PaymentData(pubkey: node.publicKey); + isar_models.AddressType addressType; + switch (type) { + case DerivePathType.bip44: + case DerivePathType.eCash44: + addressString = P2PKH(data: data, network: _network).data.address!; + addressType = isar_models.AddressType.p2pkh; + addressString = bitbox.Address.toECashAddress(addressString); + break; + default: + throw Exception("DerivePathType $type not supported"); + } + + final address = isar_models.Address( + walletId: walletId, + value: addressString, + publicKey: node.publicKey, + type: addressType, + derivationIndex: index, + derivationPath: isar_models.DerivationPath()..value = derivePath, + subType: chain == 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change, + ); + + // get address tx count + final count = await _getTxCount(address: address); + + // check and add appropriate addresses + if (count > 0) { + // add address to array + addressArray.add(address); + // reset counter + gapCounter = 0; + // add info to derivations + } else { + // increase counter when no tx history found + gapCounter++; + } + } + + return Tuple2(addressArray, type); + } + + Future, DerivePathType>> _checkGapsBatched( + int maxNumberOfIndexesToCheck, + int maxUnusedAddressGap, + int txCountBatchSize, + bip32.BIP32 root, + DerivePathType type, + int chain, + ) async { + List addressArray = []; + int gapCounter = 0; + for (int index = 0; + index < maxNumberOfIndexesToCheck && gapCounter < maxUnusedAddressGap; + index += txCountBatchSize) { + List iterationsAddressArray = []; + Logging.instance.log( + "index: $index, \t GapCounter $chain ${type.name}: $gapCounter", + level: LogLevel.Info); + + final _id = "k_$index"; + Map txCountCallArgs = {}; + final Map receivingNodes = {}; + + for (int j = 0; j < txCountBatchSize; j++) { + final derivePath = constructDerivePath( + derivePathType: type, + networkWIF: root.network.wif, + chain: chain, + index: index + j, + ); + final node = await Bip32Utils.getBip32NodeFromRoot(root, derivePath); + + String addressString; + final data = PaymentData(pubkey: node.publicKey); + isar_models.AddressType addrType; + switch (type) { + case DerivePathType.bip44: + case DerivePathType.eCash44: + addressString = P2PKH(data: data, network: _network).data.address!; + addrType = isar_models.AddressType.p2pkh; + addressString = bitbox.Address.toECashAddress(addressString); + break; + default: + throw Exception("DerivePathType $type not supported"); + } + + final address = isar_models.Address( + walletId: walletId, + value: addressString, + publicKey: node.publicKey, + type: addrType, + derivationIndex: index + j, + derivationPath: isar_models.DerivationPath()..value = derivePath, + subType: chain == 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change, + ); + + receivingNodes.addAll({ + "${_id}_$j": { + "node": node, + "address": address, + } + }); + txCountCallArgs.addAll({ + "${_id}_$j": addressString, + }); + } + + // get address tx counts + final counts = await _getBatchTxCount(addresses: txCountCallArgs); + + // check and add appropriate addresses + for (int k = 0; k < txCountBatchSize; k++) { + int count = counts["${_id}_$k"]!; + if (count > 0) { + final node = receivingNodes["${_id}_$k"]; + final address = node["address"] as isar_models.Address; + // add address to array + addressArray.add(address); + iterationsAddressArray.add(address.value); + // set current index + // reset counter + gapCounter = 0; + // add info to derivations + } + + // increase counter when no tx history found + if (count == 0) { + gapCounter++; + } + } + // cache all the transactions while waiting for the current function to finish. + unawaited(getTransactionCacheEarly(iterationsAddressArray)); + } + + return Tuple2(addressArray, type); + } + + Future getTransactionCacheEarly(List allAddresses) async { + try { + final List> allTxHashes = + await _fetchHistory(allAddresses); + for (final txHash in allTxHashes) { + try { + unawaited(cachedElectrumXClient.getTransaction( + txHash: txHash["tx_hash"] as String, + verbose: true, + coin: coin, + )); + } catch (e) { + continue; + } + } + } catch (e) { + // + } + } + + bool _shouldAutoSync = false; + + @override + bool get shouldAutoSync => _shouldAutoSync; + + @override + set shouldAutoSync(bool shouldAutoSync) { + if (_shouldAutoSync != shouldAutoSync) { + _shouldAutoSync = shouldAutoSync; + if (!shouldAutoSync) { + timer?.cancel(); + timer = null; + stopNetworkAlivePinging(); + } else { + startNetworkAlivePinging(); + refresh(); + } + } + } + + bool isActive = false; + + @override + void Function(bool)? get onIsActiveWalletChanged => + (isActive) => this.isActive = isActive; + + @override + Future estimateFeeFor(Amount amount, int feeRate) async { + final available = balance.spendable; + + if (available == amount) { + return amount - (await sweepAllEstimate(feeRate)); + } else if (amount <= Amount.zero || amount > available) { + return roughFeeEstimate(1, 2, feeRate); + } + + Amount runningBalance = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); + int inputCount = 0; + for (final output in (await utxos)) { + if (!output.isBlocked) { + runningBalance += Amount( + rawValue: BigInt.from(output.value), + fractionDigits: coin.decimals, + ); + inputCount++; + if (runningBalance > amount) { + break; + } + } + } + + final oneOutPutFee = roughFeeEstimate(inputCount, 1, feeRate); + final twoOutPutFee = roughFeeEstimate(inputCount, 2, feeRate); + + if (runningBalance - amount > oneOutPutFee) { + if (runningBalance - amount > oneOutPutFee + DUST_LIMIT) { + final change = runningBalance - amount - twoOutPutFee; + if (change > DUST_LIMIT && + runningBalance - amount - change == twoOutPutFee) { + return runningBalance - amount - change; + } else { + return runningBalance - amount; + } + } else { + return runningBalance - amount; + } + } else if (runningBalance - amount == oneOutPutFee) { + return oneOutPutFee; + } else { + return twoOutPutFee; + } + } + + // TODO: correct formula for ecash? + Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { + return Amount( + rawValue: BigInt.from(((181 * inputCount) + (34 * outputCount) + 10) * + (feeRatePerKB / 1000).ceil()), + fractionDigits: coin.decimals, + ); + } + + Future sweepAllEstimate(int feeRate) async { + int available = 0; + int inputCount = 0; + for (final output in (await utxos)) { + if (!output.isBlocked && + output.isConfirmed(storedChainHeight, MINIMUM_CONFIRMATIONS)) { + available += output.value; + inputCount++; + } + } + + // transaction will only have 1 output minus the fee + final estimatedFee = roughFeeEstimate(inputCount, 1, feeRate); + + return Amount( + rawValue: BigInt.from(available), + fractionDigits: coin.decimals, + ) - + estimatedFee; + } + + @override + Future generateNewAddress() async { + try { + final currentReceiving = await _currentReceivingAddress; + + final newReceivingIndex = currentReceiving.derivationIndex + 1; + + // Use new index to derive a new receiving address + final newReceivingAddress = await _generateAddressForChain( + 0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin)); + + // Add that new receiving address + await db.putAddress(newReceivingAddress); + + return true; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from generateNewAddress(): $e\n$s", + level: LogLevel.Error); + return false; + } + } + + @override + Future get xpub async { + final node = await Bip32Utils.getBip32Root( + (await mnemonic).join(" "), + await mnemonicPassphrase ?? "", + _network, + ); + + return node.neutered().toBase58(); + } + + @override + Future> prepareSend({ + required String address, + required Amount amount, + Map? args, + }) async { + try { + final feeRateType = args?["feeRate"]; + final feeRateAmount = args?["feeRateAmount"]; + final utxos = args?["UTXOs"] as Set?; + if (feeRateType is FeeRateType || feeRateAmount is int) { + late final int rate; + if (feeRateType is FeeRateType) { + int fee = 0; + final feeObject = await fees; + switch (feeRateType) { + case FeeRateType.fast: + fee = feeObject.fast; + break; + case FeeRateType.average: + fee = feeObject.medium; + break; + case FeeRateType.slow: + fee = feeObject.slow; + break; + } + rate = fee; + } else { + rate = feeRateAmount as int; + } + + // check for send all + bool isSendAll = false; + if (amount == balance.spendable) { + isSendAll = true; + } + + final bool coinControl = utxos != null; + + final txData = await coinSelection( + satoshiAmountToSend: amount.raw.toInt(), + selectedTxFeeRate: rate, + recipientAddress: address, + isSendAll: isSendAll, + utxos: utxos?.toList(), + coinControl: coinControl, + ); + + Logging.instance.log("prepare send: $txData", level: LogLevel.Info); + try { + if (txData is int) { + switch (txData) { + case 1: + throw Exception("Insufficient balance!"); + case 2: + throw Exception( + "Insufficient funds to pay for transaction fee!"); + default: + throw Exception("Transaction failed with error code $txData"); + } + } else { + final hex = txData["hex"]; + + if (hex is String) { + final fee = txData["fee"] as int; + final vSize = txData["vSize"] as int; + + Logging.instance + .log("prepared txHex: $hex", level: LogLevel.Info); + Logging.instance.log("prepared fee: $fee", level: LogLevel.Info); + Logging.instance + .log("prepared vSize: $vSize", level: LogLevel.Info); + + // fee should never be less than vSize sanity check + if (fee < vSize) { + throw Exception( + "Error in fee calculation: Transaction fee cannot be less than vSize"); + } + + return txData as Map; + } else { + throw Exception("prepared hex is not a String!!!"); + } + } + } catch (e, s) { + Logging.instance.log("Exception rethrown from prepareSend(): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } else { + throw ArgumentError("Invalid fee rate argument provided!"); + } + } catch (e, s) { + Logging.instance.log("Exception rethrown from prepareSend(): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + @override + Future confirmSend({required Map txData}) async { + try { + Logging.instance.log("confirmSend txData: $txData", level: LogLevel.Info); + + final hex = txData["hex"] as String; + + final txHash = await _electrumXClient.broadcastTransaction(rawTx: hex); + Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info); + + final utxos = txData["usedUTXOs"] as List; + + // mark utxos as used + await db.putUTXOs(utxos.map((e) => e.copyWith(used: true)).toList()); + + return txHash; + } catch (e, s) { + Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + @override + Future testNetworkConnection() async { + try { + final result = await _electrumXClient.ping(); + return result; + } catch (_) { + return false; + } + } + + Timer? _networkAliveTimer; + + void startNetworkAlivePinging() { + // call once on start right away + _periodicPingCheck(); + + // then periodically check + _networkAliveTimer = Timer.periodic( + Constants.networkAliveTimerDuration, + (_) async { + _periodicPingCheck(); + }, + ); + } + + void _periodicPingCheck() async { + bool hasNetwork = await testNetworkConnection(); + _isConnected = hasNetwork; + if (_isConnected != hasNetwork) { + NodeConnectionStatus status = hasNetwork + ? NodeConnectionStatus.connected + : NodeConnectionStatus.disconnected; + GlobalEventBus.instance + .fire(NodeConnectionStatusChangedEvent(status, walletId, coin)); + } + } + + void stopNetworkAlivePinging() { + _networkAliveTimer?.cancel(); + _networkAliveTimer = null; + } + + bool _isConnected = false; + + @override + bool get isConnected => _isConnected; + + @override + Future initializeNew() async { + Logging.instance + .log("Generating new ${coin.prettyName} wallet.", level: LogLevel.Info); + + if (getCachedId() != null) { + throw Exception( + "Attempted to initialize a new wallet using an existing wallet ID!"); + } + + await _prefs.init(); + try { + await _generateNewWallet(); + } catch (e, s) { + Logging.instance.log("Exception rethrown from initializeNew(): $e\n$s", + level: LogLevel.Fatal); + rethrow; + } + await Future.wait([ + updateCachedId(walletId), + updateCachedIsFavorite(false), + ]); + } + + @override + Future initializeExisting() async { + Logging.instance.log("initializeExisting() ${coin.prettyName} wallet.", + level: LogLevel.Info); + + try { + final features = await electrumXClient.getServerFeatures(); + _serverVersion = + _parseServerVersion(features["server_version"] as String); + } catch (_) { + // catch nothing as failure here means we just do not batch certain rpc + // calls + } + + if (getCachedId() == null) { + throw Exception( + "Attempted to initialize an existing wallet using an unknown wallet ID!"); + } + + await _prefs.init(); + // await _checkCurrentChangeAddressesForTransactions(); + // await _checkCurrentReceivingAddressesForTransactions(); + } + + // hack to add tx to txData before refresh completes + // required based on current app architecture where we don't properly store + // transactions locally in a good way + @override + Future updateSentCachedTxData(Map txData) async { + final transaction = isar_models.Transaction( + walletId: walletId, + txid: txData["txid"] as String, + timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, + type: isar_models.TransactionType.outgoing, + subType: isar_models.TransactionSubType.none, + // precision may be lost here hence the following amountString + amount: (txData["recipientAmt"] as Amount).raw.toInt(), + amountString: (txData["recipientAmt"] as Amount).toJsonString(), + fee: txData["fee"] as int, + height: null, + isCancelled: false, + isLelantus: false, + otherData: null, + slateId: null, + nonce: null, + inputs: [], + outputs: [], + ); + + final address = txData["address"] is String + ? await db.getAddress(walletId, txData["address"] as String) + : null; + + await db.addNewTransactionData( + [ + Tuple2(transaction, address), + ], + walletId, + ); + } + + @override + bool get isRefreshing => refreshMutex; + + bool refreshMutex = false; + + //TODO Show percentages properly/more consistently + /// Refreshes display data for the wallet + @override + Future refresh() async { + if (refreshMutex) { + Logging.instance.log("$walletId $walletName refreshMutex denied", + level: LogLevel.Info); + return; + } else { + refreshMutex = true; + } + + try { + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.syncing, + walletId, + coin, + ), + ); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId)); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId)); + + final currentHeight = await chainHeight; + const storedHeight = 1; //await storedChainHeight; + + Logging.instance + .log("chain height: $currentHeight", level: LogLevel.Info); + // Logging.instance + // .log("cached height: $storedHeight", level: LogLevel.Info); + + if (currentHeight != storedHeight) { + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId)); + await _checkCurrentChangeAddressesForTransactions(); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId)); + await _checkCurrentReceivingAddressesForTransactions(); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.4, walletId)); + + final fetchFuture = _refreshTransactions(); + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(0.50, walletId)); + + final feeObj = _getFees(); + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(0.60, walletId)); + + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(0.70, walletId)); + _feeObject = Future(() => feeObj); + + await fetchFuture; + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(0.80, walletId)); + + await _updateUTXOs(); + await getAllTxsToWatch(); + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(0.90, walletId)); + } + + refreshMutex = false; + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(1.0, walletId)); + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.synced, + walletId, + coin, + ), + ); + + if (shouldAutoSync) { + timer ??= Timer.periodic(const Duration(seconds: 30), (timer) async { + Logging.instance.log( + "Periodic refresh check for $walletId $walletName in object instance: $hashCode", + level: LogLevel.Info); + // chain height check currently broken + // if ((await chainHeight) != (await storedChainHeight)) { + if (await refreshIfThereIsNewData()) { + await refresh(); + GlobalEventBus.instance.fire(UpdatedInBackgroundEvent( + "New data found in $walletId $walletName in background!", + walletId)); + } + // } + }); + } + } catch (error, strace) { + refreshMutex = false; + GlobalEventBus.instance.fire( + NodeConnectionStatusChangedEvent( + NodeConnectionStatus.disconnected, + walletId, + coin, + ), + ); + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.unableToSync, + walletId, + coin, + ), + ); + Logging.instance.log( + "Caught exception in refreshWalletData(): $error\n$strace", + level: LogLevel.Error); + } + } + + Future refreshIfThereIsNewData() async { + if (longMutex) return false; + if (_hasCalledExit) return false; + Logging.instance.log("refreshIfThereIsNewData", level: LogLevel.Info); + + try { + bool needsRefresh = false; + Set txnsToCheck = {}; + + for (final String txid in txTracker.pendings) { + if (!txTracker.wasNotifiedConfirmed(txid)) { + txnsToCheck.add(txid); + } + } + + for (String txid in txnsToCheck) { + final txn = await electrumXClient.getTransaction(txHash: txid); + int confirmations = txn["confirmations"] as int? ?? 0; + bool isUnconfirmed = confirmations < MINIMUM_CONFIRMATIONS; + if (!isUnconfirmed) { + // unconfirmedTxs = {}; + needsRefresh = true; + break; + } + } + if (!needsRefresh) { + final allOwnAddresses = await _fetchAllOwnAddresses(); + List> allTxs = await _fetchHistory( + allOwnAddresses.map((e) => e.value).toList(growable: false)); + for (Map transaction in allTxs) { + final txid = transaction['tx_hash'] as String; + if ((await db + .getTransactions(walletId) + .filter() + .txidMatches(txid) + .findFirst()) == + null) { + Logging.instance.log( + " txid not found in address history already ${transaction['tx_hash']}", + level: LogLevel.Info); + needsRefresh = true; + break; + } + } + } + return needsRefresh; + } on NoSuchTransactionException catch (e) { + // TODO: move direct transactions elsewhere + await db.isar.writeTxn(() async { + await db.isar.transactions.deleteByTxidWalletId(e.txid, walletId); + }); + await txTracker.deleteTransaction(e.txid); + return true; + } catch (e, s) { + Logging.instance.log( + "Exception caught in refreshIfThereIsNewData: $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + Future getAllTxsToWatch() async { + if (_hasCalledExit) return; + List unconfirmedTxnsToNotifyPending = []; + List unconfirmedTxnsToNotifyConfirmed = []; + + final currentChainHeight = await chainHeight; + + final txCount = await db.getTransactions(walletId).count(); + + const paginateLimit = 50; + + for (int i = 0; i < txCount; i += paginateLimit) { + final transactions = await db + .getTransactions(walletId) + .offset(i) + .limit(paginateLimit) + .findAll(); + for (final tx in transactions) { + if (tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { + // get all transactions that were notified as pending but not as confirmed + if (txTracker.wasNotifiedPending(tx.txid) && + !txTracker.wasNotifiedConfirmed(tx.txid)) { + unconfirmedTxnsToNotifyConfirmed.add(tx); + } + } else { + // get all transactions that were not notified as pending yet + if (!txTracker.wasNotifiedPending(tx.txid)) { + unconfirmedTxnsToNotifyPending.add(tx); + } + } + } + } + + // notify on unconfirmed transactions + for (final tx in unconfirmedTxnsToNotifyPending) { + final confirmations = tx.getConfirmations(currentChainHeight); + + if (tx.type == isar_models.TransactionType.incoming) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Incoming transaction", + walletId: walletId, + walletName: walletName, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, + coin: coin, + txid: tx.txid, + confirmations: confirmations, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + ), + ); + await txTracker.addNotifiedPending(tx.txid); + } else if (tx.type == isar_models.TransactionType.outgoing) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Sending transaction", + walletId: walletId, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, + txid: tx.txid, + confirmations: confirmations, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); + await txTracker.addNotifiedPending(tx.txid); + } + } + + // notify on confirmed + for (final tx in unconfirmedTxnsToNotifyConfirmed) { + if (tx.type == isar_models.TransactionType.incoming) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Incoming transaction confirmed", + walletId: walletId, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: false, + txid: tx.txid, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); + await txTracker.addNotifiedConfirmed(tx.txid); + } else if (tx.type == isar_models.TransactionType.outgoing) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Outgoing transaction confirmed", + walletId: walletId, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: false, + txid: tx.txid, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); + await txTracker.addNotifiedConfirmed(tx.txid); + } + } + } + + @override + Future recoverFromMnemonic({ + required String mnemonic, + String? mnemonicPassphrase, + required int maxUnusedAddressGap, + required int maxNumberOfIndexesToCheck, + required int height, + }) async { + longMutex = true; + final start = DateTime.now(); + try { + Logging.instance.log("IS_INTEGRATION_TEST: $integrationTestFlag", + level: LogLevel.Info); + if (!integrationTestFlag) { + final features = await electrumXClient.getServerFeatures(); + Logging.instance.log("features: $features", level: LogLevel.Info); + _serverVersion = + _parseServerVersion(features["server_version"] as String); + switch (coin) { + case Coin.eCash: + if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { + throw Exception("genesis hash does not match main net!"); + } + break; + // case Coin.bitcoinTestNet: + // if (features['genesis_hash'] != GENESIS_HASH_TESTNET) { + // throw Exception("genesis hash does not match test net!"); + // } + // break; + default: + throw Exception( + "Attempted to generate a ECashWallet using a non eCash coin type: ${coin.name}"); + } + } + // check to make sure we aren't overwriting a mnemonic + // this should never fail + if ((await mnemonicString) != null || + (await this.mnemonicPassphrase) != null) { + longMutex = false; + throw Exception("Attempted to overwrite mnemonic on restore!"); + } + await _secureStore.write( + key: '${_walletId}_mnemonic', value: mnemonic.trim()); + await _secureStore.write( + key: '${_walletId}_mnemonicPassphrase', + value: mnemonicPassphrase ?? "", + ); + + await _recoverWalletFromBIP32SeedPhrase( + mnemonic: mnemonic.trim(), + mnemonicPassphrase: mnemonicPassphrase ?? "", + maxUnusedAddressGap: maxUnusedAddressGap, + maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + ); + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from recoverFromMnemonic(): $e\n$s", + level: LogLevel.Error); + longMutex = false; + rethrow; + } + longMutex = false; + + final end = DateTime.now(); + Logging.instance.log( + "$walletName recovery time: ${end.difference(start).inMilliseconds} millis", + level: LogLevel.Info); + } +} diff --git a/lib/services/coins/epiccash/epiccash_wallet.dart b/lib/services/coins/epiccash/epiccash_wallet.dart index c5bae8bb7..137be8001 100644 --- a/lib/services/coins/epiccash/epiccash_wallet.dart +++ b/lib/services/coins/epiccash/epiccash_wallet.dart @@ -1,20 +1,21 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:ffi'; import 'dart:io'; import 'dart:isolate'; import 'package:decimal/decimal.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_libepiccash/epic_cash.dart'; -import 'package:hive/hive.dart'; -import 'package:http/http.dart'; +import 'package:isar/isar.dart'; import 'package:mutex/mutex.dart'; import 'package:stack_wallet_backup/generate_password.dart'; -import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/epicbox_config_model.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/models/paymint/fee_object_model.dart'; -import 'package:stackwallet/models/paymint/transactions_model.dart'; -import 'package:stackwallet/models/paymint/utxo_model.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart'; import 'package:stackwallet/services/coins/coin_service.dart'; import 'package:stackwallet/services/event_bus/events/global/blocks_remaining_event.dart'; @@ -23,9 +24,13 @@ import 'package:stackwallet/services/event_bus/events/global/refresh_percent_cha import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +import 'package:stackwallet/services/mixins/epic_cash_hive.dart'; +import 'package:stackwallet/services/mixins/wallet_cache.dart'; +import 'package:stackwallet/services/mixins/wallet_db.dart'; import 'package:stackwallet/services/node_service.dart'; -import 'package:stackwallet/services/price.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/default_epicboxes.dart'; import 'package:stackwallet/utilities/default_nodes.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; @@ -34,8 +39,9 @@ import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/stack_file_system.dart'; import 'package:stackwallet/utilities/test_epic_box_connection.dart'; import 'package:tuple/tuple.dart'; +import 'package:websocket_universal/websocket_universal.dart'; -const int MINIMUM_CONFIRMATIONS = 10; +const int MINIMUM_CONFIRMATIONS = 3; const String GENESIS_HASH_MAINNET = ""; const String GENESIS_HASH_TESTNET = ""; @@ -51,6 +57,10 @@ class BadEpicHttpAddressException implements Exception { } } +abstract class ListenerManager { + static Pointer? pointer; +} + // isolate Map isolates = {}; @@ -83,46 +93,6 @@ Future executeNative(Map arguments) async { sendPort.send(result); return; } - } else if (function == "getPendingSlates") { - final wallet = arguments['wallet'] as String?; - final secretKeyIndex = arguments['secretKeyIndex'] as int?; - final slates = arguments['slates'] as String; - Map result = {}; - - if (!(wallet == null || secretKeyIndex == null)) { - Logging.instance - .log("SECRET_KEY_INDEX_IS $secretKeyIndex", level: LogLevel.Info); - result['result'] = - await getPendingSlates(wallet, secretKeyIndex, slates); - sendPort.send(result); - return; - } - } else if (function == "subscribeRequest") { - final wallet = arguments['wallet'] as String?; - final secretKeyIndex = arguments['secretKeyIndex'] as int?; - final epicboxConfig = arguments['epicboxConfig'] as String?; - Map result = {}; - - if (!(wallet == null || - secretKeyIndex == null || - epicboxConfig == null)) { - Logging.instance - .log("SECRET_KEY_INDEX_IS $secretKeyIndex", level: LogLevel.Info); - result['result'] = - await getSubscribeRequest(wallet, secretKeyIndex, epicboxConfig); - sendPort.send(result); - return; - } - } else if (function == "processSlates") { - final wallet = arguments['wallet'] as String?; - final slates = arguments['slates']; - Map result = {}; - - if (!(wallet == null || slates == null)) { - result['result'] = await processSlates(wallet, slates.toString()); - sendPort.send(result); - return; - } } else if (function == "getWalletInfo") { final wallet = arguments['wallet'] as String?; final refreshFromNode = arguments['refreshFromNode'] as int?; @@ -214,6 +184,7 @@ Future executeNative(Map arguments) async { return; } } + Logging.instance.log( "Error Arguments for $function not formatted correctly", level: LogLevel.Fatal); @@ -245,43 +216,34 @@ Future _cancelTransactionWrapper(Tuple2 data) async { return cancelTransaction(data.item1, data.item2); } -Future _deleteWalletWrapper(String wallet) async { - return deleteWallet(wallet); +Future _deleteWalletWrapper(Tuple2 data) async { + return deleteWallet(data.item1, data.item2); } Future deleteEpicWallet({ required String walletId, required SecureStorageInterface secureStore, }) async { - // is this even needed for anything? - // String? config = await secureStore.read(key: '${walletId}_config'); - // // TODO: why double check for iOS? - // if (Platform.isIOS) { - // Directory appDir = await StackFileSystem.applicationRootDirectory(); - // // todo why double check for ios? - // // if (Platform.isIOS) { - // // appDir = (await getLibraryDirectory()); - // // } - // // if (Platform.isLinux) { - // // appDir = Directory("${appDir.path}/.stackwallet"); - // // } - // final path = "${appDir.path}/epiccash"; - // final String name = walletId; - // - // final walletDir = '$path/$name'; - // var editConfig = jsonDecode(config as String); - // - // editConfig["wallet_dir"] = walletDir; - // config = jsonEncode(editConfig); - // } - final wallet = await secureStore.read(key: '${walletId}_wallet'); + String? config = await secureStore.read(key: '${walletId}_config'); + if (Platform.isIOS) { + Directory appDir = await StackFileSystem.applicationRootDirectory(); + + final path = "${appDir.path}/epiccash"; + final String name = walletId.trim(); + final walletDir = '$path/$name'; + + var editConfig = jsonDecode(config as String); + + editConfig["wallet_dir"] = walletDir; + config = jsonEncode(editConfig); + } if (wallet == null) { return "Tried to delete non existent epic wallet file with walletId=$walletId"; } else { try { - return compute(_deleteWalletWrapper, wallet); + return _deleteWalletWrapper(Tuple2(wallet, config!)); } catch (e, s) { Logging.instance.log("$e\n$s", level: LogLevel.Error); return "deleteEpicWallet($walletId) failed..."; @@ -317,226 +279,22 @@ Future _getChainHeightWrapper(String config) async { return chainHeight; } -const String EPICPOST_ADDRESS = 'https://epicpost.stackwallet.com'; - -Future postSlate(String receiveAddress, String slate) async { - Logging.instance.log("postSlate", level: LogLevel.Info); - final Client client = Client(); - try { - final uri = Uri.parse("$EPICPOST_ADDRESS/postSlate"); - - final epicpost = await client.post( - uri, - headers: {'Content-Type': 'application/json'}, - body: jsonEncode({ - "jsonrpc": "2.0", - "id": "0", - 'receivingAddress': receiveAddress, - 'slate': slate - }), - ); - - // TODO: should the following be removed for security reasons in production? - Logging.instance.log(epicpost.statusCode.toString(), level: LogLevel.Info); - Logging.instance.log(epicpost.body.toString(), level: LogLevel.Info); - final response = jsonDecode(epicpost.body.toString()); - if (response['status'] == 'success') { - return true; - } else { - return false; - } - } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Error); - return false; - } -} - -Future getSlates(String receiveAddress, String signature) async { - Logging.instance.log("getslates", level: LogLevel.Info); - final Client client = Client(); - try { - final uri = Uri.parse("$EPICPOST_ADDRESS/getSlates"); - - final epicpost = await client.post( - uri, - headers: {'Content-Type': 'application/json'}, - body: jsonEncode({ - "jsonrpc": "2.0", - "id": "0", - 'receivingAddress': receiveAddress, - 'signature': signature, - }), - ); - - // TODO: should the following be removed for security reasons in production? - Logging.instance.log(epicpost.statusCode.toString(), level: LogLevel.Info); - Logging.instance.log(epicpost.body.toString(), level: LogLevel.Info); - final response = jsonDecode(epicpost.body.toString()); - if (response['status'] == 'success') { - return response['slates']; - } else { - return response['error']; - } - } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Error); - return 'Error $e $s'; - } -} - -Future postCancel( - String receiveAddress, String slate_id, signature, sendersAddress) async { - Logging.instance.log("postCancel", level: LogLevel.Info); - final Client client = Client(); - try { - final uri = Uri.parse("$EPICPOST_ADDRESS/postCancel"); - - final body = jsonEncode({ - "jsonrpc": "2.0", - "id": "0", - 'receivingAddress': receiveAddress, - "signature": signature, - 'slate': slate_id, - "sendersAddress": sendersAddress, - }); - final epicpost = await client.post( - uri, - headers: {'Content-Type': 'application/json'}, - body: body, - ); - // TODO: should the following be removed for security reasons in production? - Logging.instance.log(epicpost.statusCode.toString(), level: LogLevel.Info); - Logging.instance.log(epicpost.body.toString(), level: LogLevel.Info); - final response = jsonDecode(epicpost.body.toString()); - if (response['status'] == 'success') { - return true; - } else { - return false; - } - } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Error); - return false; - } -} - -Future getCancels(String receiveAddress, String signature) async { - Logging.instance.log("getCancels", level: LogLevel.Info); - final Client client = Client(); - try { - final uri = Uri.parse("$EPICPOST_ADDRESS/getCancels"); - - final epicpost = await client.post( - uri, - headers: {'Content-Type': 'application/json'}, - body: jsonEncode({ - "jsonrpc": "2.0", - "id": "0", - 'receivingAddress': receiveAddress, - 'signature': signature, - }), - ); - // TODO: should the following be removed for security reasons in production? - Logging.instance.log(epicpost.statusCode.toString(), level: LogLevel.Info); - Logging.instance.log(epicpost.body.toString(), level: LogLevel.Info); - final response = jsonDecode(epicpost.body.toString()); - if (response['status'] == 'success') { - return response['canceled_slates']; - } else { - return response['error']; - } - } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Error); - return 'Error $e $s'; - } -} - -Future deleteCancels( - String receiveAddress, String signature, String slate) async { - Logging.instance.log("deleteCancels", level: LogLevel.Info); - final Client client = Client(); - try { - final uri = Uri.parse("$EPICPOST_ADDRESS/deleteCancels"); - - final epicpost = await client.post( - uri, - headers: {'Content-Type': 'application/json'}, - body: jsonEncode({ - "jsonrpc": "2.0", - "id": "0", - 'receivingAddress': receiveAddress, - 'signature': signature, - 'slate': slate, - }), - ); - // TODO: should the following be removed for security reasons in production? - Logging.instance.log(epicpost.statusCode.toString(), level: LogLevel.Info); - Logging.instance.log(epicpost.body.toString(), level: LogLevel.Info); - final response = jsonDecode(epicpost.body.toString()); - if (response['status'] == 'success') { - return true; - } else { - return false; - } - } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Error); - return 'Error $e $s'; - } -} - -Future deleteSlate( - String receiveAddress, String signature, String slate) async { - Logging.instance.log("deleteSlate", level: LogLevel.Info); - final Client client = Client(); - try { - final uri = Uri.parse("$EPICPOST_ADDRESS/deleteSlate"); - - final epicpost = await client.post( - uri, - headers: {'Content-Type': 'application/json'}, - body: jsonEncode({ - "jsonrpc": "2.0", - "id": "0", - 'receivingAddress': receiveAddress, - 'signature': signature, - 'slate': slate, - }), - ); - // TODO: should the following be removed for security reasons in production? - Logging.instance.log(epicpost.statusCode.toString(), level: LogLevel.Info); - Logging.instance.log(epicpost.body.toString(), level: LogLevel.Info); - final response = jsonDecode(epicpost.body.toString()); - if (response['status'] == 'success') { - return true; - } else { - return false; - } - } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Info); - return 'Error $e $s'; - } -} - -class EpicCashWallet extends CoinServiceAPI { - static const integrationTestFlag = - bool.fromEnvironment("IS_INTEGRATION_TEST"); - final m = Mutex(); - final syncMutex = Mutex(); - - final _prefs = Prefs.instance; - - NodeModel? _epicNode; - - EpicCashWallet( - {required String walletId, - required String walletName, - required Coin coin, - PriceAPI? priceAPI, - required SecureStorageInterface secureStore}) { +class EpicCashWallet extends CoinServiceAPI + with WalletCache, WalletDB, EpicCashHive { + EpicCashWallet({ + required String walletId, + required String walletName, + required Coin coin, + required SecureStorageInterface secureStore, + MainDB? mockableOverride, + }) { _walletId = walletId; _walletName = walletName; _coin = coin; - - _priceAPI = priceAPI ?? PriceAPI(Client()); _secureStore = secureStore; + initCache(walletId, coin); + initEpicCashHive(walletId); + initWalletDB(mockableOverride: mockableOverride); Logging.instance.log("$walletName isolate length: ${isolates.length}", level: LogLevel.Info); @@ -546,6 +304,15 @@ class EpicCashWallet extends CoinServiceAPI { isolates.clear(); } + static const integrationTestFlag = + bool.fromEnvironment("IS_INTEGRATION_TEST"); + final m = Mutex(); + final syncMutex = Mutex(); + + final _prefs = Prefs.instance; + + NodeModel? _epicNode; + @override Future updateNode(bool shouldRefresh) async { _epicNode = NodeService(secureStorageInterface: _secureStore) @@ -563,34 +330,14 @@ class EpicCashWallet extends CoinServiceAPI { @override set isFavorite(bool markFavorite) { - DB.instance.put( - boxName: walletId, key: "isFavorite", value: markFavorite); + _isFavorite = markFavorite; + updateCachedIsFavorite(markFavorite); } @override - bool get isFavorite { - try { - return DB.instance.get(boxName: walletId, key: "isFavorite") - as bool; - } catch (e, s) { - Logging.instance.log( - "isFavorite fetch failed (returning false by default): $e\n$s", - level: LogLevel.Error); - return false; - } - } + bool get isFavorite => _isFavorite ??= getCachedIsFavorite(); - @override - Future> get allOwnAddresses => - _allOwnAddresses ??= _fetchAllOwnAddresses(); - Future>? _allOwnAddresses; - - Future> _fetchAllOwnAddresses() async { - List addresses = []; - final ownAddress = await _getCurrentAddressForChain(0); - addresses.add(ownAddress); - return addresses; - } + bool? _isFavorite; late ReceivePort receivePort; @@ -655,79 +402,19 @@ class EpicCashWallet extends CoinServiceAPI { return walletBalances; } - @override - Future get availableBalance async { - String walletBalances = await allWalletBalances(); - var jsonBalances = json.decode(walletBalances); - final double spendable = - jsonBalances['amount_currently_spendable'] as double; - return Decimal.parse(spendable.toString()); - } - - @override - // TODO: implement balanceMinusMaxFee - Future get balanceMinusMaxFee => throw UnimplementedError(); - Timer? timer; - late Coin _coin; + late final Coin _coin; @override Coin get coin => _coin; late SecureStorageInterface _secureStore; - late PriceAPI _priceAPI; - - Future cancelPendingTransactionAndPost(String tx_slate_id) async { - final wallet = await _secureStore.read(key: '${_walletId}_wallet'); - final int? receivingIndex = DB.instance - .get(boxName: walletId, key: "receivingIndex") as int?; - final epicboxConfig = - await _secureStore.read(key: '${_walletId}_epicboxConfig'); - - final slatesToCommits = await getSlatesToCommits(); - final receiveAddress = slatesToCommits[tx_slate_id]['to'] as String; - final sendersAddress = slatesToCommits[tx_slate_id]['from'] as String; - - int? currentReceivingIndex; - for (int i = 0; i <= receivingIndex!; i++) { - final indexesAddress = await _getCurrentAddressForChain(i); - if (indexesAddress == sendersAddress) { - currentReceivingIndex = i; - break; - } - } - - dynamic subscribeRequest; - await m.protect(() async { - ReceivePort receivePort = await getIsolate({ - "function": "subscribeRequest", - "wallet": wallet, - "secretKeyIndex": currentReceivingIndex!, - "epicboxConfig": epicboxConfig, - }, name: walletName); - - var result = await receivePort.first; - if (result is String) { - Logging.instance.log("this is a message $result", level: LogLevel.Info); - stop(receivePort); - throw Exception("subscribeRequest isolate failed"); - } - subscribeRequest = jsonDecode(result['result'] as String); - stop(receivePort); - Logging.instance.log('Closing subscribeRequest! $subscribeRequest', - level: LogLevel.Info); - }); - // TODO, once server adds signature, give this signature to the getSlates method. - String? signature = subscribeRequest['signature'] as String?; + Future cancelPendingTransactionAndPost(String txSlateId) async { String? result; try { - result = await cancelPendingTransaction(tx_slate_id); + result = await cancelPendingTransaction(txSlateId); Logging.instance.log("result?: $result", level: LogLevel.Info); - if (!(result.toLowerCase().contains("error"))) { - await postCancel( - receiveAddress, tx_slate_id, signature, sendersAddress); - } } catch (e, s) { Logging.instance.log("$e, $s", level: LogLevel.Error); } @@ -736,7 +423,7 @@ class EpicCashWallet extends CoinServiceAPI { // /// returns an empty String on success, error message on failure - Future cancelPendingTransaction(String tx_slate_id) async { + Future cancelPendingTransaction(String txSlateId) async { final String wallet = (await _secureStore.read(key: '${_walletId}_wallet'))!; @@ -746,7 +433,7 @@ class EpicCashWallet extends CoinServiceAPI { _cancelTransactionWrapper, Tuple2( wallet, - tx_slate_id, + txSlateId, ), ); }); @@ -757,13 +444,23 @@ class EpicCashWallet extends CoinServiceAPI { Future confirmSend({required Map txData}) async { try { final wallet = await _secureStore.read(key: '${_walletId}_wallet'); - final epicboxConfig = - await _secureStore.read(key: '${_walletId}_epicboxConfig'); + + EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); // TODO determine whether it is worth sending change to a change address. dynamic message; String receiverAddress = txData['addresss'] as String; + + if (!receiverAddress.startsWith("http://") || + !receiverAddress.startsWith("https://")) { + bool isEpicboxConnected = await testEpicboxServer( + epicboxConfig.host, epicboxConfig.port ?? 443); + if (!isEpicboxConnected) { + throw Exception("Failed to send TX : Unable to reach epicbox server"); + } + } + await m.protect(() async { if (receiverAddress.startsWith("http://") || receiverAddress.startsWith("https://")) { @@ -774,8 +471,8 @@ class EpicCashWallet extends CoinServiceAPI { "selectionStrategyIsAll": selectionStrategyIsAll, "minimumConfirmations": MINIMUM_CONFIRMATIONS, "message": "", - "amount": txData['recipientAmt'], - "address": txData['addresss'] + "amount": (txData['recipientAmt'] as Amount).raw.toInt(), + "address": txData['addresss'] as String, }, name: walletName); message = await receivePort.first; @@ -792,10 +489,10 @@ class EpicCashWallet extends CoinServiceAPI { ReceivePort receivePort = await getIsolate({ "function": "createTransaction", "wallet": wallet!, - "amount": txData['recipientAmt'], - "address": txData['addresss'], + "amount": (txData['recipientAmt'] as Amount).raw.toInt(), + "address": txData['addresss'] as String, "secretKeyIndex": 0, - "epicboxConfig": epicboxConfig!, + "epicboxConfig": epicboxConfig.toString(), "minimumConfirmations": MINIMUM_CONFIRMATIONS, }, name: walletName); @@ -818,7 +515,10 @@ class EpicCashWallet extends CoinServiceAPI { throw BadEpicHttpAddressException(message: sendTx); } - await putSendToAddresses(sendTx); + Map txAddressInfo = {}; + txAddressInfo['from'] = await currentReceivingAddress; + txAddressInfo['to'] = txData['addresss'] as String; + await putSendToAddresses(sendTx, txAddressInfo); Logging.instance.log("CONFIRM_RESULT_IS $sendTx", level: LogLevel.Info); @@ -828,36 +528,15 @@ class EpicCashWallet extends CoinServiceAPI { String errorMessage = decodeData[1] as String; throw Exception("Transaction failed with error code $errorMessage"); } else { - //If it's HTTP send no need to post to epicbox - if (!(receiverAddress.startsWith("http://") || - receiverAddress.startsWith("https://"))) { - final postSlateRequest = decodeData[1]; - final postToServer = await postSlate( - txData['addresss'] as String, postSlateRequest as String); - Logging.instance - .log("POST_SLATE_IS $postToServer", level: LogLevel.Info); - } - final txCreateResult = decodeData[0]; // //TODO: second problem final transaction = json.decode(txCreateResult as String); - Logger.print("TX_IS $transaction"); final tx = transaction[0]; final txLogEntry = json.decode(tx as String); final txLogEntryFirst = txLogEntry[0]; - Logger.print("TX_LOG_ENTRY_IS $txLogEntryFirst"); - final wallet = await Hive.openBox(_walletId); - final slateToAddresses = - (await wallet.get("slate_to_address")) as Map? ?? {}; final slateId = txLogEntryFirst['tx_slate_id'] as String; - slateToAddresses[slateId] = txData['addresss']; - await wallet.put('slate_to_address', slateToAddresses); - final slatesToCommits = await getSlatesToCommits(); - String? commitId = slatesToCommits[slateId]?['commitId'] as String?; - Logging.instance.log("sent commitId: $commitId", level: LogLevel.Info); - return commitId!; - // return txLogEntryFirst['tx_slate_id'] as String; + return slateId!; } } catch (e, s) { Logging.instance.log("Error sending $e - $s", level: LogLevel.Error); @@ -865,32 +544,62 @@ class EpicCashWallet extends CoinServiceAPI { } } - /// Returns the latest receiving/change (external/internal) address for the wallet depending on [chain] - /// and - /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! - Future _getCurrentAddressForChain( - int chain, + Future _getReceivingAddressForIndex( + int index, ) async { - final wallet = await _secureStore.read(key: '${_walletId}_wallet'); - final epicboxConfig = - await _secureStore.read(key: '${_walletId}_epicboxConfig'); + isar_models.Address? address = await db + .getAddresses(walletId) + .filter() + .subTypeEqualTo(isar_models.AddressSubType.receiving) + .and() + .typeEqualTo(isar_models.AddressType.mimbleWimble) + .and() + .derivationIndexEqualTo(index) + .findFirst(); - String? walletAddress; - await m.protect(() async { - walletAddress = await compute( - _initGetAddressInfoWrapper, - Tuple3(wallet!, chain, epicboxConfig!), + if (address == null) { + final wallet = await _secureStore.read(key: '${_walletId}_wallet'); + EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); + + String? walletAddress; + await m.protect(() async { + walletAddress = await compute( + _initGetAddressInfoWrapper, + Tuple3(wallet!, index, epicboxConfig.toString()), + ); + }); + Logging.instance + .log("WALLET_ADDRESS_IS $walletAddress", level: LogLevel.Info); + + address = isar_models.Address( + walletId: walletId, + value: walletAddress!, + derivationIndex: index, + derivationPath: null, + type: isar_models.AddressType.mimbleWimble, + subType: isar_models.AddressSubType.receiving, + publicKey: [], // ?? ); - }); - Logging.instance - .log("WALLET_ADDRESS_IS $walletAddress", level: LogLevel.Info); - return walletAddress!; + + await db.updateOrPutAddresses([address]); + } + + return address; } @override - Future get currentReceivingAddress => - _currentReceivingAddress ??= _getCurrentAddressForChain(0); - Future? _currentReceivingAddress; + Future get currentReceivingAddress async => + (await _currentReceivingAddress)?.value ?? + (await _getReceivingAddressForIndex(0)).value; + + Future get _currentReceivingAddress => db + .getAddresses(walletId) + .filter() + .subTypeEqualTo(isar_models.AddressSubType.receiving) + .and() + .typeEqualTo(isar_models.AddressType.mimbleWimble) + .sortByDerivationIndexDesc() + .findFirst(); @override Future exit() async { @@ -940,10 +649,10 @@ class EpicCashWallet extends CoinServiceAPI { ), ); - await DB.instance.put( - boxName: walletId, - key: "lastScannedBlock", - value: await getRestoreHeight()); + // clear blockchain info + await db.deleteWalletBlockchainData(walletId); + + await epicUpdateLastScannedBlock(await getRestoreHeight()); if (!await startScans()) { refreshMutex = false; @@ -963,6 +672,7 @@ class EpicCashWallet extends CoinServiceAPI { ); return; } + await refresh(); GlobalEventBus.instance.fire( WalletSyncStatusChangedEvent( WalletSyncStatus.synced, @@ -981,7 +691,7 @@ class EpicCashWallet extends CoinServiceAPI { @override Future initializeExisting() async { - Logging.instance.log("Opening existing ${coin.prettyName} wallet", + Logging.instance.log("initializeExisting() ${coin.prettyName} wallet", level: LogLevel.Info); final config = await getRealConfig(); @@ -990,7 +700,7 @@ class EpicCashWallet extends CoinServiceAPI { final walletOpen = openWallet(config, password!); await _secureStore.write(key: '${_walletId}_wallet', value: walletOpen); - if ((DB.instance.get(boxName: walletId, key: "id")) == null) { + if (getCachedId() == null) { //todo: check if print needed // debugPrint("Exception was thrown"); throw Exception( @@ -998,12 +708,7 @@ class EpicCashWallet extends CoinServiceAPI { } await _prefs.init(); await updateNode(false); - final data = - DB.instance.get(boxName: walletId, key: "latest_tx_model") - as TransactionData?; - if (data != null) { - _transactionData = Future(() => data); - } + await _refreshBalance(); // TODO: is there anything else that should be set up here whenever this wallet is first loaded again? } @@ -1012,13 +717,13 @@ class EpicCashWallet extends CoinServiceAPI { int index = 0; Logging.instance.log("This index is $index", level: LogLevel.Info); - final epicboxConfig = - await _secureStore.read(key: '${_walletId}_epicboxConfig'); + EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); + String? walletAddress; await m.protect(() async { walletAddress = await compute( _initGetAddressInfoWrapper, - Tuple3(wallet!, index, epicboxConfig!), + Tuple3(wallet!, index, epicboxConfig.toString()), ); }); Logging.instance @@ -1056,14 +761,14 @@ class EpicCashWallet extends CoinServiceAPI { final String password = generatePassword(); String stringConfig = await getConfig(); - String epicboxConfig = await getEpicBoxConfig(); + EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); await _secureStore.write( key: '${_walletId}_mnemonic', value: mnemonicString); await _secureStore.write(key: '${_walletId}_config', value: stringConfig); await _secureStore.write(key: '${_walletId}_password', value: password); await _secureStore.write( - key: '${_walletId}_epicboxConfig', value: epicboxConfig); + key: '${_walletId}_epicboxConfig', value: epicboxConfig.toString()); String name = _walletId; @@ -1090,29 +795,17 @@ class EpicCashWallet extends CoinServiceAPI { final bufferedCreateHeight = calculateRestoreHeightFrom( date: DateTime.now().subtract(const Duration(days: 2))); - await DB.instance.put( - boxName: walletId, key: "restoreHeight", value: bufferedCreateHeight); + await Future.wait([ + epicUpdateRestoreHeight(bufferedCreateHeight), + updateCachedIsFavorite(false), + updateCachedId(walletId), + epicUpdateReceivingIndex(0), + epicUpdateChangeIndex(0), + ]); - await DB.instance - .put(boxName: walletId, key: "id", value: _walletId); - await DB.instance.put( - boxName: walletId, key: 'receivingAddresses', value: ["0"]); - await DB.instance - .put(boxName: walletId, key: "receivingIndex", value: 0); - await DB.instance - .put(boxName: walletId, key: "changeIndex", value: 0); - await DB.instance.put( - boxName: walletId, - key: 'blocked_tx_hashes', - value: ["0xdefault"], - ); // A list of transaction hashes to represent frozen utxos in wallet - // initialize address book entries - await DB.instance.put( - boxName: walletId, - key: 'addressBookEntries', - value: {}); - await DB.instance - .put(boxName: walletId, key: "isFavorite", value: false); + final initialReceivingAddress = await _getReceivingAddressForIndex(0); + + await db.putAddress(initialReceivingAddress); } bool refreshMutex = false; @@ -1121,26 +814,24 @@ class EpicCashWallet extends CoinServiceAPI { bool get isRefreshing => refreshMutex; @override - // TODO: implement maxFee + // unused for epic Future get maxFee => throw UnimplementedError(); Future> _getMnemonicList() async { - if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) { - final mnemonicString = - await _secureStore.read(key: '${_walletId}_mnemonic'); - final List data = mnemonicString!.split(' '); + String? _mnemonicString = await mnemonicString; + if (_mnemonicString != null) { + final List data = _mnemonicString.split(' '); return data; } else { - String? mnemonicString; await m.protect(() async { - mnemonicString = await compute( + _mnemonicString = await compute( _walletMnemonicWrapper, 0, ); }); await _secureStore.write( - key: '${_walletId}_mnemonic', value: mnemonicString); - final List data = mnemonicString!.split(' '); + key: '${_walletId}_mnemonic', value: _mnemonicString); + final List data = _mnemonicString!.split(' '); return data; } } @@ -1149,26 +840,35 @@ class EpicCashWallet extends CoinServiceAPI { Future> get mnemonic => _getMnemonicList(); @override - Future get pendingBalance async { - String walletBalances = await allWalletBalances(); - final jsonBalances = json.decode(walletBalances); - final double pending = - jsonBalances['amount_awaiting_confirmation'] as double; - return Decimal.parse(pending.toString()); - } + Future get mnemonicString => + _secureStore.read(key: '${_walletId}_mnemonic'); @override - Future> prepareSend( - {required String address, - required int satoshiAmount, - Map? args}) async { + Future get mnemonicPassphrase => _secureStore.read( + key: '${_walletId}_mnemonicPassphrase', + ); + + @override + Future> prepareSend({ + required String address, + required Amount amount, + Map? args, + }) async { try { - int realfee = await nativeFee(satoshiAmount); + int satAmount = amount.raw.toInt(); + int realfee = await nativeFee(satAmount); + + if (balance.spendable == amount) { + satAmount = balance.spendable.raw.toInt() - realfee; + } Map txData = { "fee": realfee, "addresss": address, - "recipientAmt": satoshiAmount, + "recipientAmt": Amount( + rawValue: BigInt.from(satAmount), + fractionDigits: coin.decimals, + ), }; Logging.instance.log("prepare send: $txData", level: LogLevel.Info); @@ -1208,33 +908,81 @@ class EpicCashWallet extends CoinServiceAPI { }); debugPrint(transactionFees); dynamic decodeData; - try { - decodeData = json.decode(transactionFees!); - } catch (e) { - if (ifErrorEstimateFee) { - //Error Not enough funds. Required: 0.56500000, Available: 0.56200000 - if (transactionFees!.contains("Required")) { - var splits = transactionFees!.split(" "); - Decimal required = Decimal.zero; - Decimal available = Decimal.zero; - for (int i = 0; i < splits.length; i++) { - var word = splits[i]; - if (word == "Required:") { - required = Decimal.parse(splits[i + 1].replaceAll(",", "")); - } else if (word == "Available:") { - available = Decimal.parse(splits[i + 1].replaceAll(",", "")); - } + + final available = balance.spendable.raw.toInt(); + + if (available == satoshiAmount) { + if (transactionFees!.contains("Required")) { + var splits = transactionFees!.split(" "); + Decimal required = Decimal.zero; + Decimal available = Decimal.zero; + for (int i = 0; i < splits.length; i++) { + var word = splits[i]; + if (word == "Required:") { + required = Decimal.parse(splits[i + 1].replaceAll(",", "")); + } else if (word == "Available:") { + available = Decimal.parse(splits[i + 1].replaceAll(",", "")); } - int largestSatoshiFee = - ((required - available) * Decimal.fromInt(100000000)) - .toBigInt() - .toInt(); - Logging.instance.log("largestSatoshiFee $largestSatoshiFee", - level: LogLevel.Info); - return largestSatoshiFee; } + int largestSatoshiFee = + ((required - available) * Decimal.fromInt(100000000)) + .toBigInt() + .toInt(); + var amountSending = satoshiAmount - largestSatoshiFee; + + //Get fees for this new amount + await m.protect(() async { + ReceivePort receivePort = await getIsolate({ + "function": "getTransactionFees", + "wallet": wallet!, + "amount": amountSending, + "minimumConfirmations": MINIMUM_CONFIRMATIONS, + }, name: walletName); + + var message = await receivePort.first; + if (message is String) { + Logging.instance + .log("this is a string $message", level: LogLevel.Error); + stop(receivePort); + throw Exception("getTransactionFees isolate failed"); + } + stop(receivePort); + Logging.instance.log('Closing getTransactionFees!\n $message', + level: LogLevel.Info); + // return message; + transactionFees = message['result'] as String; + }); + } + decodeData = json.decode(transactionFees!); + } else { + try { + decodeData = json.decode(transactionFees!); + } catch (e) { + if (ifErrorEstimateFee) { + //Error Not enough funds. Required: 0.56500000, Available: 0.56200000 + if (transactionFees!.contains("Required")) { + var splits = transactionFees!.split(" "); + Decimal required = Decimal.zero; + Decimal available = Decimal.zero; + for (int i = 0; i < splits.length; i++) { + var word = splits[i]; + if (word == "Required:") { + required = Decimal.parse(splits[i + 1].replaceAll(",", "")); + } else if (word == "Available:") { + available = Decimal.parse(splits[i + 1].replaceAll(",", "")); + } + } + int largestSatoshiFee = + ((required - available) * Decimal.fromInt(100000000)) + .toBigInt() + .toInt(); + Logging.instance.log("largestSatoshiFee $largestSatoshiFee", + level: LogLevel.Info); + return largestSatoshiFee; + } + } + rethrow; } - rethrow; } //TODO: first problem @@ -1289,9 +1037,93 @@ class EpicCashWallet extends CoinServiceAPI { return stringConfig; } - Future getEpicBoxConfig() async { - return await _secureStore.read(key: '${_walletId}_epicboxConfig') ?? - DefaultNodes.defaultEpicBoxConfig; + Future testEpicboxServer(String host, int port) async { + // TODO use an EpicBoxServerModel as the only param + final websocketConnectionUri = 'wss://$host:$port'; + const connectionOptions = SocketConnectionOptions( + pingIntervalMs: 3000, + timeoutConnectionMs: 4000, + + /// see ping/pong messages in [logEventStream] stream + skipPingMessages: true, + + /// Set this attribute to `true` if do not need any ping/pong + /// messages and ping measurement. Default is `false` + pingRestrictionForce: true, + ); + + final IMessageProcessor textSocketProcessor = + SocketSimpleTextProcessor(); + final textSocketHandler = IWebSocketHandler.createClient( + websocketConnectionUri, + textSocketProcessor, + connectionOptions: connectionOptions, + ); + + // Listening to server responses: + bool isConnected = true; + textSocketHandler.incomingMessagesStream.listen((inMsg) { + Logging.instance.log( + '> webSocket got text message from server: "$inMsg" ' + '[ping: ${textSocketHandler.pingDelayMs}]', + level: LogLevel.Info); + }); + + // Connecting to server: + final isTextSocketConnected = await textSocketHandler.connect(); + if (!isTextSocketConnected) { + // ignore: avoid_print + Logging.instance.log( + 'Connection to [$websocketConnectionUri] failed for some reason!', + level: LogLevel.Error); + isConnected = false; + } + return isConnected; + } + + Future getEpicBoxConfig() async { + EpicBoxConfigModel? _epicBoxConfig; + // read epicbox config from secure store + String? storedConfig = + await _secureStore.read(key: '${_walletId}_epicboxConfig'); + + // we should move to storing the primary server model like we do with nodes, and build the config from that (see epic-mobile) + // EpicBoxServerModel? _epicBox = epicBox ?? + // DB.instance.get( + // boxName: DB.boxNamePrimaryEpicBox, key: 'primary'); + // Logging.instance.log( + // "Read primary Epic Box config: ${jsonEncode(_epicBox)}", + // level: LogLevel.Info); + + if (storedConfig == null) { + // if no config stored, use the default epicbox server as config + _epicBoxConfig = + EpicBoxConfigModel.fromServer(DefaultEpicBoxes.defaultEpicBoxServer); + } else { + // if a config is stored, test it + + _epicBoxConfig = EpicBoxConfigModel.fromString( + storedConfig); // fromString handles checking old config formats + } + + bool isEpicboxConnected = await testEpicboxServer( + _epicBoxConfig.host, _epicBoxConfig.port ?? 443); + + if (!isEpicboxConnected) { + // default Epicbox is not connected, default to Europe + _epicBoxConfig = EpicBoxConfigModel.fromServer(DefaultEpicBoxes.europe); + + // example of selecting another random server from the default list + // alternative servers: copy list of all default EB servers but remove the default default + // List alternativeServers = DefaultEpicBoxes.all; + // alternativeServers.removeWhere((opt) => opt.name == DefaultEpicBoxes.defaultEpicBoxServer.name); + // alternativeServers.shuffle(); // randomize which server is used + // _epicBoxConfig = EpicBoxConfigModel.fromServer(alternativeServers.first); + + // TODO test this connection before returning it + } + + return _epicBoxConfig; } Future getRealConfig() async { @@ -1308,8 +1140,10 @@ class EpicCashWallet extends CoinServiceAPI { Future updateEpicboxConfig(String host, int port) async { String stringConfig = jsonEncode({ - "domain": host, - "port": port, + "epicbox_domain": host, + "epicbox_port": port, + "epicbox_protocol_unsecure": false, + "epicbox_address_index": 0, }); await _secureStore.write( key: '${_walletId}_epicboxConfig', value: stringConfig); @@ -1318,29 +1152,27 @@ class EpicCashWallet extends CoinServiceAPI { Future startScans() async { try { + if (ListenerManager.pointer != null) { + Logging.instance + .log("LISTENER HANDLER IS NOT NULL ....", level: LogLevel.Info); + Logging.instance + .log("STOPPING ANY WALLET LISTENER ....", level: LogLevel.Info); + epicboxListenerStop(ListenerManager.pointer!); + } + final wallet = await _secureStore.read(key: '${_walletId}_wallet'); - var restoreHeight = - DB.instance.get(boxName: walletId, key: "restoreHeight"); + var restoreHeight = epicGetRestoreHeight(); var chainHeight = await this.chainHeight; - if (!DB.instance.containsKey( - boxName: walletId, key: 'lastScannedBlock') || - DB.instance - .get(boxName: walletId, key: 'lastScannedBlock') == - null) { - await DB.instance.put( - boxName: walletId, - key: "lastScannedBlock", - value: await getRestoreHeight()); + if (epicGetLastScannedBlock() == null) { + await epicUpdateLastScannedBlock(await getRestoreHeight()); } - int lastScannedBlock = DB.instance - .get(boxName: walletId, key: 'lastScannedBlock') as int; + int lastScannedBlock = epicGetLastScannedBlock()!; const MAX_PER_LOOP = 10000; await getSyncPercent; for (; lastScannedBlock < chainHeight;) { chainHeight = await this.chainHeight; - lastScannedBlock = DB.instance - .get(boxName: walletId, key: 'lastScannedBlock') as int; + lastScannedBlock = epicGetLastScannedBlock()!; Logging.instance.log( "chainHeight: $chainHeight, restoreHeight: $restoreHeight, lastScannedBlock: $lastScannedBlock", level: LogLevel.Info); @@ -1365,13 +1197,11 @@ class EpicCashWallet extends CoinServiceAPI { Logging.instance .log('Closing scanOutPuts!\n $message', level: LogLevel.Info); }); - await DB.instance.put( - boxName: walletId, - key: "lastScannedBlock", - value: nextScannedBlock!); + await epicUpdateLastScannedBlock(nextScannedBlock!); await getSyncPercent; } Logging.instance.log("successfully at the tip", level: LogLevel.Info); + await listenToEpicbox(); return true; } catch (e, s) { Logging.instance.log("$e, $s", level: LogLevel.Warning); @@ -1380,9 +1210,7 @@ class EpicCashWallet extends CoinServiceAPI { } Future get getSyncPercent async { - int lastScannedBlock = DB.instance - .get(boxName: walletId, key: 'lastScannedBlock') as int? ?? - 0; + int lastScannedBlock = epicGetLastScannedBlock() ?? 0; final _chainHeight = await chainHeight; double restorePercent = lastScannedBlock / _chainHeight; GlobalEventBus.instance @@ -1401,25 +1229,28 @@ class EpicCashWallet extends CoinServiceAPI { double highestPercent = 0; @override - Future recoverFromMnemonic( - {required String mnemonic, - required int maxUnusedAddressGap, - required int maxNumberOfIndexesToCheck, - required int height}) async { + Future recoverFromMnemonic({ + required String mnemonic, + String? mnemonicPassphrase, // unused in epic + required int maxUnusedAddressGap, + required int maxNumberOfIndexesToCheck, + required int height, + }) async { try { await _prefs.init(); await updateNode(false); final String password = generatePassword(); String stringConfig = await getConfig(); - String epicboxConfig = await getEpicBoxConfig(); + EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); final String name = _walletName.trim(); await _secureStore.write(key: '${_walletId}_mnemonic', value: mnemonic); await _secureStore.write(key: '${_walletId}_config', value: stringConfig); await _secureStore.write(key: '${_walletId}_password', value: password); + await _secureStore.write( - key: '${_walletId}_epicboxConfig', value: epicboxConfig); + key: '${_walletId}_epicboxConfig', value: epicboxConfig.toString()); await compute( _recoverWrapper, @@ -1431,33 +1262,13 @@ class EpicCashWallet extends CoinServiceAPI { ), ); - await DB.instance - .put(boxName: walletId, key: "restoreHeight", value: height); - - await DB.instance - .put(boxName: walletId, key: "id", value: _walletId); - await DB.instance.put( - boxName: walletId, key: 'receivingAddresses', value: ["0"]); - await DB.instance - .put(boxName: walletId, key: "receivingIndex", value: 0); - if (height >= 0) { - await DB.instance.put( - boxName: walletId, key: "restoreHeight", value: height); - } - await DB.instance - .put(boxName: walletId, key: "changeIndex", value: 0); - await DB.instance.put( - boxName: walletId, - key: 'blocked_tx_hashes', - value: ["0xdefault"], - ); // A list of transaction hashes to represent frozen utxos in wallet - // initialize address book entries - await DB.instance.put( - boxName: walletId, - key: 'addressBookEntries', - value: {}); - await DB.instance - .put(boxName: walletId, key: "isFavorite", value: false); + await Future.wait([ + epicUpdateRestoreHeight(height), + updateCachedId(walletId), + epicUpdateReceivingIndex(0), + epicUpdateChangeIndex(0), + updateCachedIsFavorite(false), + ]); //Open Wallet final walletOpen = openWallet(stringConfig, password); @@ -1472,38 +1283,49 @@ class EpicCashWallet extends CoinServiceAPI { } } + Future listenToEpicbox() async { + Logging.instance.log("STARTING WALLET LISTENER ....", level: LogLevel.Info); + final wallet = await _secureStore.read(key: '${_walletId}_wallet'); + EpicBoxConfigModel epicboxConfig = await getEpicBoxConfig(); + + ListenerManager.pointer = + epicboxListenerStart(wallet!, epicboxConfig.toString()); + } + Future getRestoreHeight() async { - if (DB.instance - .containsKey(boxName: walletId, key: "restoreHeight")) { - return (DB.instance.get(boxName: walletId, key: "restoreHeight")) - as int; - } - return (DB.instance.get(boxName: walletId, key: "creationHeight")) - as int; + return epicGetRestoreHeight() ?? epicGetCreationHeight()!; } Future get chainHeight async { - final config = await getRealConfig(); - int? latestHeight; - await m.protect(() async { - latestHeight = await compute( - _getChainHeightWrapper, - config, - ); - }); - return latestHeight!; + try { + final config = await getRealConfig(); + int? latestHeight; + await m.protect(() async { + latestHeight = await compute( + _getChainHeightWrapper, + config, + ); + }); + + await updateCachedChainHeight(latestHeight!); + if (latestHeight! > storedChainHeight) { + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "Updated current chain height in $walletId $walletName!", + walletId, + ), + ); + } + return latestHeight!; + } catch (e, s) { + Logging.instance.log("Exception caught in chainHeight: $e\n$s", + level: LogLevel.Error); + return storedChainHeight; + } } - int get storedChainHeight { - return DB.instance.get(boxName: walletId, key: "storedChainHeight") - as int? ?? - 0; - } - - Future updateStoredChainHeight({required int newHeight}) async { - await DB.instance.put( - boxName: walletId, key: "storedChainHeight", value: newHeight); - } + @override + int get storedChainHeight => getCachedChainHeight(); bool _shouldAutoSync = true; @@ -1528,8 +1350,7 @@ class EpicCashWallet extends CoinServiceAPI { Future setCurrentIndex() async { try { - final int receivingIndex = DB.instance - .get(boxName: walletId, key: "receivingIndex") as int; + final int receivingIndex = epicGetReceivingIndex()!; // TODO: go through pendingarray and processed array and choose the index // of the last one that has not been processed, or the index after the one most recently processed; return receivingIndex; @@ -1563,21 +1384,21 @@ class EpicCashWallet extends CoinServiceAPI { Future> getSlatesToCommits() async { try { - var slatesToCommits = - DB.instance.get(boxName: walletId, key: "slatesToCommits"); + var slatesToCommits = epicGetSlatesToCommits(); if (slatesToCommits == null) { slatesToCommits = {}; } else { - slatesToCommits = slatesToCommits as Map; + slatesToCommits = slatesToCommits; } - return slatesToCommits as Map; + return slatesToCommits; } catch (e, s) { Logging.instance.log("$e $s", level: LogLevel.Error); return {}; } } - Future putSendToAddresses(String slateMessage) async { + Future putSendToAddresses( + String slateMessage, Map txAddressInfo) async { try { var slatesToCommits = await getSlatesToCommits(); final slate0 = jsonDecode(slateMessage); @@ -1587,20 +1408,19 @@ class EpicCashWallet extends CoinServiceAPI { final slateId = part1[0]['tx_slate_id']; final commitId = part2['tx']['body']['outputs'][0]['commit']; - final toFromInfoString = jsonDecode(slateMessage); - final toFromInfo = jsonDecode(toFromInfoString[1] as String); - final from = toFromInfo['from']; - final to = toFromInfo['to']; + final from = txAddressInfo['from']; + final to = txAddressInfo['to']; slatesToCommits[slateId] = { "commitId": commitId, "from": from, "to": to, }; - await DB.instance.put( - boxName: walletId, key: "slatesToCommits", value: slatesToCommits); + + await epicUpdateSlatesToCommits(slatesToCommits); return true; } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Error); + Logging.instance + .log("ERROR STORING ADDRESS $e $s", level: LogLevel.Error); return false; } } @@ -1628,8 +1448,7 @@ class EpicCashWallet extends CoinServiceAPI { "from": from, "to": to, }; - await DB.instance.put( - boxName: walletId, key: "slatesToCommits", value: slatesToCommits); + await epicUpdateSlatesToCommits(slatesToCommits); return true; } catch (e, s) { Logging.instance.log("$e $s", level: LogLevel.Error); @@ -1637,249 +1456,6 @@ class EpicCashWallet extends CoinServiceAPI { } } - Future processAllSlates() async { - final int? receivingIndex = DB.instance - .get(boxName: walletId, key: "receivingIndex") as int?; - for (int currentReceivingIndex = 0; - receivingIndex != null && currentReceivingIndex <= receivingIndex; - currentReceivingIndex++) { - final currentAddress = - await _getCurrentAddressForChain(currentReceivingIndex); - final wallet = await _secureStore.read(key: '${_walletId}_wallet'); - final epicboxConfig = - await _secureStore.read(key: '${_walletId}_epicboxConfig'); - dynamic subscribeRequest; - await m.protect(() async { - ReceivePort receivePort = await getIsolate({ - "function": "subscribeRequest", - "wallet": wallet, - "secretKeyIndex": currentReceivingIndex, - "epicboxConfig": epicboxConfig, - }, name: walletName); - - var result = await receivePort.first; - if (result is String) { - Logging.instance - .log("this is a message $result", level: LogLevel.Error); - stop(receivePort); - throw Exception("subscribeRequest isolate failed"); - } - subscribeRequest = jsonDecode(result['result'] as String); - stop(receivePort); - Logging.instance.log('Closing subscribeRequest! $subscribeRequest', - level: LogLevel.Info); - }); - // TODO, once server adds signature, give this signature to the getSlates method. - Logging.instance - .log(subscribeRequest['signature'], level: LogLevel.Info); // - final unprocessedSlates = await getSlates( - currentAddress, subscribeRequest['signature'] as String); - if (unprocessedSlates == null || unprocessedSlates is! List) { - Logging.instance.log( - "index $currentReceivingIndex at $currentReceivingAddress does not have any slates", - level: LogLevel.Info); - continue; - } - for (var slate in unprocessedSlates) { - final encoded = jsonEncode([slate]); - Logging.instance - .log("Received Slates is $encoded", level: LogLevel.Info); - - //Decrypt Slates - dynamic slates; - dynamic response; - await m.protect(() async { - ReceivePort receivePort = await getIsolate({ - "function": "getPendingSlates", - "wallet": wallet!, - "secretKeyIndex": currentReceivingIndex, - "slates": encoded, - }, name: walletName); - - var result = await receivePort.first; - if (result is String) { - Logging.instance - .log("this is a message $slates", level: LogLevel.Info); - stop(receivePort); - throw Exception("getPendingSlates isolate failed"); - } - slates = result['result']; - stop(receivePort); - }); - - var decoded = jsonDecode(slates as String); - - for (var decodedSlate in decoded as List) { - //Process slates - var decodedResponse = json.decode(decodedSlate as String); - String slateMessage = decodedResponse[0] as String; - await putSlatesToCommits(slateMessage, encoded); - String slateSender = decodedResponse[1] as String; - Logging.instance.log("SLATE_MESSAGE $slateMessage", - printFullLength: true, level: LogLevel.Info); - Logging.instance - .log("SLATE_SENDER $slateSender", level: LogLevel.Info); - await m.protect(() async { - ReceivePort receivePort = await getIsolate({ - "function": "processSlates", - "wallet": wallet!, - "slates": slateMessage - }, name: walletName); - - var message = await receivePort.first; - if (message is String) { - Logging.instance.log("this is PROCESS_SLATES message $message", - level: LogLevel.Error); - stop(receivePort); - throw Exception("processSlates isolate failed"); - } - - try { - final String response = message['result'] as String; - if (response == "") { - Logging.instance.log("response: ${response.runtimeType}", - level: LogLevel.Info); - await deleteSlate(currentAddress, - subscribeRequest['signature'] as String, slate as String); - } - - if (response - .contains("Error Wallet store error: DB Not Found Error")) { - //Already processed - to be deleted - Logging.instance - .log("DELETING_PROCESSED_SLATE", level: LogLevel.Info); - final slateDelete = await deleteSlate(currentAddress, - subscribeRequest['signature'] as String, slate as String); - Logging.instance.log("DELETE_SLATE_RESPONSE $slateDelete", - level: LogLevel.Info); - } else { - var decodedResponse = json.decode(response); - final processStatus = json.decode(decodedResponse[0] as String); - String slateStatus = processStatus['status'] as String; - if (slateStatus == "PendingProcessing") { - //Encrypt slate - String encryptedSlate = await getEncryptedSlate( - wallet, - slateSender, - currentReceivingIndex, - epicboxConfig!, - decodedResponse[1] as String); - - final postSlateToServer = - await postSlate(slateSender, encryptedSlate); - - await deleteSlate(currentAddress, - subscribeRequest['signature'] as String, slate as String); - Logging.instance.log("POST_SLATE_RESPONSE $postSlateToServer", - level: LogLevel.Info); - } else { - //Finalise Slate - final processSlate = - json.decode(decodedResponse[1] as String); - Logging.instance.log( - "PROCESSED_SLATE_TO_FINALIZE $processSlate", - level: LogLevel.Info); - final tx = json.decode(processSlate[0] as String); - Logging.instance.log("TX_IS $tx", level: LogLevel.Info); - String txSlateId = tx[0]['tx_slate_id'] as String; - Logging.instance - .log("TX_SLATE_ID_IS $txSlateId", level: LogLevel.Info); - final postToNode = await postSlateToNode(wallet, txSlateId); - await deleteSlate(currentAddress, - subscribeRequest['signature'] as String, slate as String); - Logging.instance.log("POST_SLATE_RESPONSE $postToNode", - level: LogLevel.Info); - //Post Slate to Node - Logging.instance.log("Finalise slate", level: LogLevel.Info); - } - } - } catch (e, s) { - Logging.instance.log("$e\n$s", level: LogLevel.Info); - return false; - } - stop(receivePort); - Logging.instance - .log('Closing processSlates! $response', level: LogLevel.Info); - }); - } - } - } - return true; - } - - Future processAllCancels() async { - Logging.instance.log("processAllCancels", level: LogLevel.Info); - final wallet = await _secureStore.read(key: '${_walletId}_wallet'); - final epicboxConfig = - await _secureStore.read(key: '${_walletId}_epicboxConfig'); - final int? receivingIndex = DB.instance - .get(boxName: walletId, key: "receivingIndex") as int?; - final tData = await _transactionData; - for (int currentReceivingIndex = 0; - receivingIndex != null && currentReceivingIndex <= receivingIndex; - currentReceivingIndex++) { - final receiveAddress = - await _getCurrentAddressForChain(currentReceivingIndex); - - dynamic subscribeRequest; - await m.protect(() async { - ReceivePort receivePort = await getIsolate({ - "function": "subscribeRequest", - "wallet": wallet!, - "secretKeyIndex": currentReceivingIndex, - "epicboxConfig": epicboxConfig, - }, name: walletName); - - var result = await receivePort.first; - if (result is String) { - Logging.instance - .log("this is a message $result", level: LogLevel.Info); - stop(receivePort); - throw Exception("subscribeRequest isolate failed"); - } - subscribeRequest = jsonDecode(result['result'] as String); - stop(receivePort); - Logging.instance.log('Closing subscribeRequest! $subscribeRequest', - level: LogLevel.Info); - }); - String? signature = subscribeRequest['signature'] as String?; - final cancels = await getCancels(receiveAddress, signature!); - - final slatesToCommits = await getSlatesToCommits(); - for (final cancel in cancels as List) { - final tx_slate_id = cancel.keys.first as String; - if (slatesToCommits[tx_slate_id] == null) { - continue; - } - final cancelRequestSender = ((cancel as Map).values.first) as String; - final receiveAddressFromMap = - slatesToCommits[tx_slate_id]['to'] as String; - final sendersAddressFromMap = - slatesToCommits[tx_slate_id]['from'] as String; - final commitId = slatesToCommits[tx_slate_id]['commitId'] as String; - - if (sendersAddressFromMap != cancelRequestSender) { - Logging.instance.log("this was not signed by the correct address", - level: LogLevel.Error); - continue; - } - - String? result; - try { - result = await cancelPendingTransaction(tx_slate_id); - if (tData?.findTransaction(commitId)?.isCancelled ?? false == true) { - await deleteCancels(receiveAddressFromMap, signature, tx_slate_id); - } - } catch (e, s) { - Logging.instance.log("$e, $s", level: LogLevel.Error); - return false; - } - } - continue; - } - return true; - } - /// Refreshes display data for the wallet @override Future refresh() async { @@ -1902,14 +1478,12 @@ class EpicCashWallet extends CoinServiceAPI { ), ); - if (!DB.instance - .containsKey(boxName: walletId, key: "creationHeight")) { - await DB.instance.put( - boxName: walletId, key: "creationHeight", value: await chainHeight); + if (epicGetCreationHeight() == null) { + await epicUpdateCreationHeight(await chainHeight); } final int curAdd = await setCurrentIndex(); - _currentReceivingAddress = _getCurrentAddressForChain(curAdd); + await _getReceivingAddressForIndex(curAdd); if (!await startScans()) { refreshMutex = false; @@ -1930,10 +1504,7 @@ class EpicCashWallet extends CoinServiceAPI { return; } - await processAllSlates(); - await processAllCancels(); - - startSync(); + unawaited(startSync()); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId)); @@ -1950,21 +1521,16 @@ class EpicCashWallet extends CoinServiceAPI { // TODO: implement refresh // TODO: check if it needs a refresh and if so get all of the most recent data. if (currentHeight != storedHeight) { - if (currentHeight != -1) { - // -1 failed to fetch current height - unawaited(updateStoredChainHeight(newHeight: currentHeight)); - } - - final newTxData = _fetchTransactionData(); + await _refreshTransactions(); GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.50, walletId)); - _transactionData = Future(() => newTxData); - GlobalEventBus.instance.fire(UpdatedInBackgroundEvent( "New data found in $walletName in background!", walletId)); } + await _refreshBalance(); + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(1.0, walletId)); GlobalEventBus.instance.fire( WalletSyncStatusChangedEvent( @@ -1974,7 +1540,6 @@ class EpicCashWallet extends CoinServiceAPI { ), ); refreshMutex = false; - if (shouldAutoSync) { timer ??= Timer.periodic(const Duration(seconds: 60), (timer) async { Logging.instance.log( @@ -2020,15 +1585,6 @@ class EpicCashWallet extends CoinServiceAPI { // TODO: do a quick check to see if there is any new data that would require a refresh } - @override - Future send( - {required String toAddress, - required int amount, - Map args = const {}}) { - // TODO: implement send - throw UnimplementedError(); - } - @override Future testNetworkConnection() async { try { @@ -2084,18 +1640,8 @@ class EpicCashWallet extends CoinServiceAPI { @override bool get isConnected => _isConnected; - @override - Future get totalBalance async { - String walletBalances = await allWalletBalances(); - var jsonBalances = json.decode(walletBalances); - double total = jsonBalances['total'] as double; - double awaiting = jsonBalances['amount_awaiting_finalization'] as double; - total = total + awaiting; - return Decimal.parse(total.toString()); - } - - Future _fetchTransactionData() async { - final currentChainHeight = await chainHeight; + Future _refreshTransactions() async { + // final currentChainHeight = await chainHeight; final wallet = await _secureStore.read(key: '${_walletId}_wallet'); const refreshFromNode = 0; @@ -2121,38 +1667,28 @@ class EpicCashWallet extends CoinServiceAPI { // return message; final String transactions = message['result'] as String; final jsonTransactions = json.decode(transactions) as List; - // for (var el in jsonTransactions) { - // Logging.instance.log("gettran: $el", - // normalLength: false, addToDebugMessagesDB: true); - // } - final priceData = - await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final List> midSortedArray = []; + final List> txnsData = + []; - int latestTxnBlockHeight = - DB.instance.get(boxName: walletId, key: "storedTxnDataHeight") - as int? ?? - 0; + // int latestTxnBlockHeight = + // DB.instance.get(boxName: walletId, key: "storedTxnDataHeight") + // as int? ?? + // 0; final slatesToCommits = await getSlatesToCommits(); - final cachedTransactions = - DB.instance.get(boxName: walletId, key: 'latest_tx_model') - as TransactionData?; - var cachedMap = cachedTransactions?.getAllTransactions(); + for (var tx in jsonTransactions) { Logging.instance.log("tx: $tx", level: LogLevel.Info); - final txHeight = tx["kernel_lookup_min_height"] as int? ?? 0; - // TODO: does "confirmed" mean finalized? If so please remove this todo + // // TODO: does "confirmed" mean finalized? If so please remove this todo final isConfirmed = tx["confirmed"] as bool; - // TODO: since we are now caching tx history in hive are we losing anything by skipping here? - // TODO: we can skip this filtering if it causes issues as the cache is later merged with updated data anyways - // this would just make processing and updating cache more efficient - if (txHeight > 0 && - txHeight < latestTxnBlockHeight - MINIMUM_CONFIRMATIONS && - isConfirmed) { - continue; - } + // // TODO: since we are now caching tx history in hive are we losing anything by skipping here? + // // TODO: we can skip this filtering if it causes issues as the cache is later merged with updated data anyways + // // this would just make processing and updating cache more efficient + // if (txHeight > 0 && + // txHeight < latestTxnBlockHeight - MINIMUM_CONFIRMATIONS && + // isConfirmed) { + // continue; + // } // Logging.instance.log("Transactions listed below"); // Logging.instance.log(jsonTransactions); int amt = 0; @@ -2165,141 +1701,187 @@ class EpicCashWallet extends CoinServiceAPI { int fee = int.parse((tx['fee'] ?? "0") as String); amt = debit - credit - fee; } - final String worthNow = - (currentPrice * Decimal.parse(amt.toString())).toStringAsFixed(2); DateTime dt = DateTime.parse(tx["creation_ts"] as String); - Map midSortedTx = {}; - midSortedTx["txType"] = (tx["tx_type"] == "TxReceived" || - tx["tx_type"] == "TxReceivedCancelled") - ? "Received" - : "Sent"; String? slateId = tx['tx_slate_id'] as String?; - String? address = slatesToCommits[slateId] - ?[midSortedTx["txType"] == "TxReceived" ? "from" : "to"] - as String? ?? + String address = slatesToCommits[slateId] + ?[tx["tx_type"] == "TxReceived" ? "from" : "to"] as String? ?? ""; String? commitId = slatesToCommits[slateId]?['commitId'] as String?; - Logging.instance.log( - "commitId: $commitId, slateId: $slateId, id: ${tx["id"]}", - level: LogLevel.Info); - bool isCancelled = tx["tx_type"] == "TxSentCancelled" || - tx["tx_type"] == "TxReceivedCancelled"; + int? height; - midSortedTx["slateId"] = slateId; - midSortedTx["isCancelled"] = isCancelled; - midSortedTx["txid"] = commitId ?? tx["id"].toString(); - midSortedTx["confirmed_status"] = isConfirmed; - midSortedTx["timestamp"] = (dt.millisecondsSinceEpoch ~/ 1000); - midSortedTx["amount"] = amt; - midSortedTx["worthNow"] = worthNow; - midSortedTx["worthAtBlockTimestamp"] = worthNow; - midSortedTx["fees"] = - (tx["fee"] == null) ? 0 : int.parse(tx["fee"] as String); - midSortedTx["address"] = - ""; // for this when you send a transaction you will just need to save in a hashmap in hive with the key being the txid, and the value being the address it was sent to. then you can look this value up right here in your hashmap. - midSortedTx["address"] = address; - midSortedTx["height"] = txHeight; - int confirmations = 0; - try { - confirmations = currentChainHeight - txHeight; - } catch (e, s) { - //todo: come back to this - debugPrint("$e $s"); - } - midSortedTx["confirmations"] = confirmations; - - midSortedTx["inputSize"] = tx["num_inputs"]; - midSortedTx["outputSize"] = tx["num_outputs"]; - midSortedTx["aliens"] = []; - midSortedTx["inputs"] = []; - midSortedTx["outputs"] = []; - midSortedTx["tx_slate_id"] = tx["tx_slate_id"]; - midSortedTx["key_id"] = tx["parent_key_id"]; - midSortedTx["otherData"] = tx["id"].toString(); - - if (txHeight >= latestTxnBlockHeight) { - latestTxnBlockHeight = txHeight; - } - - midSortedArray.add(midSortedTx); - cachedMap?.remove(tx["id"].toString()); - cachedMap?.remove(commitId); - Logging.instance.log("cmap: $cachedMap", level: LogLevel.Info); - } - - midSortedArray - .sort((a, b) => (b["timestamp"] as int) - (a["timestamp"] as int)); - - final Map result = {"dateTimeChunks": []}; - final dateArray = []; - - for (int i = 0; i < midSortedArray.length; i++) { - final txObject = midSortedArray[i]; - final date = extractDateFromTimestamp(txObject["timestamp"] as int); - - final txTimeArray = [txObject["timestamp"], date]; - - if (dateArray.contains(txTimeArray[1])) { - result["dateTimeChunks"].forEach((dynamic chunk) { - if (extractDateFromTimestamp(chunk["timestamp"] as int) == - txTimeArray[1]) { - if (chunk["transactions"] == null) { - chunk["transactions"] = >[]; - } - chunk["transactions"].add(txObject); - } - }); + if (isConfirmed) { + height = tx["kernel_lookup_min_height"] as int? ?? 1; } else { - dateArray.add(txTimeArray[1]); - - final chunk = { - "timestamp": txTimeArray[0], - "transactions": [txObject], - }; - - // result["dateTimeChunks"]. - result["dateTimeChunks"].add(chunk); + height = null; } - } - final transactionsMap = - TransactionData.fromJson(result).getAllTransactions(); - if (cachedMap != null) { - transactionsMap.addAll(cachedMap); + + final isIncoming = (tx["tx_type"] == "TxReceived" || + tx["tx_type"] == "TxReceivedCancelled"); + + final txn = isar_models.Transaction( + walletId: walletId, + txid: commitId ?? tx["id"].toString(), + timestamp: (dt.millisecondsSinceEpoch ~/ 1000), + type: isIncoming + ? isar_models.TransactionType.incoming + : isar_models.TransactionType.outgoing, + subType: isar_models.TransactionSubType.none, + amount: amt, + amountString: Amount( + rawValue: BigInt.from(amt), + fractionDigits: coin.decimals, + ).toJsonString(), + fee: (tx["fee"] == null) ? 0 : int.parse(tx["fee"] as String), + height: height, + isCancelled: tx["tx_type"] == "TxSentCancelled" || + tx["tx_type"] == "TxReceivedCancelled", + isLelantus: false, + slateId: slateId, + nonce: null, + otherData: tx["id"].toString(), + inputs: [], + outputs: [], + ); + + // txn.address = + // ""; // for this when you send a transaction you will just need to save in a hashmap in hive with the key being the txid, and the value being the address it was sent to. then you can look this value up right here in your hashmap. + isar_models.Address? transactionAddress = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(address) + .findFirst(); + + if (transactionAddress == null) { + if (isIncoming) { + transactionAddress = isar_models.Address( + walletId: walletId, + value: address, + publicKey: [], + derivationIndex: 0, + derivationPath: null, + type: isar_models.AddressType.mimbleWimble, + subType: isar_models.AddressSubType.receiving, + ); + } else { + final myRcvAddr = await currentReceivingAddress; + final isSentToSelf = myRcvAddr == address; + + transactionAddress = isar_models.Address( + walletId: walletId, + value: address, + publicKey: [], + derivationIndex: isSentToSelf ? 0 : -1, + derivationPath: null, + type: isSentToSelf + ? isar_models.AddressType.mimbleWimble + : isar_models.AddressType.nonWallet, + subType: isSentToSelf + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.nonWallet, + ); + } + } + + // + // midSortedTx["inputSize"] = tx["num_inputs"]; + // midSortedTx["outputSize"] = tx["num_outputs"]; + // midSortedTx["aliens"] = []; + // midSortedTx["inputs"] = []; + // midSortedTx["outputs"] = []; + + // key id not used afaik? + // midSortedTx["key_id"] = tx["parent_key_id"]; + + // if (txHeight >= latestTxnBlockHeight) { + // latestTxnBlockHeight = txHeight; + // } + + txnsData.add(Tuple2(txn, transactionAddress)); + // cachedMap?.remove(tx["id"].toString()); + // cachedMap?.remove(commitId); + // Logging.instance.log("cmap: $cachedMap", level: LogLevel.Info); } - final txModel = TransactionData.fromMap(transactionsMap); + await db.addNewTransactionData(txnsData, walletId); - await DB.instance.put( - boxName: walletId, - key: 'storedTxnDataHeight', - value: latestTxnBlockHeight); - await DB.instance.put( - boxName: walletId, key: 'latest_tx_model', value: txModel); + // quick hack to notify manager to call notifyListeners if + // transactions changed + if (txnsData.isNotEmpty) { + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "Transactions updated/added for: $walletId $walletName ", + walletId, + ), + ); + } - return txModel; + // midSortedArray + // .sort((a, b) => (b["timestamp"] as int) - (a["timestamp"] as int)); + // + // final Map result = {"dateTimeChunks": []}; + // final dateArray = []; + // + // for (int i = 0; i < midSortedArray.length; i++) { + // final txObject = midSortedArray[i]; + // final date = extractDateFromTimestamp(txObject["timestamp"] as int); + // + // final txTimeArray = [txObject["timestamp"], date]; + // + // if (dateArray.contains(txTimeArray[1])) { + // result["dateTimeChunks"].forEach((dynamic chunk) { + // if (extractDateFromTimestamp(chunk["timestamp"] as int) == + // txTimeArray[1]) { + // if (chunk["transactions"] == null) { + // chunk["transactions"] = >[]; + // } + // chunk["transactions"].add(txObject); + // } + // }); + // } else { + // dateArray.add(txTimeArray[1]); + // + // final chunk = { + // "timestamp": txTimeArray[0], + // "transactions": [txObject], + // };sendAll + // + // // result["dateTimeChunks"]. + // result["dateTimeChunks"].add(chunk); + // } + // } + // final transactionsMap = + // TransactionData.fromJson(result).getAllTransactions(); + // if (cachedMap != null) { + // transactionsMap.addAll(cachedMap); + // } + // + // final txModel = TransactionData.fromMap(transactionsMap); + // + // await DB.instance.put( + // boxName: walletId, + // key: 'storedTxnDataHeight', + // value: latestTxnBlockHeight); + // await DB.instance.put( + // boxName: walletId, key: 'latest_tx_model', value: txModel); + // + // return txModel; } - @override - Future get transactionData => - _transactionData ??= _fetchTransactionData(); - Future? _transactionData; - - // not used in epic - TransactionData? cachedTxData; - @override Future updateSentCachedTxData(Map txData) async { // not used in epic } - @override - Future> get unspentOutputs => throw UnimplementedError(); - @override bool validateAddress(String address) { + //Invalid address that contains HTTP and epicbox domain + if ((address.startsWith("http://") || address.startsWith("https://")) && + address.contains("@")) { + return false; + } if (address.startsWith("http://") || address.startsWith("https://")) { if (Uri.tryParse(address) != null) { return true; @@ -2308,7 +1890,11 @@ class EpicCashWallet extends CoinServiceAPI { String validate = validateSendAddress(address); if (int.parse(validate) == 1) { - return true; + //Check if address contrains a domain + if (address.contains("@")) { + return true; + } + return false; } else { return false; } @@ -2316,7 +1902,7 @@ class EpicCashWallet extends CoinServiceAPI { @override String get walletId => _walletId; - late String _walletId; + late final String _walletId; @override String get walletName => _walletName; @@ -2343,28 +1929,19 @@ class EpicCashWallet extends CoinServiceAPI { bool isActive = false; @override - Future estimateFeeFor(int satoshiAmount, int feeRate) async { - int currentFee = await nativeFee(satoshiAmount, ifErrorEstimateFee: true); - // TODO: implement this - return currentFee; + Future estimateFeeFor(Amount amount, int feeRate) async { + int currentFee = + await nativeFee(amount.raw.toInt(), ifErrorEstimateFee: true); + return Amount( + rawValue: BigInt.from(currentFee), + fractionDigits: coin.decimals, + ); } // not used in epic currently @override Future generateNewAddress() async { try { - // await incrementAddressIndexForChain( - // 0); // First increment the receiving index - // final newReceivingIndex = - // DB.instance.get(boxName: walletId, key: 'receivingIndex') - // as int; // Check the new receiving index - // final newReceivingAddress = await _generateAddressForChain(0, - // newReceivingIndex); // Use new index to derive a new receiving address - // await addToAddressesArrayForChain(newReceivingAddress, - // 0); // Add that new receiving address to the array of receiving addresses - // _currentReceivingAddress = Future(() => - // newReceivingAddress); // Set the new receiving address that the service - return true; } catch (e, s) { Logging.instance.log( @@ -2373,4 +1950,51 @@ class EpicCashWallet extends CoinServiceAPI { return false; } } + + Future _refreshBalance() async { + String walletBalances = await allWalletBalances(); + var jsonBalances = json.decode(walletBalances); + + final spendable = + (jsonBalances['amount_currently_spendable'] as double).toString(); + + final pending = + (jsonBalances['amount_awaiting_confirmation'] as double).toString(); + + final total = (jsonBalances['total'] as double).toString(); + final awaiting = + (jsonBalances['amount_awaiting_finalization'] as double).toString(); + + _balance = Balance( + total: Amount.fromDecimal( + Decimal.parse(total) + Decimal.parse(awaiting), + fractionDigits: coin.decimals, + ), + spendable: Amount.fromDecimal( + Decimal.parse(spendable), + fractionDigits: coin.decimals, + ), + blockedTotal: Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ), + pendingSpendable: Amount.fromDecimal( + Decimal.parse(pending), + fractionDigits: coin.decimals, + ), + ); + + await updateCachedBalance(_balance!); + } + + @override + Balance get balance => _balance ??= getCachedBalance(); + Balance? _balance; + + @override + Future> get utxos => throw UnimplementedError(); + + @override + Future> get transactions => + db.getTransactions(walletId).findAll(); } diff --git a/lib/services/coins/ethereum/ethereum_wallet.dart b/lib/services/coins/ethereum/ethereum_wallet.dart new file mode 100644 index 000000000..0e933a1c0 --- /dev/null +++ b/lib/services/coins/ethereum/ethereum_wallet.dart @@ -0,0 +1,1117 @@ +import 'dart:async'; + +import 'package:bip39/bip39.dart' as bip39; +import 'package:ethereum_addresses/ethereum_addresses.dart'; +import 'package:http/http.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/hive/db.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/models/node_model.dart'; +import 'package:stackwallet/models/paymint/fee_object_model.dart'; +import 'package:stackwallet/services/coins/coin_service.dart'; +import 'package:stackwallet/services/ethereum/ethereum_api.dart'; +import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; +import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart'; +import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; +import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; +import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +import 'package:stackwallet/services/mixins/eth_token_cache.dart'; +import 'package:stackwallet/services/mixins/wallet_cache.dart'; +import 'package:stackwallet/services/mixins/wallet_db.dart'; +import 'package:stackwallet/services/node_service.dart'; +import 'package:stackwallet/services/transaction_notification_tracker.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/default_nodes.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; +import 'package:stackwallet/utilities/eth_commons.dart'; +import 'package:stackwallet/utilities/extensions/extensions.dart'; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/prefs.dart'; +import 'package:stackwallet/widgets/crypto_notifications.dart'; +import 'package:tuple/tuple.dart'; +import 'package:web3dart/web3dart.dart' as web3; + +const int MINIMUM_CONFIRMATIONS = 3; + +class EthereumWallet extends CoinServiceAPI with WalletCache, WalletDB { + EthereumWallet({ + required String walletId, + required String walletName, + required Coin coin, + required SecureStorageInterface secureStore, + required TransactionNotificationTracker tracker, + MainDB? mockableOverride, + }) { + txTracker = tracker; + _walletId = walletId; + _walletName = walletName; + _coin = coin; + _secureStore = secureStore; + initCache(walletId, coin); + initWalletDB(mockableOverride: mockableOverride); + } + + NodeModel? _ethNode; + + final _gasLimit = 21000; + + Timer? timer; + Timer? _networkAliveTimer; + + Future updateTokenContracts(List contractAddresses) async { + // final set = getWalletTokenContractAddresses().toSet(); + // set.addAll(contractAddresses); + await updateWalletTokenContractAddresses(contractAddresses); + + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "$contractAddresses updated/added for: $walletId $walletName", + walletId, + ), + ); + } + + Balance getCachedTokenBalance(EthContract contract) { + final jsonString = DB.instance.get( + boxName: _walletId, + key: TokenCacheKeys.tokenBalance(contract.address), + ) as String?; + if (jsonString == null) { + return Balance( + total: Amount( + rawValue: BigInt.zero, + fractionDigits: contract.decimals, + ), + spendable: Amount( + rawValue: BigInt.zero, + fractionDigits: contract.decimals, + ), + blockedTotal: Amount( + rawValue: BigInt.zero, + fractionDigits: contract.decimals, + ), + pendingSpendable: Amount( + rawValue: BigInt.zero, + fractionDigits: contract.decimals, + ), + ); + } + return Balance.fromJson( + jsonString, + contract.decimals, + ); + } + + // Future removeTokenContract(String contractAddress) async { + // final set = getWalletTokenContractAddresses().toSet(); + // set.removeWhere((e) => e == contractAddress); + // await updateWalletTokenContractAddresses(set.toList()); + // + // GlobalEventBus.instance.fire( + // UpdatedInBackgroundEvent( + // "$contractAddress removed for: $walletId $walletName", + // walletId, + // ), + // ); + // } + + @override + String get walletId => _walletId; + late String _walletId; + + @override + String get walletName => _walletName; + late String _walletName; + + @override + set walletName(String newName) => _walletName = newName; + + @override + set isFavorite(bool markFavorite) { + _isFavorite = markFavorite; + updateCachedIsFavorite(markFavorite); + } + + @override + bool get isFavorite => _isFavorite ??= getCachedIsFavorite(); + bool? _isFavorite; + + @override + Coin get coin => _coin; + late Coin _coin; + + late SecureStorageInterface _secureStore; + late final TransactionNotificationTracker txTracker; + final _prefs = Prefs.instance; + bool longMutex = false; + + NodeModel getCurrentNode() { + return _ethNode ?? + NodeService(secureStorageInterface: _secureStore) + .getPrimaryNodeFor(coin: coin) ?? + DefaultNodes.getNodeFor(coin); + } + + web3.Web3Client getEthClient() { + final node = getCurrentNode(); + return web3.Web3Client(node.host, Client()); + } + + late web3.EthPrivateKey _credentials; + + bool _shouldAutoSync = false; + + @override + bool get shouldAutoSync => _shouldAutoSync; + + @override + set shouldAutoSync(bool shouldAutoSync) { + if (_shouldAutoSync != shouldAutoSync) { + _shouldAutoSync = shouldAutoSync; + if (!shouldAutoSync) { + timer?.cancel(); + timer = null; + stopNetworkAlivePinging(); + } else { + startNetworkAlivePinging(); + refresh(); + } + } + } + + @override + Future> get utxos => db.getUTXOs(walletId).findAll(); + + @override + Future> get transactions => db + .getTransactions(walletId) + .filter() + .otherDataEqualTo( + null) // eth txns with other data where other data is the token contract address + .sortByTimestampDesc() + .findAll(); + + @override + Future get currentReceivingAddress async { + final address = await _currentReceivingAddress; + return checksumEthereumAddress( + address?.value ?? _credentials.address.hexEip55); + } + + Future get _currentReceivingAddress => db + .getAddresses(walletId) + .filter() + .typeEqualTo(AddressType.ethereum) + .subTypeEqualTo(AddressSubType.receiving) + .sortByDerivationIndexDesc() + .findFirst(); + + @override + Balance get balance => _balance ??= getCachedBalance(); + Balance? _balance; + + Future updateBalance() async { + web3.Web3Client client = getEthClient(); + web3.EtherAmount ethBalance = await client.getBalance(_credentials.address); + _balance = Balance( + total: Amount( + rawValue: ethBalance.getInWei, + fractionDigits: coin.decimals, + ), + spendable: Amount( + rawValue: ethBalance.getInWei, + fractionDigits: coin.decimals, + ), + blockedTotal: Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ), + pendingSpendable: Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ), + ); + await updateCachedBalance(_balance!); + } + + @override + Future estimateFeeFor(Amount amount, int feeRate) async { + return estimateFee(feeRate, _gasLimit, coin.decimals); + } + + @override + Future exit() async { + _hasCalledExit = true; + timer?.cancel(); + timer = null; + stopNetworkAlivePinging(); + } + + @override + Future get fees => EthereumAPI.getFees(); + + //Full rescan is not needed for ETH since we have a balance + @override + Future fullRescan( + int maxUnusedAddressGap, int maxNumberOfIndexesToCheck) { + // TODO: implement fullRescan + throw UnimplementedError(); + } + + @override + Future generateNewAddress() { + // TODO: implement generateNewAddress - might not be needed for ETH + throw UnimplementedError(); + } + + bool _hasCalledExit = false; + + @override + bool get hasCalledExit => _hasCalledExit; + + @override + Future initializeExisting() async { + Logging.instance.log( + "initializeExisting() ${coin.prettyName} wallet", + level: LogLevel.Info, + ); + + //First get mnemonic so we can initialize credentials + String privateKey = + getPrivateKey((await mnemonicString)!, (await mnemonicPassphrase)!); + _credentials = web3.EthPrivateKey.fromHex(privateKey); + + if (getCachedId() == null) { + throw Exception( + "Attempted to initialize an existing wallet using an unknown wallet ID!"); + } + await _prefs.init(); + } + + @override + Future initializeNew() async { + Logging.instance.log( + "Generating new ${coin.prettyName} wallet.", + level: LogLevel.Info, + ); + + if (getCachedId() != null) { + throw Exception( + "Attempted to initialize a new wallet using an existing wallet ID!"); + } + + await _prefs.init(); + + try { + await _generateNewWallet(); + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from initializeNew(): $e\n$s", + level: LogLevel.Fatal, + ); + rethrow; + } + await Future.wait([ + updateCachedId(walletId), + updateCachedIsFavorite(false), + ]); + } + + Future _generateNewWallet() async { + // Logging.instance + // .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); + // if (!integrationTestFlag) { + // try { + // final features = await electrumXClient.getServerFeatures(); + // Logging.instance.log("features: $features", level: LogLevel.Info); + // switch (coin) { + // case Coin.namecoin: + // if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { + // throw Exception("genesis hash does not match main net!"); + // } + // break; + // default: + // throw Exception( + // "Attempted to generate a EthereumWallet using a non eth coin type: ${coin.name}"); + // } + // } catch (e, s) { + // Logging.instance.log("$e/n$s", level: LogLevel.Info); + // } + // } + + // this should never fail - sanity check + if ((await mnemonicString) != null || (await mnemonicPassphrase) != null) { + throw Exception( + "Attempted to overwrite mnemonic on generate new wallet!"); + } + + final String mnemonic = bip39.generateMnemonic(strength: 256); + await _secureStore.write(key: '${_walletId}_mnemonic', value: mnemonic); + await _secureStore.write( + key: '${_walletId}_mnemonicPassphrase', + value: "", + ); + + String privateKey = getPrivateKey(mnemonic, ""); + _credentials = web3.EthPrivateKey.fromHex(privateKey); + + final address = Address( + walletId: walletId, + value: _credentials.address.hexEip55, + publicKey: [], // maybe store address bytes here? seems a waste of space though + derivationIndex: 0, + derivationPath: DerivationPath()..value = "$hdPathEthereum/0", + type: AddressType.ethereum, + subType: AddressSubType.receiving, + ); + + await db.putAddress(address); + + Logging.instance.log("_generateNewWalletFinished", level: LogLevel.Info); + } + + bool _isConnected = false; + + @override + bool get isConnected => _isConnected; + + @override + bool get isRefreshing => refreshMutex; + + bool refreshMutex = false; + + @override + Future get maxFee async { + throw UnimplementedError("Not used for eth"); + } + + @override + Future> get mnemonic => _getMnemonicList(); + + @override + Future get mnemonicString => + _secureStore.read(key: '${_walletId}_mnemonic'); + + @override + Future get mnemonicPassphrase => _secureStore.read( + key: '${_walletId}_mnemonicPassphrase', + ); + + Future get chainHeight async { + web3.Web3Client client = getEthClient(); + try { + final height = await client.getBlockNumber(); + await updateCachedChainHeight(height); + if (height > storedChainHeight) { + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "Updated current chain height in $walletId $walletName!", + walletId, + ), + ); + } + return height; + } catch (e, s) { + Logging.instance.log("Exception caught in chainHeight: $e\n$s", + level: LogLevel.Error); + return storedChainHeight; + } + } + + @override + int get storedChainHeight => getCachedChainHeight(); + + Future> _getMnemonicList() async { + final _mnemonicString = await mnemonicString; + if (_mnemonicString == null) { + return []; + } + final List data = _mnemonicString.split(' '); + return data; + } + + @override + Future> prepareSend({ + required String address, + required Amount amount, + Map? args, + }) async { + final feeRateType = args?["feeRate"]; + int fee = 0; + final feeObject = await fees; + switch (feeRateType) { + case FeeRateType.fast: + fee = feeObject.fast; + break; + case FeeRateType.average: + fee = feeObject.medium; + break; + case FeeRateType.slow: + fee = feeObject.slow; + break; + } + + final feeEstimate = await estimateFeeFor(amount, fee); + + // bool isSendAll = false; + // final availableBalance = balance.spendable; + // if (satoshiAmount == availableBalance) { + // isSendAll = true; + // } + // + // if (isSendAll) { + // //Subtract fee amount from send amount + // satoshiAmount -= feeEstimate; + // } + + final client = getEthClient(); + + final myAddress = await currentReceivingAddress; + final myWeb3Address = web3.EthereumAddress.fromHex(myAddress); + + final est = await client.estimateGas( + sender: myWeb3Address, + to: web3.EthereumAddress.fromHex(address), + gasPrice: web3.EtherAmount.fromUnitAndValue( + web3.EtherUnit.wei, + fee, + ), + amountOfGas: BigInt.from(_gasLimit), + value: web3.EtherAmount.inWei(amount.raw), + ); + + final nonce = args?["nonce"] as int? ?? + await client.getTransactionCount(myWeb3Address, + atBlock: const web3.BlockNum.pending()); + + final nResponse = await EthereumAPI.getAddressNonce(address: myAddress); + print("=============================================================="); + print("ETH client.estimateGas: $est"); + print("ETH estimateFeeFor : $feeEstimate"); + print("ETH nonce custom response: $nResponse"); + print("ETH actual nonce : $nonce"); + print("=============================================================="); + + final tx = web3.Transaction( + to: web3.EthereumAddress.fromHex(address), + gasPrice: web3.EtherAmount.fromUnitAndValue( + web3.EtherUnit.wei, + fee, + ), + maxGas: _gasLimit, + value: web3.EtherAmount.inWei(amount.raw), + nonce: nonce, + ); + + Map txData = { + "fee": feeEstimate, + "feeInWei": fee, + "address": address, + "recipientAmt": amount, + "ethTx": tx, + "chainId": (await client.getChainId()).toInt(), + "nonce": tx.nonce, + }; + + return txData; + } + + @override + Future confirmSend({required Map txData}) async { + web3.Web3Client client = getEthClient(); + + final txid = await client.sendTransaction( + _credentials, + txData["ethTx"] as web3.Transaction, + chainId: txData["chainId"] as int, + ); + + return txid; + } + + @override + Future recoverFromMnemonic({ + required String mnemonic, + String? mnemonicPassphrase, + required int maxUnusedAddressGap, + required int maxNumberOfIndexesToCheck, + required int height, + }) async { + longMutex = true; + final start = DateTime.now(); + + try { + // check to make sure we aren't overwriting a mnemonic + // this should never fail + if ((await mnemonicString) != null || + (await this.mnemonicPassphrase) != null) { + longMutex = false; + throw Exception("Attempted to overwrite mnemonic on restore!"); + } + + await _secureStore.write( + key: '${_walletId}_mnemonic', value: mnemonic.trim()); + await _secureStore.write( + key: '${_walletId}_mnemonicPassphrase', + value: mnemonicPassphrase ?? "", + ); + + String privateKey = + getPrivateKey(mnemonic.trim(), mnemonicPassphrase ?? ""); + _credentials = web3.EthPrivateKey.fromHex(privateKey); + + final address = Address( + walletId: walletId, + value: _credentials.address.hexEip55, + publicKey: [], // maybe store address bytes here? seems a waste of space though + derivationIndex: 0, + derivationPath: DerivationPath()..value = "$hdPathEthereum/0", + type: AddressType.ethereum, + subType: AddressSubType.receiving, + ); + + await db.putAddress(address); + + await Future.wait([ + updateCachedId(walletId), + updateCachedIsFavorite(false), + ]); + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from recoverFromMnemonic(): $e\n$s", + level: LogLevel.Error); + longMutex = false; + rethrow; + } + + longMutex = false; + final end = DateTime.now(); + Logging.instance.log( + "$walletName recovery time: ${end.difference(start).inMilliseconds} millis", + level: LogLevel.Info); + } + + Future> _fetchAllOwnAddresses() => db + .getAddresses(walletId) + .filter() + .not() + .typeEqualTo(AddressType.nonWallet) + .and() + .group((q) => q + .subTypeEqualTo(AddressSubType.receiving) + .or() + .subTypeEqualTo(AddressSubType.change)) + .findAll(); + + Future refreshIfThereIsNewData() async { + if (longMutex) return false; + if (_hasCalledExit) return false; + final currentChainHeight = await chainHeight; + + try { + bool needsRefresh = false; + Set txnsToCheck = {}; + + for (final String txid in txTracker.pendings) { + if (!txTracker.wasNotifiedConfirmed(txid)) { + txnsToCheck.add(txid); + } + } + + for (String txid in txnsToCheck) { + final response = await EthereumAPI.getEthTransactionByHash(txid); + final txBlockNumber = response.value?.blockNumber; + + if (txBlockNumber != null) { + final int txConfirmations = currentChainHeight - txBlockNumber; + bool isUnconfirmed = txConfirmations < MINIMUM_CONFIRMATIONS; + if (!isUnconfirmed) { + needsRefresh = true; + break; + } + } + } + if (!needsRefresh) { + var allOwnAddresses = await _fetchAllOwnAddresses(); + final response = await EthereumAPI.getEthTransactions( + allOwnAddresses.elementAt(0).value, + ); + if (response.value != null) { + final allTxs = response.value!; + for (final element in allTxs) { + final txid = element.hash; + if ((await db + .getTransactions(walletId) + .filter() + .txidMatches(txid) + .findFirst()) == + null) { + Logging.instance.log( + " txid not found in address history already $txid", + level: LogLevel.Info); + needsRefresh = true; + break; + } + } + } else { + Logging.instance.log( + " refreshIfThereIsNewData get eth transactions failed: ${response.exception}", + level: LogLevel.Error, + ); + } + } + return needsRefresh; + } catch (e, s) { + Logging.instance.log( + "Exception caught in refreshIfThereIsNewData: $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + Future getAllTxsToWatch() async { + if (_hasCalledExit) return; + List unconfirmedTxnsToNotifyPending = []; + List unconfirmedTxnsToNotifyConfirmed = []; + + final currentChainHeight = await chainHeight; + + final txCount = await db.getTransactions(walletId).count(); + + const paginateLimit = 50; + + for (int i = 0; i < txCount; i += paginateLimit) { + final transactions = await db + .getTransactions(walletId) + .offset(i) + .limit(paginateLimit) + .findAll(); + for (final tx in transactions) { + if (tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { + // get all transactions that were notified as pending but not as confirmed + if (txTracker.wasNotifiedPending(tx.txid) && + !txTracker.wasNotifiedConfirmed(tx.txid)) { + unconfirmedTxnsToNotifyConfirmed.add(tx); + } + } else { + // get all transactions that were not notified as pending yet + if (!txTracker.wasNotifiedPending(tx.txid)) { + unconfirmedTxnsToNotifyPending.add(tx); + } + } + } + } + + // notify on unconfirmed transactions + for (final tx in unconfirmedTxnsToNotifyPending) { + final confirmations = tx.getConfirmations(currentChainHeight); + + if (tx.type == TransactionType.incoming) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Incoming transaction", + walletId: walletId, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, + txid: tx.txid, + confirmations: confirmations, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); + + await txTracker.addNotifiedPending(tx.txid); + } else if (tx.type == TransactionType.outgoing) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Sending transaction", + walletId: walletId, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, + txid: tx.txid, + confirmations: confirmations, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); + + await txTracker.addNotifiedPending(tx.txid); + } + } + + // notify on confirmed + for (final tx in unconfirmedTxnsToNotifyConfirmed) { + if (tx.type == TransactionType.incoming) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Incoming transaction confirmed", + walletId: walletId, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: false, + txid: tx.txid, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); + + await txTracker.addNotifiedConfirmed(tx.txid); + } else if (tx.type == TransactionType.outgoing) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Outgoing transaction confirmed", + walletId: walletId, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: false, + txid: tx.txid, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); + + await txTracker.addNotifiedConfirmed(tx.txid); + } + } + } + + @override + Future refresh() async { + if (refreshMutex) { + Logging.instance.log("$walletId $walletName refreshMutex denied", + level: LogLevel.Info); + return; + } else { + refreshMutex = true; + } + + try { + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.syncing, + walletId, + coin, + ), + ); + + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId)); + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId)); + + final currentHeight = await chainHeight; + const storedHeight = 1; //await storedChainHeight; + + Logging.instance + .log("chain height: $currentHeight", level: LogLevel.Info); + Logging.instance + .log("cached height: $storedHeight", level: LogLevel.Info); + + if (currentHeight != storedHeight) { + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId)); + + final newTxDataFuture = _refreshTransactions(); + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(0.50, walletId)); + + // final feeObj = _getFees(); + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(0.60, walletId)); + + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(0.70, walletId)); + // _feeObject = Future(() => feeObj); + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(0.80, walletId)); + + final allTxsToWatch = getAllTxsToWatch(); + await Future.wait([ + updateBalance(), + newTxDataFuture, + // feeObj, + allTxsToWatch, + ]); + GlobalEventBus.instance + .fire(RefreshPercentChangedEvent(0.90, walletId)); + } + refreshMutex = false; + GlobalEventBus.instance.fire(RefreshPercentChangedEvent(1.0, walletId)); + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.synced, + walletId, + coin, + ), + ); + + if (shouldAutoSync) { + timer ??= Timer.periodic(const Duration(seconds: 30), (timer) async { + Logging.instance.log( + "Periodic refresh check for $walletId $walletName in object instance: $hashCode", + level: LogLevel.Info); + if (await refreshIfThereIsNewData()) { + await refresh(); + GlobalEventBus.instance.fire(UpdatedInBackgroundEvent( + "New data found in $walletId $walletName in background!", + walletId)); + } + }); + } + } catch (error, strace) { + refreshMutex = false; + GlobalEventBus.instance.fire( + NodeConnectionStatusChangedEvent( + NodeConnectionStatus.disconnected, + walletId, + coin, + ), + ); + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.unableToSync, + walletId, + coin, + ), + ); + Logging.instance.log( + "Caught exception in $walletName $walletId refresh(): $error\n$strace", + level: LogLevel.Warning, + ); + } + } + + @override + Future testNetworkConnection() async { + web3.Web3Client client = getEthClient(); + try { + await client.getBlockNumber(); + return true; + } catch (_) { + return false; + } + } + + void _periodicPingCheck() async { + bool hasNetwork = await testNetworkConnection(); + _isConnected = hasNetwork; + if (_isConnected != hasNetwork) { + NodeConnectionStatus status = hasNetwork + ? NodeConnectionStatus.connected + : NodeConnectionStatus.disconnected; + GlobalEventBus.instance + .fire(NodeConnectionStatusChangedEvent(status, walletId, coin)); + } + } + + @override + Future updateNode(bool shouldRefresh) async { + _ethNode = NodeService(secureStorageInterface: _secureStore) + .getPrimaryNodeFor(coin: coin) ?? + DefaultNodes.getNodeFor(coin); + + if (shouldRefresh) { + unawaited(refresh()); + } + } + + @override + Future updateSentCachedTxData(Map txData) async { + final txid = txData["txid"] as String; + final addressString = checksumEthereumAddress(txData["address"] as String); + final response = await EthereumAPI.getEthTransactionByHash(txid); + + final transaction = Transaction( + walletId: walletId, + txid: txid, + timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, + type: TransactionType.outgoing, + subType: TransactionSubType.none, + // precision may be lost here hence the following amountString + amount: (txData["recipientAmt"] as Amount).raw.toInt(), + amountString: (txData["recipientAmt"] as Amount).toJsonString(), + fee: (txData["fee"] as Amount).raw.toInt(), + height: null, + isCancelled: false, + isLelantus: false, + otherData: null, + slateId: null, + nonce: (txData["nonce"] as int?) ?? + response.value?.nonce.toBigIntFromHex.toInt(), + inputs: [], + outputs: [], + ); + + Address? address = await db.getAddress( + walletId, + addressString, + ); + + address ??= Address( + walletId: walletId, + value: addressString, + publicKey: [], + derivationIndex: -1, + derivationPath: null, + type: AddressType.ethereum, + subType: AddressSubType.nonWallet, + ); + + await db.addNewTransactionData( + [ + Tuple2(transaction, address), + ], + walletId, + ); + } + + @override + bool validateAddress(String address) { + return isValidEthereumAddress(address); + } + + Future _refreshTransactions() async { + String thisAddress = await currentReceivingAddress; + + final response = await EthereumAPI.getEthTransactions(thisAddress); + + if (response.value == null) { + Logging.instance.log( + "Failed to refresh transactions for ${coin.prettyName} $walletName " + "$walletId: ${response.exception}", + level: LogLevel.Warning, + ); + return; + } + + final txsResponse = + await EthereumAPI.getEthTransactionNonces(response.value!); + + if (txsResponse.value != null) { + final allTxs = txsResponse.value!; + final List> txnsData = []; + for (final tuple in allTxs) { + final element = tuple.item1; + + Amount transactionAmount = element.value; + + bool isIncoming; + bool txFailed = false; + if (checksumEthereumAddress(element.from) == thisAddress) { + if (element.isError != 0) { + txFailed = true; + } + isIncoming = false; + } else { + isIncoming = true; + } + + //Calculate fees (GasLimit * gasPrice) + // int txFee = element.gasPrice * element.gasUsed; + Amount txFee = element.gasCost; + + final String addressString = checksumEthereumAddress(element.to); + final int height = element.blockNumber; + + final txn = Transaction( + walletId: walletId, + txid: element.hash, + timestamp: element.timestamp, + type: + isIncoming ? TransactionType.incoming : TransactionType.outgoing, + subType: TransactionSubType.none, + amount: transactionAmount.raw.toInt(), + amountString: transactionAmount.toJsonString(), + fee: txFee.raw.toInt(), + height: height, + isCancelled: txFailed, + isLelantus: false, + slateId: null, + otherData: null, + nonce: tuple.item2, + inputs: [], + outputs: [], + ); + + Address? transactionAddress = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(addressString) + .findFirst(); + + if (transactionAddress == null) { + if (isIncoming) { + transactionAddress = Address( + walletId: walletId, + value: addressString, + publicKey: [], + derivationIndex: 0, + derivationPath: DerivationPath()..value = "$hdPathEthereum/0", + type: AddressType.ethereum, + subType: AddressSubType.receiving, + ); + } else { + final myRcvAddr = await currentReceivingAddress; + final isSentToSelf = myRcvAddr == addressString; + + transactionAddress = Address( + walletId: walletId, + value: addressString, + publicKey: [], + derivationIndex: isSentToSelf ? 0 : -1, + derivationPath: isSentToSelf + ? (DerivationPath()..value = "$hdPathEthereum/0") + : null, + type: AddressType.ethereum, + subType: isSentToSelf + ? AddressSubType.receiving + : AddressSubType.nonWallet, + ); + } + } + + txnsData.add(Tuple2(txn, transactionAddress)); + } + await db.addNewTransactionData(txnsData, walletId); + + // quick hack to notify manager to call notifyListeners if + // transactions changed + if (txnsData.isNotEmpty) { + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "Transactions updated/added for: $walletId $walletName ", + walletId, + ), + ); + } + } else { + Logging.instance.log( + "Failed to refresh transactions with nonces for ${coin.prettyName} " + "$walletName $walletId: ${txsResponse.exception}", + level: LogLevel.Warning, + ); + } + } + + void stopNetworkAlivePinging() { + _networkAliveTimer?.cancel(); + _networkAliveTimer = null; + } + + void startNetworkAlivePinging() { + // call once on start right away + _periodicPingCheck(); + + // then periodically check + _networkAliveTimer = Timer.periodic( + Constants.networkAliveTimerDuration, + (_) async { + _periodicPingCheck(); + }, + ); + } +} diff --git a/lib/services/coins/firo/firo_wallet.dart b/lib/services/coins/firo/firo_wallet.dart index 5d29bd0a9..23e367ca4 100644 --- a/lib/services/coins/firo/firo_wallet.dart +++ b/lib/services/coins/firo/firo_wallet.dart @@ -3,44 +3,48 @@ import 'dart:convert'; import 'dart:io'; import 'dart:isolate'; import 'dart:math'; -import 'dart:typed_data'; import 'package:bip32/bip32.dart' as bip32; import 'package:bip39/bip39.dart' as bip39; import 'package:bitcoindart/bitcoindart.dart'; import 'package:decimal/decimal.dart'; -import 'package:devicelocale/devicelocale.dart'; import 'package:flutter/foundation.dart'; -import 'package:http/http.dart'; +import 'package:isar/isar.dart'; import 'package:lelantus/lelantus.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; -import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; import 'package:stackwallet/models/lelantus_coin.dart'; import 'package:stackwallet/models/lelantus_fee_data.dart'; -import 'package:stackwallet/models/models.dart' as models; import 'package:stackwallet/models/paymint/fee_object_model.dart'; -import 'package:stackwallet/models/paymint/utxo_model.dart'; +import 'package:stackwallet/models/signing_data.dart'; import 'package:stackwallet/services/coins/coin_service.dart'; import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +import 'package:stackwallet/services/mixins/firo_hive.dart'; +import 'package:stackwallet/services/mixins/wallet_cache.dart'; +import 'package:stackwallet/services/mixins/wallet_db.dart'; +import 'package:stackwallet/services/mixins/xpubable.dart'; import 'package:stackwallet/services/node_service.dart'; -import 'package:stackwallet/services/notifications_api.dart'; -import 'package:stackwallet/services/price.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; import 'package:stackwallet/utilities/address_utils.dart'; -import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/bip32_utils.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/default_nodes.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart'; import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; +import 'package:stackwallet/widgets/crypto_notifications.dart'; import 'package:tuple/tuple.dart'; import 'package:uuid/uuid.dart'; @@ -101,29 +105,28 @@ Future executeNative(Map arguments) async { final address = arguments['address'] as String; final subtractFeeFromAmount = arguments['subtractFeeFromAmount'] as bool; final mnemonic = arguments['mnemonic'] as String; + final mnemonicPassphrase = arguments['mnemonicPassphrase'] as String; final index = arguments['index'] as int; - final price = arguments['price'] as Decimal; final lelantusEntries = arguments['lelantusEntries'] as List; final coin = arguments['coin'] as Coin; final network = arguments['network'] as NetworkType?; final locktime = arguments['locktime'] as int; final anonymitySets = arguments['_anonymity_sets'] as List?; - final locale = arguments["locale"] as String; if (!(network == null || anonymitySets == null)) { var joinSplit = await isolateCreateJoinSplitTransaction( - spendAmount, - address, - subtractFeeFromAmount, - mnemonic, - index, - price, - lelantusEntries, - locktime, - coin, - network, - anonymitySets, - locale); + spendAmount, + address, + subtractFeeFromAmount, + mnemonic, + mnemonicPassphrase, + index, + lelantusEntries, + locktime, + coin, + network, + anonymitySets, + ); sendPort.send(joinSplit); return; } @@ -143,33 +146,25 @@ Future executeNative(Map arguments) async { } else if (function == "restore") { final latestSetId = arguments['latestSetId'] as int; final setDataMap = arguments['setDataMap'] as Map; - final usedSerialNumbers = arguments['usedSerialNumbers'] as List?; + final usedSerialNumbers = arguments['usedSerialNumbers'] as List; final mnemonic = arguments['mnemonic'] as String; + final mnemonicPassphrase = arguments['mnemonicPassphrase'] as String; final coin = arguments['coin'] as Coin; - final network = arguments['network'] as NetworkType?; - if (!(usedSerialNumbers == null || network == null)) { - var restoreData = await isolateRestore( - mnemonic, - coin, - latestSetId, - setDataMap, - usedSerialNumbers, - network, - ); - sendPort.send(restoreData); - return; - } - } else if (function == "isolateDerive") { - final mnemonic = arguments['mnemonic'] as String; - final from = arguments['from'] as int; - final to = arguments['to'] as int; - final network = arguments['network'] as NetworkType?; - if (!(network == null)) { - var derived = await isolateDerive(mnemonic, from, to, network); - sendPort.send(derived); - return; - } + final network = arguments['network'] as NetworkType; + + final restoreData = await isolateRestore( + mnemonic, + mnemonicPassphrase, + coin, + latestSetId, + setDataMap, + usedSerialNumbers, + network, + ); + sendPort.send(restoreData); + return; } + Logging.instance.log( "Error Arguments for $function not formatted correctly", level: LogLevel.Fatal); @@ -193,49 +188,13 @@ void stop(ReceivePort port) { } } -Future> isolateDerive( - String mnemonic, int from, int to, NetworkType _network) async { - Map result = {}; - Map allReceive = {}; - Map allChange = {}; - final root = getBip32Root(mnemonic, _network); - for (int i = from; i < to; i++) { - var currentNode = getBip32NodeFromRoot(0, i, root); - var address = P2PKH( - network: _network, data: PaymentData(pubkey: currentNode.publicKey)) - .data - .address!; - allReceive["$i"] = { - "publicKey": Format.uint8listToString(currentNode.publicKey), - "wif": currentNode.toWIF(), - "address": address, - }; - - currentNode = getBip32NodeFromRoot(1, i, root); - address = P2PKH( - network: _network, data: PaymentData(pubkey: currentNode.publicKey)) - .data - .address!; - allChange["$i"] = { - "publicKey": Format.uint8listToString(currentNode.publicKey), - "wif": currentNode.toWIF(), - "address": address, - }; - if (i % 50 == 0) { - Logging.instance.log("thread at $i", level: LogLevel.Info); - } - } - result['receive'] = allReceive; - result['change'] = allChange; - return result; -} - Future> isolateRestore( String mnemonic, + String mnemonicPassphrase, Coin coin, int _latestSetId, Map _setDataMap, - List _usedSerialNumbers, + List _usedSerialNumbers, NetworkType network, ) async { List jindexes = []; @@ -246,50 +205,73 @@ Future> isolateRestore( var currentIndex = 0; try { - final usedSerialNumbers = _usedSerialNumbers; - Set usedSerialNumbersSet = {}; - for (int ind = 0; ind < usedSerialNumbers.length; ind++) { - usedSerialNumbersSet.add(usedSerialNumbers[ind]); - } + Set usedSerialNumbersSet = _usedSerialNumbers.toSet(); - final root = getBip32Root(mnemonic, network); + final root = await Bip32Utils.getBip32Root( + mnemonic, + mnemonicPassphrase, + network, + ); while (currentIndex < lastFoundIndex + 50) { - final mintKeyPair = getBip32NodeFromRoot(MINT_INDEX, currentIndex, root); - final mintTag = CreateTag( - Format.uint8listToString(mintKeyPair.privateKey!), - currentIndex, - Format.uint8listToString(mintKeyPair.identifier), - isTestnet: coin == Coin.firoTestNet); + final _derivePath = constructDerivePath( + networkWIF: network.wif, + chain: MINT_INDEX, + index: currentIndex, + ); + final bip32.BIP32 mintKeyPair = await Bip32Utils.getBip32NodeFromRoot( + root, + _derivePath, + ); + final String mintTag = CreateTag( + Format.uint8listToString(mintKeyPair.privateKey!), + currentIndex, + Format.uint8listToString(mintKeyPair.identifier), + isTestnet: coin == Coin.firoTestNet, + ); for (var setId = 1; setId <= _latestSetId; setId++) { - final setData = _setDataMap[setId]; - final foundCoin = setData["coins"].firstWhere( - (dynamic e) => e[1] == mintTag, - orElse: () => []); + final setData = _setDataMap[setId] as Map; + final foundCoin = (setData["coins"] as List).firstWhere( + (e) => e[1] == mintTag, + orElse: () => [], + ); if (foundCoin.length == 4) { lastFoundIndex = currentIndex; - if (foundCoin[2] is int) { - final amount = foundCoin[2] as int; - final serialNumber = GetSerialNumber(amount, - Format.uint8listToString(mintKeyPair.privateKey!), currentIndex, - isTestnet: coin == Coin.firoTestNet); - String publicCoin = foundCoin[0] as String; - String txId = foundCoin[3] as String; - bool isUsed = usedSerialNumbersSet.contains(serialNumber); - final duplicateCoin = lelantusCoins.firstWhere((element) { - final coin = element.values.first; - return coin.txId == txId && - coin.index == currentIndex && - coin.anonymitySetId != setId; - }, orElse: () => {}); + + final String publicCoin = foundCoin[0] as String; + final String txId = foundCoin[3] as String; + + // this value will either be an int or a String + final dynamic thirdValue = foundCoin[2]; + + if (thirdValue is int) { + final int amount = thirdValue; + final String serialNumber = GetSerialNumber( + amount, + Format.uint8listToString(mintKeyPair.privateKey!), + currentIndex, + isTestnet: coin == Coin.firoTestNet, + ); + final bool isUsed = usedSerialNumbersSet.contains(serialNumber); + final duplicateCoin = lelantusCoins.firstWhere( + (element) { + final coin = element.values.first; + return coin.txId == txId && + coin.index == currentIndex && + coin.anonymitySetId != setId; + }, + orElse: () => {}, + ); if (duplicateCoin.isNotEmpty) { - //todo: check if print needed - // debugPrint("removing duplicate: $duplicateCoin"); + Logging.instance.log( + "Firo isolateRestore removing duplicate coin: $duplicateCoin", + level: LogLevel.Info, + ); lelantusCoins.remove(duplicateCoin); } lelantusCoins.add({ - publicCoin: LelantusCoin( + txId: LelantusCoin( currentIndex, amount, publicCoin, @@ -298,40 +280,56 @@ Future> isolateRestore( isUsed, ) }); - Logging.instance - .log("amount $amount used $isUsed", level: LogLevel.Info); - } else { - final keyPath = GetAesKeyPath(foundCoin[0] as String); - final aesKeyPair = getBip32NodeFromRoot(JMINT_INDEX, keyPath, root); + Logging.instance.log( + "amount $amount used $isUsed", + level: LogLevel.Info, + ); + } else if (thirdValue is String) { + final int keyPath = GetAesKeyPath(publicCoin); + final String derivePath = constructDerivePath( + networkWIF: network.wif, + chain: JMINT_INDEX, + index: keyPath, + ); + final aesKeyPair = await Bip32Utils.getBip32NodeFromRoot( + root, + derivePath, + ); + if (aesKeyPair.privateKey != null) { - final aesPrivateKey = - Format.uint8listToString(aesKeyPair.privateKey!); - final amount = decryptMintAmount( + final String aesPrivateKey = Format.uint8listToString( + aesKeyPair.privateKey!, + ); + final int amount = decryptMintAmount( aesPrivateKey, - foundCoin[2] as String, + thirdValue, ); - final serialNumber = GetSerialNumber( - amount, - Format.uint8listToString(mintKeyPair.privateKey!), - currentIndex, - isTestnet: coin == Coin.firoTestNet); - String publicCoin = foundCoin[0] as String; - String txId = foundCoin[3] as String; + final String serialNumber = GetSerialNumber( + amount, + Format.uint8listToString(mintKeyPair.privateKey!), + currentIndex, + isTestnet: coin == Coin.firoTestNet, + ); bool isUsed = usedSerialNumbersSet.contains(serialNumber); - final duplicateCoin = lelantusCoins.firstWhere((element) { - final coin = element.values.first; - return coin.txId == txId && - coin.index == currentIndex && - coin.anonymitySetId != setId; - }, orElse: () => {}); + final duplicateCoin = lelantusCoins.firstWhere( + (element) { + final coin = element.values.first; + return coin.txId == txId && + coin.index == currentIndex && + coin.anonymitySetId != setId; + }, + orElse: () => {}, + ); if (duplicateCoin.isNotEmpty) { - //todo: check if print needed - // debugPrint("removing duplicate: $duplicateCoin"); + Logging.instance.log( + "Firo isolateRestore removing duplicate coin: $duplicateCoin", + level: LogLevel.Info, + ); lelantusCoins.remove(duplicateCoin); } lelantusCoins.add({ - '${foundCoin[3]!}': LelantusCoin( + txId: LelantusCoin( currentIndex, amount, publicCoin, @@ -342,9 +340,24 @@ Future> isolateRestore( }); jindexes.add(currentIndex); - spendTxIds.add(foundCoin[3] as String); + spendTxIds.add(txId); + } else { + Logging.instance.log( + "AES keypair derivation issue for derive path: $derivePath", + level: LogLevel.Warning, + ); } + } else { + Logging.instance.log( + "Unexpected coin found: $foundCoin", + level: LogLevel.Warning, + ); } + } else { + Logging.instance.log( + "Coin not found in data with the mint tag: $mintTag", + level: LogLevel.Warning, + ); } } @@ -369,8 +382,9 @@ Future> isolateRestore( } Future> staticProcessRestore( - models.TransactionData data, + List txns, Map result, + int currentHeight, ) async { List? _l = result['_lelantus_coins'] as List?; final List> lelantusCoins = []; @@ -379,19 +393,30 @@ Future> staticProcessRestore( } // Edit the receive transactions with the mint fees. - Map editedTransactions = - {}; + Map editedTransactions = + {}; for (var item in lelantusCoins) { item.forEach((key, value) { String txid = value.txId; - var tx = data.findTransaction(txid); + isar_models.Transaction? tx; + try { + tx = txns.firstWhere((e) => e.txid == txid); + } catch (_) { + tx = null; + } + if (tx == null) { // This is a jmint. return; } - List inputs = []; + List inputs = []; for (var element in tx.inputs) { - var input = data.findTransaction(element.txid); + isar_models.Transaction? input; + try { + input = txns.firstWhere((e) => e.txid == element.txid); + } catch (_) { + input = null; + } if (input != null) { inputs.add(input); } @@ -401,35 +426,39 @@ Future> staticProcessRestore( return; } - int mintfee = tx.fees; - int sharedfee = mintfee ~/ inputs.length; + int mintFee = tx.fee; + int sharedFee = mintFee ~/ inputs.length; for (var element in inputs) { - editedTransactions[element.txid] = models.Transaction( + editedTransactions[element.txid] = isar_models.Transaction( + walletId: element.walletId, txid: element.txid, - confirmedStatus: element.confirmedStatus, timestamp: element.timestamp, - txType: element.txType, + type: element.type, + subType: isar_models.TransactionSubType.mint, amount: element.amount, - aliens: element.aliens, - worthNow: element.worthNow, - worthAtBlockTimestamp: element.worthAtBlockTimestamp, - fees: sharedfee, - inputSize: element.inputSize, - outputSize: element.outputSize, + amountString: Amount( + rawValue: BigInt.from(element.amount), + fractionDigits: Coin.firo.decimals, + ).toJsonString(), + fee: sharedFee, + height: element.height, + isCancelled: false, + isLelantus: true, + slateId: null, + otherData: txid, + nonce: null, inputs: element.inputs, outputs: element.outputs, - address: element.address, - height: element.height, - confirmations: element.confirmations, - subType: "mint", - otherData: txid, - ); + )..address.value = element.address.value; } }); } // Logging.instance.log(editedTransactions, addToDebugMessagesDB: false); - Map transactionMap = data.getAllTransactions(); + Map transactionMap = {}; + for (final e in txns) { + transactionMap[e.txid] = e; + } // Logging.instance.log(transactionMap, addToDebugMessagesDB: false); editedTransactions.forEach((key, value) { @@ -438,7 +467,8 @@ Future> staticProcessRestore( transactionMap.removeWhere((key, value) => lelantusCoins.any((element) => element.containsKey(key)) || - (value.height == -1 && !value.confirmedStatus)); + ((value.height == -1 || value.height == null) && + !value.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS))); result['newTxMap'] = transactionMap; return result; @@ -481,14 +511,13 @@ Future isolateCreateJoinSplitTransaction( String address, bool subtractFeeFromAmount, String mnemonic, + String mnemonicPassphrase, int index, - Decimal price, List lelantusEntries, int locktime, Coin coin, NetworkType _network, List> anonymitySetsArg, - String locale, ) async { final estimateJoinSplitFee = await isolateEstimateJoinSplitFee( spendAmount, subtractFeeFromAmount, lelantusEntries, coin); @@ -513,8 +542,17 @@ Future isolateCreateJoinSplitTransaction( 4294967295, Uint8List(0), ); - - final jmintKeyPair = getBip32Node(MINT_INDEX, index, mnemonic, _network); + final derivePath = constructDerivePath( + networkWIF: _network.wif, + chain: MINT_INDEX, + index: index, + ); + final jmintKeyPair = await Bip32Utils.getBip32Node( + mnemonic, + mnemonicPassphrase, + _network, + derivePath, + ); final String jmintprivatekey = Format.uint8listToString(jmintKeyPair.privateKey!); @@ -522,7 +560,17 @@ Future isolateCreateJoinSplitTransaction( final keyPath = getMintKeyPath(changeToMint, jmintprivatekey, index, isTestnet: coin == Coin.firoTestNet); - final aesKeyPair = getBip32Node(JMINT_INDEX, keyPath, mnemonic, _network); + final _derivePath = constructDerivePath( + networkWIF: _network.wif, + chain: JMINT_INDEX, + index: keyPath, + ); + final aesKeyPair = await Bip32Utils.getBip32Node( + mnemonic, + mnemonicPassphrase, + _network, + _derivePath, + ); final aesPrivateKey = Format.uint8listToString(aesKeyPair.privateKey!); final jmintData = createJMintScript( @@ -618,11 +666,20 @@ Future isolateCreateJoinSplitTransaction( final txId = extTx.getId(); Logging.instance.log("txid $txId", level: LogLevel.Info); Logging.instance.log("txHex: $txHex", level: LogLevel.Info); + + final amountAmount = Amount( + rawValue: BigInt.from(amount), + fractionDigits: coin.decimals, + ); + return { "txid": txId, "txHex": txHex, "value": amount, - "fees": Format.satoshisToAmount(fee, coin: coin).toDouble(), + "fees": Amount( + rawValue: BigInt.from(fee), + fractionDigits: coin.decimals, + ).decimal.toDouble(), "fee": fee, "vSize": extTx.virtualSize(), "jmintValue": changeToMint, @@ -631,14 +688,8 @@ Future isolateCreateJoinSplitTransaction( "height": locktime, "txType": "Sent", "confirmed_status": false, - "amount": Format.satoshisToAmount(amount, coin: coin).toDouble(), - "recipientAmt": amount, - "worthNow": Format.localizedStringAsFixed( - value: ((Decimal.fromInt(amount) * price) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2), - decimalPlaces: 2, - locale: locale), + "amount": amountAmount.decimal.toDouble(), + "recipientAmt": amountAmount, "address": address, "timestamp": DateTime.now().millisecondsSinceEpoch ~/ 1000, "subType": "join", @@ -657,29 +708,15 @@ Future getBlockHead(ElectrumX client) async { } // end of isolates -bip32.BIP32 getBip32Node( - int chain, int index, String mnemonic, NetworkType network) { - final root = getBip32Root(mnemonic, network); - - final node = getBip32NodeFromRoot(chain, index, root); - return node; -} - -/// wrapper for compute() -bip32.BIP32 getBip32NodeWrapper( - Tuple4 args, -) { - return getBip32Node( - args.item1, - args.item2, - args.item3, - args.item4, - ); -} - -bip32.BIP32 getBip32NodeFromRoot(int chain, int index, bip32.BIP32 root) { +String constructDerivePath({ + // required DerivePathType derivePathType, + required int networkWIF, + int account = 0, + required int chain, + required int index, +}) { String coinType; - switch (root.network.wif) { + switch (networkWIF) { case 0xd2: // firo mainnet wif coinType = "136"; // firo mainnet break; @@ -687,41 +724,19 @@ bip32.BIP32 getBip32NodeFromRoot(int chain, int index, bip32.BIP32 root) { coinType = "1"; // firo testnet break; default: - throw Exception("Invalid Bitcoin network type used!"); + throw Exception("Invalid Firo network wif used!"); } - final node = root.derivePath("m/44'/$coinType'/0'/$chain/$index"); - return node; -} + int purpose; + // switch (derivePathType) { + // case DerivePathType.bip44: + purpose = 44; + // break; + // default: + // throw Exception("DerivePathType $derivePathType not supported"); + // } -/// wrapper for compute() -bip32.BIP32 getBip32NodeFromRootWrapper( - Tuple3 args, -) { - return getBip32NodeFromRoot( - args.item1, - args.item2, - args.item3, - ); -} - -bip32.BIP32 getBip32Root(String mnemonic, NetworkType network) { - final seed = bip39.mnemonicToSeed(mnemonic); - final firoNetworkType = bip32.NetworkType( - wif: network.wif, - bip32: bip32.Bip32Type( - public: network.bip32.public, - private: network.bip32.private, - ), - ); - - final root = bip32.BIP32.fromSeed(seed, firoNetworkType); - return root; -} - -/// wrapper for compute() -bip32.BIP32 getBip32RootWrapper(Tuple2 args) { - return getBip32Root(args.item1, args.item2); + return "m/$purpose'/$coinType'/$account'/$chain/$index"; } Future _getMintScriptWrapper( @@ -736,14 +751,47 @@ Future _setTestnetWrapper(bool isTestnet) async { } /// Handles a single instance of a firo wallet -class FiroWallet extends CoinServiceAPI { +class FiroWallet extends CoinServiceAPI + with WalletCache, WalletDB, FiroHive + implements XPubAble { + // Constructor + FiroWallet({ + required String walletId, + required String walletName, + required Coin coin, + required ElectrumX client, + required CachedElectrumX cachedClient, + required TransactionNotificationTracker tracker, + required SecureStorageInterface secureStore, + MainDB? mockableOverride, + }) { + txTracker = tracker; + _walletId = walletId; + _walletName = walletName; + _coin = coin; + _electrumXClient = client; + _cachedElectrumXClient = cachedClient; + _secureStore = secureStore; + initCache(walletId, coin); + initFiroHive(walletId); + initWalletDB(mockableOverride: mockableOverride); + + Logging.instance.log("$walletName isolates length: ${isolates.length}", + level: LogLevel.Info); + // investigate possible issues killing shared isolates between multiple firo instances + for (final isolate in isolates.values) { + isolate.kill(priority: Isolate.immediate); + } + isolates.clear(); + } + static const integrationTestFlag = bool.fromEnvironment("IS_INTEGRATION_TEST"); final _prefs = Prefs.instance; Timer? timer; - late Coin _coin; + late final Coin _coin; bool _shouldAutoSync = false; @@ -778,190 +826,125 @@ class FiroWallet extends CoinServiceAPI { @override set isFavorite(bool markFavorite) { - DB.instance.put( - boxName: walletId, key: "isFavorite", value: markFavorite); + _isFavorite = markFavorite; + updateCachedIsFavorite(markFavorite); } @override - bool get isFavorite { - try { - return DB.instance.get(boxName: walletId, key: "isFavorite") - as bool; - } catch (e, s) { - Logging.instance.log( - "isFavorite fetch failed (returning false by default): $e\n$s", - level: LogLevel.Error); - return false; - } - } + bool get isFavorite => _isFavorite ??= getCachedIsFavorite(); + + bool? _isFavorite; @override Coin get coin => _coin; - // @override - // String get coinName => - // networkType == BasicNetworkType.main ? "Firo" : "tFiro"; - // - // @override - // String get coinTicker => - // networkType == BasicNetworkType.main ? "FIRO" : "tFIRO"; - @override Future> get mnemonic => _getMnemonicList(); - // index 0 and 1 for the funds available to spend. - // index 2 and 3 for all the funds in the wallet (including the undependable ones) @override - Future get availableBalance async { - final balances = await this.balances; - return balances[0]; - } - - // index 0 and 1 for the funds available to spend. - // index 2 and 3 for all the funds in the wallet (including the undependable ones) - @override - Future get pendingBalance async { - final balances = await this.balances; - return balances[2] - balances[0]; - } - - // index 0 and 1 for the funds available to spend. - // index 2 and 3 for all the funds in the wallet (including the undependable ones) - @override - Future get totalBalance async { - if (!isActive) { - final totalBalance = DB.instance - .get(boxName: walletId, key: 'totalBalance') as String?; - if (totalBalance == null) { - final balances = await this.balances; - return balances[2]; - } else { - return Decimal.parse(totalBalance); - // the following caused a crash as it seems totalBalance here - // is a string. Gotta love dynamics - // return Format.satoshisToAmount(totalBalance); - } - } - final balances = await this.balances; - return balances[2]; - } - - /// return spendable balance minus the maximum tx fee - @override - Future get balanceMinusMaxFee async { - final balances = await this.balances; - final maxFee = await this.maxFee; - return balances[0] - Format.satoshisToAmount(maxFee, coin: coin); - } + Future get mnemonicString => + _secureStore.read(key: '${_walletId}_mnemonic'); @override - Future get transactionData => lelantusTransactionData; + Future get mnemonicPassphrase => _secureStore.read( + key: '${_walletId}_mnemonicPassphrase', + ); @override bool validateAddress(String address) { return Address.validateAddress(address, _network); } - /// Holds final balances, all utxos under control - Future? _utxoData; - Future get utxoData => _utxoData ??= _fetchUtxoData(); - - @override - Future> get unspentOutputs async => - (await utxoData).unspentOutputArray; - /// Holds wallet transaction data - Future? _transactionData; - Future get _txnData => - _transactionData ??= _fetchTransactionData(); + Future> get _txnData => db + .getTransactions(walletId) + .filter() + .isLelantusIsNull() + .or() + .isLelantusEqualTo(false) + .findAll(); - models.TransactionData? cachedTxData; + // _transactionData ??= _refreshTransactions(); + + // models.TransactionData? cachedTxData; // hack to add tx to txData before refresh completes // required based on current app architecture where we don't properly store // transactions locally in a good way @override Future updateSentCachedTxData(Map txData) async { - final currentPrice = await firoPrice; - final locale = Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; - final String worthNow = Format.localizedStringAsFixed( - value: - ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2), - decimalPlaces: 2, - locale: locale!); - - final tx = models.Transaction( + final transaction = isar_models.Transaction( + walletId: walletId, txid: txData["txid"] as String, - confirmedStatus: false, timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, - txType: "Sent", - amount: txData["recipientAmt"] as int, - worthNow: worthNow, - worthAtBlockTimestamp: worthNow, - fees: txData["fee"] as int, - inputSize: 0, - outputSize: 0, + type: isar_models.TransactionType.outgoing, + subType: isar_models.TransactionSubType.none, + // precision may be lost here hence the following amountString + amount: (txData["recipientAmt"] as Amount).raw.toInt(), + amountString: (txData["recipientAmt"] as Amount).toJsonString(), + fee: txData["fee"] as int, + height: null, + isCancelled: false, + isLelantus: false, + otherData: null, + slateId: null, + nonce: null, inputs: [], outputs: [], - address: txData["address"] as String, - height: -1, - confirmations: 0, ); - if (cachedTxData == null) { - final data = await _fetchTransactionData(); - _transactionData = Future(() => data); - } + final address = txData["address"] is String + ? await db.getAddress(walletId, txData["address"] as String) + : null; - final transactions = cachedTxData!.getAllTransactions(); - transactions[tx.txid] = tx; - cachedTxData = models.TransactionData.fromMap(transactions); - _transactionData = Future(() => cachedTxData!); + await db.addNewTransactionData( + [ + Tuple2(transaction, address), + ], + walletId, + ); } - /// Holds wallet lelantus transaction data - Future? _lelantusTransactionData; - Future get lelantusTransactionData => - _lelantusTransactionData ??= _getLelantusTransactionData(); - /// Holds the max fee that can be sent Future? _maxFee; + @override Future get maxFee => _maxFee ??= _fetchMaxFee(); - /// Holds the current balance data - Future>? _balances; - Future> get balances => _balances ??= _getFullBalance(); - - /// Holds all outputs for wallet, used for displaying utxos in app security view - List _outputsList = []; - - Future get firoPrice async { - final data = - await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - if (coin == Coin.firoTestNet) { - return data[Coin.firo]!.item1; - } - return data[coin]!.item1; - } - - // currently isn't used but required due to abstract parent class Future? _feeObject; + @override Future get fees => _feeObject ??= _getFees(); - /// Holds updated receiving address - Future? _currentReceivingAddress; @override - Future get currentReceivingAddress => - _currentReceivingAddress ??= _getCurrentAddressForChain(0); + Future get currentReceivingAddress async => + (await _currentReceivingAddress).value; - // @override - // Future get currentLegacyReceivingAddress => null; + Future get _currentReceivingAddress async => + (await db + .getAddresses(walletId) + .filter() + .typeEqualTo(isar_models.AddressType.p2pkh) + .subTypeEqualTo(isar_models.AddressSubType.receiving) + .sortByDerivationIndexDesc() + .findFirst()) ?? + await _generateAddressForChain(0, 0); + + Future get currentChangeAddress async => + (await _currentChangeAddress).value; + + Future get _currentChangeAddress async => + (await db + .getAddresses(walletId) + .filter() + .typeEqualTo(isar_models.AddressType.p2pkh) + .subTypeEqualTo(isar_models.AddressSubType.change) + .sortByDerivationIndexDesc() + .findFirst()) ?? + await _generateAddressForChain(1, 0); late String _walletName; + @override String get walletName => _walletName; @@ -970,15 +953,11 @@ class FiroWallet extends CoinServiceAPI { set walletName(String newName) => _walletName = newName; /// unique wallet id - late String _walletId; + late final String _walletId; + @override String get walletId => _walletId; - Future>? _allOwnAddresses; - @override - Future> get allOwnAddresses => - _allOwnAddresses ??= _fetchAllOwnAddresses(); - @override Future testNetworkConnection() async { try { @@ -1028,7 +1007,7 @@ class FiroWallet extends CoinServiceAPI { Future> prepareSendPublic({ required String address, - required int satoshiAmount, + required Amount amount, Map? args, }) async { try { @@ -1057,14 +1036,17 @@ class FiroWallet extends CoinServiceAPI { // check for send all bool isSendAll = false; - final balance = Format.decimalAmountToSatoshis( - await availablePublicBalance(), coin); - if (satoshiAmount == balance) { + final balance = availablePublicBalance(); + if (amount == balance) { isSendAll = true; } - final txData = - await coinSelection(satoshiAmount, rate, address, isSendAll); + final txData = await coinSelection( + amount.raw.toInt(), + rate, + address, + isSendAll, + ); Logging.instance.log("prepare send: $txData", level: LogLevel.Info); try { @@ -1137,20 +1119,22 @@ class FiroWallet extends CoinServiceAPI { @override Future> prepareSend({ required String address, - required int satoshiAmount, + required Amount amount, Map? args, }) async { try { // check for send all bool isSendAll = false; - final balance = - Format.decimalAmountToSatoshis(await availablePrivateBalance(), coin); - if (satoshiAmount == balance) { + final balance = availablePrivateBalance(); + if (amount == balance) { // print("is send all"); isSendAll = true; } - dynamic txHexOrError = - await _createJoinSplitTransaction(satoshiAmount, address, isSendAll); + dynamic txHexOrError = await _createJoinSplitTransaction( + amount.raw.toInt(), + address, + isSendAll, + ); Logging.instance.log("txHexOrError $txHexOrError", level: LogLevel.Error); if (txHexOrError is int) { // Here, we assume that transaction crafting returned an error @@ -1190,12 +1174,14 @@ class FiroWallet extends CoinServiceAPI { // temporarily update apdate available balance until a full refresh is done // TODO: something here causes an exception to be thrown giving user false info that the tx failed - Decimal sendTotal = - Format.satoshisToAmount(txData["value"] as int, coin: coin); - sendTotal += Decimal.parse(txData["fees"].toString()); - final bals = await balances; - bals[0] -= sendTotal; - _balances = Future(() => bals); + // Decimal sendTotal = + // Format.satoshisToAmount(txData["value"] as int, coin: coin); + // sendTotal += Decimal.parse(txData["fees"].toString()); + + // TODO: is this needed? + // final bals = await balances; + // bals[0] -= sendTotal; + // _balances = Future(() => bals); return txid; } catch (e, s) { @@ -1212,105 +1198,74 @@ class FiroWallet extends CoinServiceAPI { } } - /// returns txid on successful send - /// - /// can throw - @override - Future send({ - required String toAddress, - required int amount, - Map args = const {}, - }) async { - try { - dynamic txHexOrError = - await _createJoinSplitTransaction(amount, toAddress, false); - Logging.instance.log("txHexOrError $txHexOrError", level: LogLevel.Error); - if (txHexOrError is int) { - // Here, we assume that transaction crafting returned an error - switch (txHexOrError) { - case 1: - throw Exception("Insufficient balance!"); - default: - throw Exception("Error Creating Transaction!"); - } - } else { - if (await _submitLelantusToNetwork( - txHexOrError as Map)) { - final txid = txHexOrError["txid"] as String; - - // temporarily update apdate available balance until a full refresh is done - Decimal sendTotal = - Format.satoshisToAmount(txHexOrError["value"] as int, coin: coin); - sendTotal += Decimal.parse(txHexOrError["fees"].toString()); - final bals = await balances; - bals[0] -= sendTotal; - _balances = Future(() => bals); - - return txid; - } else { - //TODO provide more info - throw Exception("Transaction failed."); - } - } - } catch (e, s) { - Logging.instance.log("Exception rethrown in firo send(): $e\n$s", - level: LogLevel.Error); - rethrow; - } - } + // /// returns txid on successful send + // /// + // /// can throw + // @override + // Future send({ + // required String toAddress, + // required int amount, + // Map args = const {}, + // }) async { + // try { + // dynamic txHexOrError = + // await _createJoinSplitTransaction(amount, toAddress, false); + // Logging.instance.log("txHexOrError $txHexOrError", level: LogLevel.Error); + // if (txHexOrError is int) { + // // Here, we assume that transaction crafting returned an error + // switch (txHexOrError) { + // case 1: + // throw Exception("Insufficient balance!"); + // default: + // throw Exception("Error Creating Transaction!"); + // } + // } else { + // if (await _submitLelantusToNetwork( + // txHexOrError as Map)) { + // final txid = txHexOrError["txid"] as String; + // + // // temporarily update apdate available balance until a full refresh is done + // Decimal sendTotal = + // Format.satoshisToAmount(txHexOrError["value"] as int, coin: coin); + // sendTotal += Decimal.parse(txHexOrError["fees"].toString()); + // final bals = await balances; + // bals[0] -= sendTotal; + // _balances = Future(() => bals); + // + // return txid; + // } else { + // //TODO provide more info + // throw Exception("Transaction failed."); + // } + // } + // } catch (e, s) { + // Logging.instance.log("Exception rethrown in firo send(): $e\n$s", + // level: LogLevel.Error); + // rethrow; + // } + // } Future> _getMnemonicList() async { - final mnemonicString = - await _secureStore.read(key: '${_walletId}_mnemonic'); - if (mnemonicString == null) { + final _mnemonicString = await mnemonicString; + if (_mnemonicString == null) { return []; } - final List data = mnemonicString.split(' '); + final List data = _mnemonicString.split(' '); return data; } late ElectrumX _electrumXClient; + ElectrumX get electrumXClient => _electrumXClient; late CachedElectrumX _cachedElectrumXClient; + CachedElectrumX get cachedElectrumXClient => _cachedElectrumXClient; late SecureStorageInterface _secureStore; - late PriceAPI _priceAPI; - late TransactionNotificationTracker txTracker; - // Constructor - FiroWallet({ - required String walletId, - required String walletName, - required Coin coin, - required ElectrumX client, - required CachedElectrumX cachedClient, - required TransactionNotificationTracker tracker, - PriceAPI? priceAPI, - required SecureStorageInterface secureStore, - }) { - txTracker = tracker; - _walletId = walletId; - _walletName = walletName; - _coin = coin; - _electrumXClient = client; - _cachedElectrumXClient = cachedClient; - - _priceAPI = priceAPI ?? PriceAPI(Client()); - _secureStore = secureStore; - - Logging.instance.log("$walletName isolates length: ${isolates.length}", - level: LogLevel.Info); - // investigate possible issues killing shared isolates between multiple firo instances - for (final isolate in isolates.values) { - isolate.kill(priority: Isolate.immediate); - } - isolates.clear(); - } - int estimateTxFee({required int vSize, required int feeRatePerKB}) { return vSize * (feeRatePerKB / 1000).ceil(); } @@ -1325,26 +1280,28 @@ class FiroWallet extends CoinServiceAPI { String _recipientAddress, bool isSendAll, { int additionalOutputs = 0, - List? utxos, + List? utxos, }) async { Logging.instance .log("Starting coinSelection ----------", level: LogLevel.Info); - final List availableOutputs = utxos ?? _outputsList; - final List spendableOutputs = []; + final List availableOutputs = utxos ?? await this.utxos; + final currentChainHeight = await chainHeight; + final List spendableOutputs = []; int spendableSatoshiValue = 0; // Build list of spendable outputs and totaling their satoshi amount for (var i = 0; i < availableOutputs.length; i++) { - if (availableOutputs[i].blocked == false && - availableOutputs[i].status.confirmed == true) { + if (availableOutputs[i].isBlocked == false && + availableOutputs[i] + .isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) == + true) { spendableOutputs.add(availableOutputs[i]); spendableSatoshiValue += availableOutputs[i].value; } } // sort spendable by age (oldest first) - spendableOutputs.sort( - (a, b) => b.status.confirmations.compareTo(a.status.confirmations)); + spendableOutputs.sort((a, b) => b.blockTime!.compareTo(a.blockTime!)); Logging.instance.log("spendableOutputs.length: ${spendableOutputs.length}", level: LogLevel.Info); @@ -1371,7 +1328,7 @@ class FiroWallet extends CoinServiceAPI { // Possible situation right here int satoshisBeingUsed = 0; int inputsBeingConsumed = 0; - List utxoObjectsToUse = []; + List utxoObjectsToUse = []; for (var i = 0; satoshisBeingUsed <= satoshiAmountToSend && i < spendableOutputs.length; @@ -1407,7 +1364,6 @@ class FiroWallet extends CoinServiceAPI { .log("Attempting to send all $coin", level: LogLevel.Info); final int vSizeForOneOutput = (await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: [_recipientAddress], satoshiAmounts: [satoshisBeingUsed - 1], @@ -1423,7 +1379,6 @@ class FiroWallet extends CoinServiceAPI { final int amount = satoshiAmountToSend - feeForOneOutput; dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: [amount], @@ -1431,7 +1386,10 @@ class FiroWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": amount, + "recipientAmt": Amount( + rawValue: BigInt.from(amount), + fractionDigits: coin.decimals, + ), "fee": feeForOneOutput, "vSize": txn["vSize"], }; @@ -1439,13 +1397,11 @@ class FiroWallet extends CoinServiceAPI { } final int vSizeForOneOutput = (await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: [_recipientAddress], satoshiAmounts: [satoshisBeingUsed - 1], ))["vSize"] as int; final int vSizeForTwoOutPuts = (await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: [ _recipientAddress, @@ -1524,7 +1480,6 @@ class FiroWallet extends CoinServiceAPI { Logging.instance .log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info); dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -1552,7 +1507,6 @@ class FiroWallet extends CoinServiceAPI { Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs', level: LogLevel.Info); txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -1562,7 +1516,10 @@ class FiroWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": feeBeingPaid, "vSize": txn["vSize"], }; @@ -1581,7 +1538,6 @@ class FiroWallet extends CoinServiceAPI { Logging.instance .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -1589,7 +1545,10 @@ class FiroWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": satoshisBeingUsed - satoshiAmountToSend, "vSize": txn["vSize"], }; @@ -1610,7 +1569,6 @@ class FiroWallet extends CoinServiceAPI { Logging.instance .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -1618,7 +1576,10 @@ class FiroWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": satoshisBeingUsed - satoshiAmountToSend, "vSize": txn["vSize"], }; @@ -1639,7 +1600,6 @@ class FiroWallet extends CoinServiceAPI { Logging.instance .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -1647,7 +1607,10 @@ class FiroWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": feeForOneOutput, "vSize": txn["vSize"], }; @@ -1669,119 +1632,142 @@ class FiroWallet extends CoinServiceAPI { } } - Future> fetchBuildTxData( - List utxosToUse, + Future> fetchBuildTxData( + List utxosToUse, ) async { // return data - Map results = {}; - Map> addressTxid = {}; - - // addresses to check - List addresses = []; + List signingData = []; try { // Populating the addresses to check for (var i = 0; i < utxosToUse.length; i++) { - final txid = utxosToUse[i].txid; - final tx = await _cachedElectrumXClient.getTransaction( - txHash: txid, - coin: coin, - ); - - for (final output in tx["vout"] as List) { - final n = output["n"]; - if (n != null && n == utxosToUse[i].vout) { - final address = output["scriptPubKey"]["addresses"][0] as String; - - if (!addressTxid.containsKey(address)) { - addressTxid[address] = []; + if (utxosToUse[i].address == null) { + final txid = utxosToUse[i].txid; + final tx = await _cachedElectrumXClient.getTransaction( + txHash: txid, + coin: coin, + ); + for (final output in tx["vout"] as List) { + final n = output["n"]; + if (n != null && n == utxosToUse[i].vout) { + utxosToUse[i] = utxosToUse[i].copyWith( + address: output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]["address"] as String, + ); } - (addressTxid[address] as List).add(txid); - - addresses.add(address); } } + + signingData.add( + SigningData( + derivePathType: DerivePathType.bip44, + utxo: utxosToUse[i], + ), + ); } - // p2pkh / bip44 - final addressesLength = addresses.length; - if (addressesLength > 0) { - final receiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivations"); - final receiveDerivations = Map.from( - jsonDecode(receiveDerivationsString ?? "{}") as Map); + Map> receiveDerivations = {}; + Map> changeDerivations = {}; - final changeDerivationsString = - await _secureStore.read(key: "${walletId}_changeDerivations"); - final changeDerivations = Map.from( - jsonDecode(changeDerivationsString ?? "{}") as Map); + for (final sd in signingData) { + String? pubKey; + String? wif; - for (int i = 0; i < addressesLength; i++) { - // receives + final address = await db.getAddress(walletId, sd.utxo.address!); + if (address?.derivationPath != null) { + final node = await Bip32Utils.getBip32Node( + (await mnemonicString)!, + (await mnemonicPassphrase)!, + _network, + address!.derivationPath!.value, + ); + + wif = node.toWIF(); + pubKey = Format.uint8listToString(node.publicKey); + } + if (wif == null || pubKey == null) { + // fetch receiving derivations if null + receiveDerivations[sd.derivePathType] ??= Map.from( + jsonDecode((await _secureStore.read( + key: "${walletId}_receiveDerivations", + )) ?? + "{}") as Map, + ); dynamic receiveDerivation; - - for (int j = 0; j < receiveDerivations.length; j++) { - if (receiveDerivations["$j"]["address"] == addresses[i]) { - receiveDerivation = receiveDerivations["$j"]; + for (int j = 0; + j < receiveDerivations[sd.derivePathType]!.length && + receiveDerivation == null; + j++) { + if (receiveDerivations[sd.derivePathType]!["$j"]["address"] == + sd.utxo.address!) { + receiveDerivation = receiveDerivations[sd.derivePathType]!["$j"]; } } - // receiveDerivation = receiveDerivations[addresses[i]]; - // if a match exists it will not be null if (receiveDerivation != null) { - final data = P2PKH( - data: PaymentData( - pubkey: Format.stringToUint8List( - receiveDerivation["publicKey"] as String)), - network: _network, - ).data; - - for (String tx in addressTxid[addresses[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - receiveDerivation["wif"] as String, - network: _network, - ), - }; - } + pubKey = receiveDerivation["publicKey"] as String; + wif = receiveDerivation["wif"] as String; } else { - // if its not a receive, check change + // fetch change derivations if null + changeDerivations[sd.derivePathType] ??= Map.from( + jsonDecode((await _secureStore.read( + key: "${walletId}_changeDerivations", + )) ?? + "{}") as Map, + ); dynamic changeDerivation; - - for (int j = 0; j < changeDerivations.length; j++) { - if (changeDerivations["$j"]["address"] == addresses[i]) { - changeDerivation = changeDerivations["$j"]; + for (int j = 0; + j < changeDerivations[sd.derivePathType]!.length && + changeDerivation == null; + j++) { + if (changeDerivations[sd.derivePathType]!["$j"]["address"] == + sd.utxo.address!) { + changeDerivation = changeDerivations[sd.derivePathType]!["$j"]; } } - // final changeDerivation = changeDerivations[addresses[i]]; - // if a match exists it will not be null if (changeDerivation != null) { - final data = P2PKH( - data: PaymentData( - pubkey: Format.stringToUint8List( - changeDerivation["publicKey"] as String)), - network: _network, - ).data; - - for (String tx in addressTxid[addresses[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - changeDerivation["wif"] as String, - network: _network, - ), - }; - } + pubKey = changeDerivation["publicKey"] as String; + wif = changeDerivation["wif"] as String; } } } + + if (wif != null && pubKey != null) { + final PaymentData data; + final Uint8List? redeemScript; + + switch (sd.derivePathType) { + case DerivePathType.bip44: + data = P2PKH( + data: PaymentData( + pubkey: Format.stringToUint8List(pubKey), + ), + network: _network, + ).data; + redeemScript = null; + break; + + default: + throw Exception("DerivePathType unsupported"); + } + + final keyPair = ECPair.fromWIF( + wif, + network: _network, + ); + + sd.redeemScript = redeemScript; + sd.output = data.output; + sd.keyPair = keyPair; + } else { + throw Exception("key or wif not found for ${sd.utxo}"); + } } - return results; + return signingData; } catch (e, s) { Logging.instance .log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error); @@ -1791,8 +1777,7 @@ class FiroWallet extends CoinServiceAPI { /// Builds and signs a transaction Future> buildTransaction({ - required List utxosToUse, - required Map utxoSigningData, + required List utxoSigningData, required List recipients, required List satoshiAmounts, }) async { @@ -1803,10 +1788,14 @@ class FiroWallet extends CoinServiceAPI { txb.setVersion(1); // Add transaction inputs - for (var i = 0; i < utxosToUse.length; i++) { - final txid = utxosToUse[i].txid; - txb.addInput(txid, utxosToUse[i].vout, null, - utxoSigningData[txid]["output"] as Uint8List); + for (var i = 0; i < utxoSigningData.length; i++) { + final txid = utxoSigningData[i].utxo.txid; + txb.addInput( + txid, + utxoSigningData[i].utxo.vout, + null, + utxoSigningData[i].output!, + ); } // Add transaction output @@ -1816,13 +1805,12 @@ class FiroWallet extends CoinServiceAPI { try { // Sign the transaction accordingly - for (var i = 0; i < utxosToUse.length; i++) { - final txid = utxosToUse[i].txid; + for (var i = 0; i < utxoSigningData.length; i++) { txb.sign( vin: i, - keyPair: utxoSigningData[txid]["keyPair"] as ECPair, - witnessValue: utxosToUse[i].value, - redeemScript: utxoSigningData[txid]["redeemScript"] as Uint8List?, + keyPair: utxoSigningData[i].keyPair!, + witnessValue: utxoSigningData[i].utxo.value, + redeemScript: utxoSigningData[i].redeemScript, ); } } catch (e, s) { @@ -1873,7 +1861,7 @@ class FiroWallet extends CoinServiceAPI { Logging.instance .log("Generating new ${coin.prettyName} wallet.", level: LogLevel.Info); - if (DB.instance.get(boxName: walletId, key: "id") != null) { + if (getCachedId() != null) { throw Exception( "Attempted to initialize a new wallet using an existing wallet ID!"); } @@ -1888,32 +1876,24 @@ class FiroWallet extends CoinServiceAPI { } await Future.wait([ - DB.instance.put(boxName: walletId, key: "id", value: _walletId), - _getLelantusTransactionData().then((lelantusTxData) => - _lelantusTransactionData = Future(() => lelantusTxData)), - DB.instance - .put(boxName: walletId, key: "isFavorite", value: false), + updateCachedId(walletId), + updateCachedIsFavorite(false), ]); } @override Future initializeExisting() async { Logging.instance.log( - "Opening existing $_walletId ${coin.prettyName} wallet.", + "initializeExisting() $_walletId ${coin.prettyName} wallet.", level: LogLevel.Info); - if ((DB.instance.get(boxName: walletId, key: "id") as String?) == - null) { + if (getCachedId() == null) { throw Exception( "Attempted to initialize an existing wallet using an unknown wallet ID!"); } await _prefs.init(); - final data = - DB.instance.get(boxName: walletId, key: "latest_tx_model") - as models.TransactionData?; - if (data != null) { - _transactionData = Future(() => data); - } + // await checkChangeAddressForTransactions(); + // await checkReceivingAddressForTransactions(); } Future refreshIfThereIsNewData() async { @@ -1942,12 +1922,16 @@ class FiroWallet extends CoinServiceAPI { } } if (!needsRefresh) { - var allOwnAddresses = await this.allOwnAddresses; - List> allTxs = - await _fetchHistory(allOwnAddresses); - models.TransactionData txData = await _txnData; + final allOwnAddresses = await _fetchAllOwnAddresses(); + List> allTxs = await _fetchHistory( + allOwnAddresses.map((e) => e.value).toList(growable: false)); for (Map transaction in allTxs) { - if (txData.findTransaction(transaction['tx_hash'] as String) == + final txid = transaction['tx_hash'] as String; + if ((await db + .getTransactions(walletId) + .filter() + .txidMatches(txid) + .findFirst()) == null) { Logging.instance.log( " txid not found in address history already ${transaction['tx_hash']}", @@ -1966,71 +1950,92 @@ class FiroWallet extends CoinServiceAPI { } } - Future getAllTxsToWatch( - models.TransactionData txData, - models.TransactionData lTxData, - ) async { + Future getAllTxsToWatch() async { if (_hasCalledExit) return; Logging.instance.log("$walletName periodic", level: LogLevel.Info); - List unconfirmedTxnsToNotifyPending = []; - List unconfirmedTxnsToNotifyConfirmed = []; + List unconfirmedTxnsToNotifyPending = []; + List unconfirmedTxnsToNotifyConfirmed = []; - for (models.TransactionChunk chunk in txData.txChunks) { - for (models.Transaction tx in chunk.transactions) { - models.Transaction? lTx = lTxData.findTransaction(tx.txid); + final currentChainHeight = await chainHeight; - if (tx.confirmedStatus) { - if (txTracker.wasNotifiedPending(tx.txid) && - !txTracker.wasNotifiedConfirmed(tx.txid)) { - // get all transactions that were notified as pending but not as confirmed - unconfirmedTxnsToNotifyConfirmed.add(tx); - } - if (lTx != null && - (lTx.inputs.isEmpty || lTx.inputs[0].txid.isEmpty) && - lTx.confirmedStatus == false && - tx.txType == "Received") { - // If this is a received that is past 1 or more confirmations and has not been minted, - if (!txTracker.wasNotifiedPending(tx.txid)) { - unconfirmedTxnsToNotifyPending.add(tx); - } - } - } else { + final txTxns = await db + .getTransactions(walletId) + .filter() + .isLelantusIsNull() + .or() + .isLelantusEqualTo(false) + .findAll(); + final ltxTxns = await db + .getTransactions(walletId) + .filter() + .isLelantusEqualTo(true) + .findAll(); + + for (isar_models.Transaction tx in txTxns) { + isar_models.Transaction? lTx; + try { + lTx = ltxTxns.firstWhere((e) => e.txid == tx.txid); + } catch (_) { + lTx = null; + } + + if (tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { + if (txTracker.wasNotifiedPending(tx.txid) && + !txTracker.wasNotifiedConfirmed(tx.txid)) { + // get all transactions that were notified as pending but not as confirmed + unconfirmedTxnsToNotifyConfirmed.add(tx); + } + if (lTx != null && + (lTx.inputs.isEmpty || lTx.inputs.first.txid.isEmpty) && + lTx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) == + false && + tx.type == isar_models.TransactionType.incoming) { + // If this is a received that is past 1 or more confirmations and has not been minted, if (!txTracker.wasNotifiedPending(tx.txid)) { - // get all transactions that were not notified as pending yet unconfirmedTxnsToNotifyPending.add(tx); } } - } - } - - for (models.TransactionChunk chunk in txData.txChunks) { - for (models.Transaction tx in chunk.transactions) { - if (!tx.confirmedStatus && tx.inputs[0].txid.isNotEmpty) { - // Get all normal txs that are at 0 confirmations - unconfirmedTxnsToNotifyPending - .removeWhere((e) => e.txid == tx.inputs[0].txid); - Logging.instance.log("removed tx: ${tx.txid}", level: LogLevel.Info); + } else { + if (!txTracker.wasNotifiedPending(tx.txid)) { + // get all transactions that were not notified as pending yet + unconfirmedTxnsToNotifyPending.add(tx); } } } - for (models.TransactionChunk chunk in lTxData.txChunks) { - for (models.Transaction lTX in chunk.transactions) { - models.Transaction? tx = txData.findTransaction(lTX.txid); - if (tx == null) { - // if this is a ltx transaction that is unconfirmed and not represented in the normal transaction set. - if (!lTX.confirmedStatus) { - if (!txTracker.wasNotifiedPending(lTX.txid)) { - unconfirmedTxnsToNotifyPending.add(lTX); - } - } else { - if (txTracker.wasNotifiedPending(lTX.txid) && - !txTracker.wasNotifiedConfirmed(lTX.txid)) { - unconfirmedTxnsToNotifyConfirmed.add(lTX); - } + + for (isar_models.Transaction tx in txTxns) { + if (!tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) && + tx.inputs.first.txid.isNotEmpty) { + // Get all normal txs that are at 0 confirmations + unconfirmedTxnsToNotifyPending + .removeWhere((e) => e.txid == tx.inputs.first.txid); + Logging.instance.log("removed tx: ${tx.txid}", level: LogLevel.Info); + } + } + + for (isar_models.Transaction lTX in ltxTxns) { + isar_models.Transaction? tx; + try { + tx = ltxTxns.firstWhere((e) => e.txid == lTX.txid); + } catch (_) { + tx = null; + } + + if (tx == null) { + // if this is a ltx transaction that is unconfirmed and not represented in the normal transaction set. + if (!lTX.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { + if (!txTracker.wasNotifiedPending(lTX.txid)) { + unconfirmedTxnsToNotifyPending.add(lTX); + } + } else { + if (txTracker.wasNotifiedPending(lTX.txid) && + !txTracker.wasNotifiedConfirmed(lTX.txid)) { + unconfirmedTxnsToNotifyConfirmed.add(lTX); } } } } + Logging.instance.log( "unconfirmedTxnsToNotifyPending $unconfirmedTxnsToNotifyPending", level: LogLevel.Info); @@ -2039,40 +2044,43 @@ class FiroWallet extends CoinServiceAPI { level: LogLevel.Info); for (final tx in unconfirmedTxnsToNotifyPending) { - switch (tx.txType) { - case "Received": - unawaited( - NotificationApi.showNotification( + final confirmations = tx.getConfirmations(currentChainHeight); + + switch (tx.type) { + case isar_models.TransactionType.incoming: + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( title: "Incoming transaction", - body: walletName, walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS, - coinName: coin.name, + shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, txid: tx.txid, - confirmations: tx.confirmations, + confirmations: confirmations, requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, ), ); + await txTracker.addNotifiedPending(tx.txid); break; - case "Sent": - unawaited( - NotificationApi.showNotification( - title: - tx.subType == "mint" ? "Anonymizing" : "Outgoing transaction", - body: walletName, + case isar_models.TransactionType.outgoing: + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: tx.subType == isar_models.TransactionSubType.mint + ? "Anonymizing" + : "Outgoing transaction", walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS, - coinName: coin.name, + shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, txid: tx.txid, - confirmations: tx.confirmations, + confirmations: confirmations, requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, ), ); + await txTracker.addNotifiedPending(tx.txid); break; default: @@ -2081,31 +2089,36 @@ class FiroWallet extends CoinServiceAPI { } for (final tx in unconfirmedTxnsToNotifyConfirmed) { - if (tx.txType == "Received") { - unawaited( - NotificationApi.showNotification( + if (tx.type == isar_models.TransactionType.incoming) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( title: "Incoming transaction confirmed", - body: walletName, walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), shouldWatchForUpdates: false, - coinName: coin.name, + txid: tx.txid, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, ), ); + await txTracker.addNotifiedConfirmed(tx.txid); - } else if (tx.txType == "Sent" && tx.subType == "join") { - unawaited( - NotificationApi.showNotification( - title: tx.subType == "mint" + } else if (tx.type == isar_models.TransactionType.outgoing && + tx.subType == isar_models.TransactionSubType.join) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: tx.subType == + isar_models.TransactionSubType.mint // redundant check? ? "Anonymized" : "Outgoing transaction confirmed", - body: walletName, walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), shouldWatchForUpdates: false, - coinName: coin.name, + txid: tx.txid, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, ), ); await txTracker.addNotifiedConfirmed(tx.txid); @@ -2144,41 +2157,31 @@ class FiroWallet extends CoinServiceAPI { } // this should never fail as overwriting a mnemonic is big bad - assert((await _secureStore.read(key: '${_walletId}_mnemonic')) == null); + if ((await mnemonicString) != null || (await mnemonicPassphrase) != null) { + longMutex = false; + throw Exception("Attempted to overwrite mnemonic on initialize new!"); + } await _secureStore.write( key: '${_walletId}_mnemonic', value: bip39.generateMnemonic(strength: 256)); + await _secureStore.write( + key: '${_walletId}_mnemonicPassphrase', + value: "", + ); - // Set relevant indexes - await DB.instance - .put(boxName: walletId, key: 'receivingIndex', value: 0); - await DB.instance - .put(boxName: walletId, key: 'changeIndex', value: 0); - await DB.instance - .put(boxName: walletId, key: 'mintIndex', value: 0); - await DB.instance.put( - boxName: walletId, - key: 'blocked_tx_hashes', - value: [ - "0xdefault" - ]); // A list of transaction hashes to represent frozen utxos in wallet - // initialize address book entries - await DB.instance.put( - boxName: walletId, - key: 'addressBookEntries', - value: {}); - - await DB.instance - .put(boxName: walletId, key: 'jindex', value: []); + await firoUpdateJIndex([]); // Generate and add addresses to relevant arrays final initialReceivingAddress = await _generateAddressForChain(0, 0); final initialChangeAddress = await _generateAddressForChain(1, 0); - await addToAddressesArrayForChain(initialReceivingAddress, 0); - await addToAddressesArrayForChain(initialChangeAddress, 1); - _currentReceivingAddress = Future(() => initialReceivingAddress); + + await db.putAddresses([ + initialReceivingAddress, + initialChangeAddress, + ]); } bool refreshMutex = false; + @override bool get isRefreshing => refreshMutex; @@ -2205,33 +2208,20 @@ class FiroWallet extends CoinServiceAPI { GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.0, walletId)); - final receiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivations"); - if (receiveDerivationsString == null || - receiveDerivationsString == "{}") { - GlobalEventBus.instance - .fire(RefreshPercentChangedEvent(0.05, walletId)); - final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic'); - await fillAddresses(mnemonic!, - numberOfThreads: Platform.numberOfProcessors - isolates.length - 1); - } - await checkReceivingAddressForTransactions(); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.1, walletId)); - final newUtxoData = _fetchUtxoData(); + await _refreshUTXOs(); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId)); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.25, walletId)); - final newTxData = _fetchTransactionData(); + await _refreshTransactions(); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.35, walletId)); final feeObj = _getFees(); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.50, walletId)); - _utxoData = Future(() => newUtxoData); - _transactionData = Future(() => newTxData); _feeObject = Future(() => feeObj); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.60, walletId)); @@ -2243,17 +2233,13 @@ class FiroWallet extends CoinServiceAPI { await _refreshLelantusData(); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.80, walletId)); - // await autoMint(); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.90, walletId)); - var balance = await _getFullBalance(); - _balances = Future(() => balance); + await _refreshBalance(); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.95, walletId)); - var txData = (await _txnData); - var lTxData = (await lelantusTransactionData); - await getAllTxsToWatch(txData, lTxData); + await getAllTxsToWatch(); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(1.0, walletId)); @@ -2300,33 +2286,40 @@ class FiroWallet extends CoinServiceAPI { } Future _fetchMaxFee() async { - final balance = await availableBalance; - int spendAmount = (balance * Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); + final balance = availablePrivateBalance(); + int spendAmount = + (balance.decimal * Decimal.fromInt(Constants.satsPerCoin(coin))) + .toBigInt() + .toInt(); int fee = await estimateJoinSplitFee(spendAmount); return fee; } Future> _getLelantusEntry() async { - final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic'); + final _mnemonic = await mnemonicString; + final _mnemonicPassphrase = await mnemonicPassphrase; + if (_mnemonicPassphrase == null) { + Logging.instance.log( + "Exception in _getLelantusEntry: mnemonic passphrase null, possible migration issue; if using internal builds, delete wallet and restore from seed, if using a release build, please file bug report", + level: LogLevel.Error); + } + final List lelantusCoins = await _getUnspentCoins(); - final root = await compute( - getBip32RootWrapper, - Tuple2( - mnemonic!, - _network, - ), + + final root = await Bip32Utils.getBip32Root( + _mnemonic!, + _mnemonicPassphrase!, + _network, ); + final waitLelantusEntries = lelantusCoins.map((coin) async { - final keyPair = await compute( - getBip32NodeFromRootWrapper, - Tuple3( - MINT_INDEX, - coin.index, - root, - ), + final derivePath = constructDerivePath( + networkWIF: _network.wif, + chain: MINT_INDEX, + index: coin.index, ); + final keyPair = await Bip32Utils.getBip32NodeFromRoot(root, derivePath); + if (keyPair.privateKey == null) { Logging.instance.log("error bad key", level: LogLevel.Error); return DartLelantusEntry(1, 0, 0, 0, 0, ''); @@ -2346,8 +2339,7 @@ class FiroWallet extends CoinServiceAPI { } List> getLelantusCoinMap() { - final _l = DB.instance - .get(boxName: walletId, key: '_lelantus_coins') as List?; + final _l = firoGetLelantusCoins(); final List> lelantusCoins = []; for (var el in _l ?? []) { lelantusCoins.add({el.keys.first: el.values.first as LelantusCoin}); @@ -2361,10 +2353,8 @@ class FiroWallet extends CoinServiceAPI { lelantusCoins.removeWhere((element) => element.values.any((elementCoin) => elementCoin.value == 0)); } - final jindexes = - DB.instance.get(boxName: walletId, key: 'jindex') as List?; - final data = await _txnData; - final lelantusData = await lelantusTransactionData; + final jindexes = firoGetJIndex(); + List coins = []; List lelantusCoinsList = @@ -2372,27 +2362,31 @@ class FiroWallet extends CoinServiceAPI { previousValue.add(element.values.first); return previousValue; }); + + final currentChainHeight = await chainHeight; + for (int i = 0; i < lelantusCoinsList.length; i++) { // Logging.instance.log("lelantusCoinsList[$i]: ${lelantusCoinsList[i]}"); + final txid = lelantusCoinsList[i].txId; final txn = await cachedElectrumXClient.getTransaction( - txHash: lelantusCoinsList[i].txId, + txHash: txid, verbose: true, coin: coin, ); final confirmations = txn["confirmations"]; bool isUnconfirmed = confirmations is int && confirmations < 1; + + final tx = await db.getTransaction(walletId, txid); + if (!jindexes!.contains(lelantusCoinsList[i].index) && - data.findTransaction(lelantusCoinsList[i].txId) == null) { + tx != null && + tx.isLelantus == true && + !(tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS))) { isUnconfirmed = true; } - if ((data.findTransaction(lelantusCoinsList[i].txId) != null && - !data - .findTransaction(lelantusCoinsList[i].txId)! - .confirmedStatus) || - (lelantusData.findTransaction(lelantusCoinsList[i].txId) != null && - !lelantusData - .findTransaction(lelantusCoinsList[i].txId)! - .confirmedStatus)) { + + if (tx != null && + !tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { continue; } if (!lelantusCoinsList[i].isUsed && @@ -2406,82 +2400,91 @@ class FiroWallet extends CoinServiceAPI { // index 0 and 1 for the funds available to spend. // index 2 and 3 for all the funds in the wallet (including the undependable ones) - Future> _getFullBalance() async { + // Future> _refreshBalance() async { + Future _refreshBalance() async { try { + final utxosUpdateFuture = _refreshUTXOs(); final List> lelantusCoins = getLelantusCoinMap(); if (lelantusCoins.isNotEmpty) { lelantusCoins.removeWhere((element) => element.values.any((elementCoin) => elementCoin.value == 0)); } - final utxos = await utxoData; - final Decimal price = await firoPrice; - final data = await _txnData; - final lData = await lelantusTransactionData; - final jindexes = - DB.instance.get(boxName: walletId, key: 'jindex') as List?; + + final currentChainHeight = await chainHeight; + final jindexes = firoGetJIndex(); int intLelantusBalance = 0; int unconfirmedLelantusBalance = 0; - for (var element in lelantusCoins) { - element.forEach((key, value) { - final tx = data.findTransaction(value.txId); - models.Transaction? ltx; - ltx = lData.findTransaction(value.txId); - // Logging.instance.log("$value $tx $ltx"); - if (!jindexes!.contains(value.index) && tx == null) { + for (final element in lelantusCoins) { + element.forEach((key, lelantusCoin) { + isar_models.Transaction? txn = db.isar.transactions + .where() + .txidWalletIdEqualTo( + lelantusCoin.txId, + walletId, + ) + .findFirstSync(); + + if (txn == null) { + // TODO: ?????????????????????????????????????? + } else { + bool isLelantus = txn.isLelantus == true; + if (!jindexes!.contains(lelantusCoin.index) && isLelantus) { + if (!lelantusCoin.isUsed && + txn.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { + // mint tx, add value to balance + intLelantusBalance += lelantusCoin.value; + } /* else { // This coin is not confirmed and may be replaced - } else if (jindexes.contains(value.index) && - tx == null && - !value.isUsed && - ltx != null && - !ltx.confirmedStatus) { - unconfirmedLelantusBalance += value.value; - } else if (jindexes.contains(value.index) && !value.isUsed) { - intLelantusBalance += value.value; - } else if (!value.isUsed && - (tx == null ? true : tx.confirmedStatus != false)) { - intLelantusBalance += value.value; - } else if (tx != null && tx.confirmedStatus == false) { - unconfirmedLelantusBalance += value.value; + }*/ + } else if (jindexes.contains(lelantusCoin.index) && + isLelantus && + !lelantusCoin.isUsed && + !txn.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { + unconfirmedLelantusBalance += lelantusCoin.value; + } else if (jindexes.contains(lelantusCoin.index) && + !lelantusCoin.isUsed) { + intLelantusBalance += lelantusCoin.value; + } else if (!lelantusCoin.isUsed && + (txn.isLelantus == true + ? true + : txn.isConfirmed( + currentChainHeight, MINIMUM_CONFIRMATIONS) != + false)) { + intLelantusBalance += lelantusCoin.value; + } else if (!isLelantus && + txn.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) == + false) { + unconfirmedLelantusBalance += lelantusCoin.value; + } } }); } - final int utxosIntValue = utxos.satoshiBalance; - final Decimal utxosValue = - Format.satoshisToAmount(utxosIntValue, coin: coin); + _balancePrivate = Balance( + total: Amount( + rawValue: + BigInt.from(intLelantusBalance + unconfirmedLelantusBalance), + fractionDigits: coin.decimals, + ), + spendable: Amount( + rawValue: BigInt.from(intLelantusBalance), + fractionDigits: coin.decimals, + ), + blockedTotal: Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ), + pendingSpendable: Amount( + rawValue: BigInt.from(unconfirmedLelantusBalance), + fractionDigits: coin.decimals, + ), + ); + await updateCachedBalanceSecondary(_balancePrivate!); - List balances = List.empty(growable: true); - - Decimal lelantusBalance = - Format.satoshisToAmount(intLelantusBalance, coin: coin); - - balances.add(lelantusBalance); - - balances.add(lelantusBalance * price); - - Decimal _unconfirmedLelantusBalance = - Format.satoshisToAmount(unconfirmedLelantusBalance, coin: coin); - - balances.add(lelantusBalance + utxosValue + _unconfirmedLelantusBalance); - - balances.add( - (lelantusBalance + utxosValue + _unconfirmedLelantusBalance) * price); - - int availableSats = - utxos.satoshiBalance - utxos.satoshiBalanceUnconfirmed; - if (availableSats < 0) { - availableSats = 0; - } - balances.add(Format.satoshisToAmount(availableSats, coin: coin)); - - Logging.instance.log("balances $balances", level: LogLevel.Info); - await DB.instance.put( - boxName: walletId, - key: 'totalBalance', - value: balances[2].toString()); - return balances; + // wait for updated uxtos to get updated public balance + await utxosUpdateFuture; } catch (e, s) { Logging.instance.log("Exception rethrown in getFullBalance(): $e\n$s", level: LogLevel.Error); @@ -2508,15 +2511,19 @@ class FiroWallet extends CoinServiceAPI { /// Returns the mint transaction hex to mint all of the available funds. Future> _mintSelection() async { - final List availableOutputs = _outputsList; - final List spendableOutputs = []; + final currentChainHeight = await chainHeight; + final List availableOutputs = await utxos; + final List spendableOutputs = []; // Build list of spendable outputs and totaling their satoshi amount for (var i = 0; i < availableOutputs.length; i++) { - if (availableOutputs[i].blocked == false && - availableOutputs[i].status.confirmed == true && + if (availableOutputs[i].isBlocked == false && + availableOutputs[i] + .isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) == + true && !(availableOutputs[i].isCoinbase && - availableOutputs[i].status.confirmations <= 101)) { + availableOutputs[i].getConfirmations(currentChainHeight) <= + 101)) { spendableOutputs.add(availableOutputs[i]); } } @@ -2527,8 +2534,7 @@ class FiroWallet extends CoinServiceAPI { element.values.any((elementCoin) => elementCoin.value == 0)); } final data = await _txnData; - final dataMap = data.getAllTransactions(); - dataMap.forEach((key, value) { + for (final value in data) { if (value.inputs.isNotEmpty) { for (var element in value.inputs) { if (lelantusCoins @@ -2542,7 +2548,7 @@ class FiroWallet extends CoinServiceAPI { } } } - }); + } // If there is no Utxos to mint then stop the function. if (spendableOutputs.isEmpty) { @@ -2552,7 +2558,7 @@ class FiroWallet extends CoinServiceAPI { } int satoshisBeingUsed = 0; - List utxoObjectsToUse = []; + List utxoObjectsToUse = []; for (var i = 0; i < spendableOutputs.length; i++) { final spendable = spendableOutputs[i]; @@ -2567,19 +2573,21 @@ class FiroWallet extends CoinServiceAPI { var tmpTx = await buildMintTransaction( utxoObjectsToUse, satoshisBeingUsed, mintsWithoutFee); - int vsize = (tmpTx['transaction'] as Transaction).virtualSize(); - final Decimal dvsize = Decimal.fromInt(vsize); + int vSize = (tmpTx['transaction'] as Transaction).virtualSize(); + final Decimal dvSize = Decimal.fromInt(vSize); final feesObject = await fees; - final Decimal fastFee = - Format.satoshisToAmount(feesObject.fast, coin: coin); + final Decimal fastFee = Amount( + rawValue: BigInt.from(feesObject.fast), + fractionDigits: coin.decimals, + ).decimal; int firoFee = - (dvsize * fastFee * Decimal.fromInt(100000)).toDouble().ceil(); - // int firoFee = (vsize * feesObject.fast * (1 / 1000.0) * 100000000).ceil(); + (dvSize * fastFee * Decimal.fromInt(100000)).toDouble().ceil(); + // int firoFee = (vSize * feesObject.fast * (1 / 1000.0) * 100000000).ceil(); - if (firoFee < vsize) { - firoFee = vsize + 1; + if (firoFee < vSize) { + firoFee = vSize + 1; } firoFee = firoFee + 10; int satoshiAmountToSend = satoshisBeingUsed - firoFee; @@ -2596,10 +2604,9 @@ class FiroWallet extends CoinServiceAPI { Future>> createMintsFromAmount(int total) async { var tmpTotal = total; - var index = 0; + var index = 1; var mints = >[]; - final nextFreeMintIndex = - DB.instance.get(boxName: walletId, key: 'mintIndex') as int; + final nextFreeMintIndex = firoGetMintIndex()!; while (tmpTotal > 0) { final mintValue = min(tmpTotal, MINT_LIMIT); final mint = await _getMintHex( @@ -2634,11 +2641,14 @@ class FiroWallet extends CoinServiceAPI { } /// Builds and signs a transaction - Future> buildMintTransaction(List utxosToUse, - int satoshisPerRecipient, List> mintsMap) async { + Future> buildMintTransaction( + List utxosToUse, + int satoshisPerRecipient, + List> mintsMap, + ) async { //todo: check if print needed // debugPrint(utxosToUse.toString()); - List addressesToDerive = []; + List addressStringsToGet = []; // Populating the addresses to derive for (var i = 0; i < utxosToUse.length; i++) { @@ -2657,64 +2667,95 @@ class FiroWallet extends CoinServiceAPI { final address = vouts[outputIndex]["scriptPubKey"]["addresses"][0] as String?; if (address != null) { - addressesToDerive.add(address); + addressStringsToGet.add(address); } } } - List elipticCurvePairArray = []; + final List addresses = []; + for (final addressString in addressStringsToGet) { + final address = await db.getAddress(walletId, addressString); + if (address == null) { + Logging.instance.log( + "Failed to fetch the corresponding address object for $addressString", + level: LogLevel.Fatal, + ); + } else { + addresses.add(address); + } + } + + List ellipticCurvePairArray = []; List outputDataArray = []; - final receiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivations"); - final changeDerivationsString = - await _secureStore.read(key: "${walletId}_changeDerivations"); + Map? receiveDerivations; + Map? changeDerivations; - final receiveDerivations = Map.from( - jsonDecode(receiveDerivationsString ?? "{}") as Map); - final changeDerivations = Map.from( - jsonDecode(changeDerivationsString ?? "{}") as Map); + for (final addressString in addressStringsToGet) { + String? pubKey; + String? wif; - for (var i = 0; i < addressesToDerive.length; i++) { - final addressToCheckFor = addressesToDerive[i]; + final address = await db.getAddress(walletId, addressString); - for (var i = 0; i < receiveDerivations.length; i++) { - final receive = receiveDerivations["$i"]; - final change = changeDerivations["$i"]; + if (address?.derivationPath != null) { + final node = await Bip32Utils.getBip32Node( + (await mnemonicString)!, + (await mnemonicPassphrase)!, + _network, + address!.derivationPath!.value, + ); + wif = node.toWIF(); + pubKey = Format.uint8listToString(node.publicKey); + } - if (receive['address'] == addressToCheckFor) { - Logging.instance - .log('Receiving found on loop $i', level: LogLevel.Info); - // Logging.instance.log( - // 'decoded receive[\'wif\'] version: ${wif.decode(receive['wif'] as String)}, _network: $_network'); - elipticCurvePairArray - .add(ECPair.fromWIF(receive['wif'] as String, network: _network)); - outputDataArray.add(P2PKH( - network: _network, - data: PaymentData( - pubkey: Format.stringToUint8List( - receive['publicKey'] as String))) - .data - .output!); - break; + if (wif == null || pubKey == null) { + receiveDerivations ??= Map.from( + jsonDecode((await _secureStore.read( + key: "${walletId}_receiveDerivations")) ?? + "{}") as Map, + ); + for (var i = 0; i < receiveDerivations.length; i++) { + final receive = receiveDerivations["$i"]; + if (receive['address'] == addressString) { + wif = receive['wif'] as String; + pubKey = receive['publicKey'] as String; + break; + } } - if (change['address'] == addressToCheckFor) { - Logging.instance.log('Change found on loop $i', level: LogLevel.Info); - // Logging.instance.log( - // 'decoded change[\'wif\'] version: ${wif.decode(change['wif'] as String)}, _network: $_network'); - elipticCurvePairArray - .add(ECPair.fromWIF(change['wif'] as String, network: _network)); - outputDataArray.add(P2PKH( - network: _network, - data: PaymentData( - pubkey: Format.stringToUint8List( - change['publicKey'] as String))) - .data - .output!); - break; + if (wif == null || pubKey == null) { + changeDerivations ??= Map.from( + jsonDecode((await _secureStore.read( + key: "${walletId}_changeDerivations")) ?? + "{}") as Map, + ); + + for (var i = 0; i < changeDerivations.length; i++) { + final change = changeDerivations["$i"]; + if (change['address'] == addressString) { + wif = change['wif'] as String; + pubKey = change['publicKey'] as String; + + break; + } + } } } + + ellipticCurvePairArray.add( + ECPair.fromWIF( + wif!, + network: _network, + ), + ); + outputDataArray.add(P2PKH( + network: _network, + data: PaymentData( + pubkey: Format.stringToUint8List( + pubKey!, + ), + ), + ).data.output!); } final txb = TransactionBuilder(network: _network); @@ -2730,8 +2771,7 @@ class FiroWallet extends CoinServiceAPI { amount += utxosToUse[i].value; } - final index = - DB.instance.get(boxName: walletId, key: 'mintIndex') as int; + final index = firoGetMintIndex()!; Logging.instance.log("index of mint $index", level: LogLevel.Info); for (var mintsElement in mintsMap) { @@ -2744,7 +2784,7 @@ class FiroWallet extends CoinServiceAPI { for (var i = 0; i < utxosToUse.length; i++) { txb.sign( vin: i, - keyPair: elipticCurvePairArray[i], + keyPair: ellipticCurvePairArray[i], witnessValue: utxosToUse[i].value, ); } @@ -2753,45 +2793,47 @@ class FiroWallet extends CoinServiceAPI { var txHex = incomplete.toHex(); int fee = amount - incomplete.outs[0].value!; - var price = await firoPrice; var builtHex = txb.build(); // return builtHex; - final locale =Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; + // final locale = + // Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; return { "transaction": builtHex, "txid": txId, "txHex": txHex, "value": amount - fee, - "fees": Format.satoshisToAmount(fee, coin: coin).toDouble(), + "fees": Amount( + rawValue: BigInt.from(fee), + fractionDigits: coin.decimals, + ).decimal.toDouble(), "publicCoin": "", "height": height, "txType": "Sent", "confirmed_status": false, - "amount": Format.satoshisToAmount(amount, coin: coin).toDouble(), - "worthNow": Format.localizedStringAsFixed( - value: ((Decimal.fromInt(amount) * price) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2), - decimalPlaces: 2, - locale: locale!), + "amount": Amount( + rawValue: BigInt.from(amount), + fractionDigits: coin.decimals, + ).decimal.toDouble(), "timestamp": DateTime.now().millisecondsSinceEpoch ~/ 1000, "subType": "mint", "mintsMap": mintsMap, }; } - Future _refreshLelantusData() async { + Future _refreshLelantusData() async { final List> lelantusCoins = getLelantusCoinMap(); - final jindexes = - DB.instance.get(boxName: walletId, key: 'jindex') as List?; + final jindexes = firoGetJIndex(); // Get all joinsplit transaction ids - final lelantusTxData = await lelantusTransactionData; - final listLelantusTxData = lelantusTxData.getAllTransactions(); + final listLelantusTxData = await db + .getTransactions(walletId) + .filter() + .isLelantusEqualTo(true) + .findAll(); List joinsplits = []; - for (final tx in listLelantusTxData.values) { - if (tx.subType == "join") { + for (final tx in listLelantusTxData) { + if (tx.subType == isar_models.TransactionSubType.join) { joinsplits.add(tx.txid); } } @@ -2807,31 +2849,52 @@ class FiroWallet extends CoinServiceAPI { } } - final currentPrice = await firoPrice; + Map> data = + {}; + for (final entry in listLelantusTxData) { + data[entry.txid] = Tuple2(entry.address.value, entry); + } + // Grab the most recent information on all the joinsplits - final locale = Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; - final updatedJSplit = await getJMintTransactions(cachedElectrumXClient, - joinsplits, _prefs.currency, coin, currentPrice, locale!); + final updatedJSplit = await getJMintTransactions( + cachedElectrumXClient, + joinsplits, + coin, + ); + + final currentChainHeight = await chainHeight; // update all of joinsplits that are now confirmed. - for (final tx in updatedJSplit) { - final currentTx = listLelantusTxData[tx.txid]; + for (final tx in updatedJSplit.entries) { + isar_models.Transaction? currentTx; + + try { + currentTx = + listLelantusTxData.firstWhere((e) => e.txid == tx.value.txid); + } catch (_) { + currentTx = null; + } + if (currentTx == null) { // this send was accidentally not included in the list - listLelantusTxData[tx.txid] = tx; + tx.value.isLelantus = true; + data[tx.value.txid] = + Tuple2(tx.value.address.value ?? tx.key, tx.value); + continue; } - if (currentTx.confirmedStatus != tx.confirmedStatus) { - listLelantusTxData[tx.txid] = tx; + if (currentTx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) != + tx.value.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { + tx.value.isLelantus = true; + data[tx.value.txid] = + Tuple2(tx.value.address.value ?? tx.key, tx.value); } } - final txData = await _txnData; - // Logging.instance.log(txData.txChunks); - final listTxData = txData.getAllTransactions(); - listTxData.forEach((key, value) { + final listTxData = await _txnData; + for (final value in listTxData) { // ignore change addresses // bool hasAtLeastOneReceive = false; // int howManyReceiveInputs = 0; @@ -2846,36 +2909,80 @@ class FiroWallet extends CoinServiceAPI { // } // } - if (value.txType == "Received" && value.subType != "mint") { + if (value.type == isar_models.TransactionType.incoming && + value.subType != isar_models.TransactionSubType.mint) { // Every receive other than a mint should be shown. Mints will be collected and shown from the send side - listLelantusTxData[value.txid] = value; - } else if (value.txType == "Sent") { + value.isLelantus = true; + data[value.txid] = Tuple2(value.address.value, value); + } else if (value.type == isar_models.TransactionType.outgoing) { // all sends should be shown, mints will be displayed correctly in the ui - listLelantusTxData[value.txid] = value; + value.isLelantus = true; + data[value.txid] = Tuple2(value.address.value, value); } - }); + } - // update the _lelantusTransactionData - final models.TransactionData newTxData = - models.TransactionData.fromMap(listLelantusTxData); - // Logging.instance.log(newTxData.txChunks); - _lelantusTransactionData = Future(() => newTxData); - await DB.instance.put( - boxName: walletId, key: 'latest_lelantus_tx_model', value: newTxData); - return newTxData; + // TODO: optimize this whole lelantus process + + final List> txnsData = + []; + + for (final value in data.values) { + // allow possible null address on mints as we don't display address + // this should normally never be null anyways but old (dbVersion up to 4) + // migrated transactions may not have had an address (full rescan should + // fix this) + isar_models.Address? transactionAddress; + try { + transactionAddress = + value.item2.subType == isar_models.TransactionSubType.mint + ? value.item1 + : value.item1!; + } catch (_) { + Logging.instance + .log("_refreshLelantusData value: $value", level: LogLevel.Fatal); + } + final outs = + value.item2.outputs.where((_) => true).toList(growable: false); + final ins = value.item2.inputs.where((_) => true).toList(growable: false); + + txnsData.add(Tuple2( + value.item2.copyWith(inputs: ins, outputs: outs).item1, + transactionAddress)); + } + + await db.addNewTransactionData(txnsData, walletId); + + // // update the _lelantusTransactionData + // final models.TransactionData newTxData = + // models.TransactionData.fromMap(listLelantusTxData); + // // Logging.instance.log(newTxData.txChunks); + // _lelantusTransactionData = Future(() => newTxData); + // await DB.instance.put( + // boxName: walletId, key: 'latest_lelantus_tx_model', value: newTxData); + // return newTxData; } Future _getMintHex(int amount, int index) async { - final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic'); - final mintKeyPair = await compute( - getBip32NodeWrapper, - Tuple4( - MINT_INDEX, - index, - mnemonic!, - _network, - ), + final _mnemonic = await mnemonicString; + final _mnemonicPassphrase = await mnemonicPassphrase; + if (_mnemonicPassphrase == null) { + Logging.instance.log( + "Exception in _getMintHex: mnemonic passphrase null, possible migration issue; if using internal builds, delete wallet and restore from seed, if using a release build, please file bug report", + level: LogLevel.Error); + } + + final derivePath = constructDerivePath( + networkWIF: _network.wif, + chain: MINT_INDEX, + index: index, ); + final mintKeyPair = await Bip32Utils.getBip32Node( + _mnemonic!, + _mnemonicPassphrase!, + _network, + derivePath, + ); + String keydata = Format.uint8listToString(mintKeyPair.privateKey!); String seedID = Format.uint8listToString(mintKeyPair.identifier); @@ -2901,8 +3008,7 @@ class FiroWallet extends CoinServiceAPI { "_submitLelantusToNetwork txid: ${transactionInfo['txid']}", level: LogLevel.Info); if (txid == transactionInfo['txid']) { - final index = - DB.instance.get(boxName: walletId, key: 'mintIndex') as int?; + final index = firoGetMintIndex(); final List> lelantusCoins = getLelantusCoinMap(); List> coins; @@ -2940,33 +3046,80 @@ class FiroWallet extends CoinServiceAPI { false); if (jmint.value > 0) { coins.add({jmint.txId: jmint}); - final jindexes = DB.instance - .get(boxName: walletId, key: 'jindex') as List?; - jindexes!.add(index); - await DB.instance - .put(boxName: walletId, key: 'jindex', value: jindexes); - await DB.instance.put( - boxName: walletId, key: 'mintIndex', value: index + 1); + final jindexes = firoGetJIndex()!; + jindexes.add(index); + await firoUpdateJIndex(jindexes); + await firoUpdateMintIndex(index + 1); } - await DB.instance.put( - boxName: walletId, key: '_lelantus_coins', value: coins); + await firoUpdateLelantusCoins(coins); + + final amount = Amount.fromDecimal( + Decimal.parse(transactionInfo["amount"].toString()), + fractionDigits: coin.decimals, + ); // add the send transaction - models.TransactionData data = await lelantusTransactionData; - Map transactions = - data.getAllTransactions(); - transactions[transactionInfo['txid'] as String] = - models.Transaction.fromLelantusJson(transactionInfo); - final models.TransactionData newTxData = - models.TransactionData.fromMap(transactions); - await DB.instance.put( - boxName: walletId, - key: 'latest_lelantus_tx_model', - value: newTxData); - final ldata = DB.instance.get( - boxName: walletId, - key: 'latest_lelantus_tx_model') as models.TransactionData; - _lelantusTransactionData = Future(() => ldata); + final transaction = isar_models.Transaction( + walletId: walletId, + txid: transactionInfo['txid'] as String, + timestamp: transactionInfo['timestamp'] as int? ?? + (DateTime.now().millisecondsSinceEpoch ~/ 1000), + type: transactionInfo['txType'] == "Received" + ? isar_models.TransactionType.incoming + : isar_models.TransactionType.outgoing, + subType: transactionInfo["subType"] == "mint" + ? isar_models.TransactionSubType.mint + : transactionInfo["subType"] == "join" + ? isar_models.TransactionSubType.join + : isar_models.TransactionSubType.none, + amount: amount.raw.toInt(), + amountString: amount.toJsonString(), + fee: Amount.fromDecimal( + Decimal.parse(transactionInfo["fees"].toString()), + fractionDigits: coin.decimals, + ).raw.toInt(), + height: transactionInfo["height"] as int?, + isCancelled: false, + isLelantus: true, + slateId: null, + nonce: null, + otherData: transactionInfo["otherData"] as String?, + inputs: [], + outputs: [], + ); + + final transactionAddress = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(transactionInfo["address"] as String) + .findFirst() ?? + isar_models.Address( + walletId: walletId, + value: transactionInfo["address"] as String, + derivationIndex: -1, + derivationPath: null, + type: isar_models.AddressType.nonWallet, + subType: isar_models.AddressSubType.nonWallet, + publicKey: [], + ); + + final List> + txnsData = []; + + txnsData.add(Tuple2(transaction, transactionAddress)); + + await db.addNewTransactionData(txnsData, walletId); + + // final models.TransactionData newTxData = + // models.TransactionData.fromMap(transactions); + // await DB.instance.put( + // boxName: walletId, + // key: 'latest_lelantus_tx_model', + // value: newTxData); + // final ldata = DB.instance.get( + // boxName: walletId, + // key: 'latest_lelantus_tx_model') as models.TransactionData; + // _lelantusTransactionData = Future(() => ldata); } else { // This is a mint Logging.instance.log("this is a mint", level: LogLevel.Info); @@ -2985,13 +3138,11 @@ class FiroWallet extends CoinServiceAPI { ); if (mint.value > 0) { coins.add({mint.txId: mint}); - await DB.instance.put( - boxName: walletId, key: 'mintIndex', value: index + 1); + await firoUpdateMintIndex(index + 1); } } // Logging.instance.log(coins); - await DB.instance.put( - boxName: walletId, key: '_lelantus_coins', value: coins); + await firoUpdateLelantusCoins(coins); } return true; } else { @@ -3013,9 +3164,18 @@ class FiroWallet extends CoinServiceAPI { numberOfBlocksFast: f, numberOfBlocksAverage: m, numberOfBlocksSlow: s, - fast: Format.decimalAmountToSatoshis(fast, coin), - medium: Format.decimalAmountToSatoshis(medium, coin), - slow: Format.decimalAmountToSatoshis(slow, coin), + fast: Amount.fromDecimal( + fast, + fractionDigits: coin.decimals, + ).raw.toInt(), + medium: Amount.fromDecimal( + medium, + fractionDigits: coin.decimals, + ).raw.toInt(), + slow: Amount.fromDecimal( + slow, + fractionDigits: coin.decimals, + ).raw.toInt(), ); Logging.instance.log("fetched fees: $feeObject", level: LogLevel.Info); @@ -3072,25 +3232,38 @@ class FiroWallet extends CoinServiceAPI { Future checkReceivingAddressForTransactions() async { try { - final String currentExternalAddr = await _getCurrentAddressForChain(0); - final int numtxs = - await _getReceivedTxCount(address: currentExternalAddr); + final currentReceiving = await _currentReceivingAddress; + + final int txCount = + await _getReceivedTxCount(address: currentReceiving.value); Logging.instance.log( - 'Number of txs for current receiving: $currentExternalAddr: $numtxs', + 'Number of txs for current receiving address $currentReceiving: $txCount', level: LogLevel.Info); - if (numtxs >= 1) { - await incrementAddressIndexForChain( - 0); // First increment the receiving index - final newReceivingIndex = - DB.instance.get(boxName: walletId, key: 'receivingIndex') - as int; // Check the new receiving index - final newReceivingAddress = await _generateAddressForChain(0, - newReceivingIndex); // Use new index to derive a new receiving address - await addToAddressesArrayForChain(newReceivingAddress, - 0); // Add that new receiving address to the array of receiving addresses - _currentReceivingAddress = Future(() => - newReceivingAddress); // Set the new receiving address that the service + if (txCount >= 1 || currentReceiving.derivationIndex < 0) { + // First increment the receiving index + final newReceivingIndex = currentReceiving.derivationIndex + 1; + + // Use new index to derive a new receiving address + final newReceivingAddress = await _generateAddressForChain( + 0, + newReceivingIndex, + ); + + final existing = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(newReceivingAddress.value) + .findFirst(); + if (existing == null) { + // Add that new change address + await db.putAddress(newReceivingAddress); + } else { + // we need to update the address + await db.updateAddress(existing, newReceivingAddress); + } + // keep checking until address with no tx history is set as current + await checkReceivingAddressForTransactions(); } } on SocketException catch (se, s) { Logging.instance.log( @@ -3107,23 +3280,37 @@ class FiroWallet extends CoinServiceAPI { Future checkChangeAddressForTransactions() async { try { - final String currentExternalAddr = await _getCurrentAddressForChain(1); - final int numtxs = - await _getReceivedTxCount(address: currentExternalAddr); + final currentChange = await _currentChangeAddress; + final int txCount = + await _getReceivedTxCount(address: currentChange.value); Logging.instance.log( - 'Number of txs for current change address: $currentExternalAddr: $numtxs', + 'Number of txs for current change address: $currentChange: $txCount', level: LogLevel.Info); - if (numtxs >= 1) { - await incrementAddressIndexForChain( - 0); // First increment the change index - final newReceivingIndex = - DB.instance.get(boxName: walletId, key: 'changeIndex') - as int; // Check the new change index - final newReceivingAddress = await _generateAddressForChain(0, - newReceivingIndex); // Use new index to derive a new change address - await addToAddressesArrayForChain(newReceivingAddress, - 0); // Add that new receiving address to the array of change addresses + if (txCount >= 1 || currentChange.derivationIndex < 0) { + // First increment the change index + final newChangeIndex = currentChange.derivationIndex + 1; + + // Use new index to derive a new change address + final newChangeAddress = await _generateAddressForChain( + 1, + newChangeIndex, + ); + + final existing = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(newChangeAddress.value) + .findFirst(); + if (existing == null) { + // Add that new change address + await db.putAddress(newChangeAddress); + } else { + // we need to update the address + await db.updateAddress(existing, newChangeAddress); + } + // keep checking until address with no tx history is set as current + await checkChangeAddressForTransactions(); } } on SocketException catch (se, s) { Logging.instance.log( @@ -3138,21 +3325,32 @@ class FiroWallet extends CoinServiceAPI { } } - Future> _fetchAllOwnAddresses() async { - final List allAddresses = []; - final receivingAddresses = - DB.instance.get(boxName: walletId, key: 'receivingAddresses') - as List; - final changeAddresses = - DB.instance.get(boxName: walletId, key: 'changeAddresses') - as List; - - for (var i = 0; i < receivingAddresses.length; i++) { - allAddresses.add(receivingAddresses[i] as String); - } - for (var i = 0; i < changeAddresses.length; i++) { - allAddresses.add(changeAddresses[i] as String); - } + Future> _fetchAllOwnAddresses() async { + final allAddresses = await db + .getAddresses(walletId) + .filter() + .not() + .typeEqualTo(isar_models.AddressType.nonWallet) + .and() + .group((q) => q + .subTypeEqualTo(isar_models.AddressSubType.receiving) + .or() + .subTypeEqualTo(isar_models.AddressSubType.change)) + .findAll(); + // final List allAddresses = []; + // final receivingAddresses = + // DB.instance.get(boxName: walletId, key: 'receivingAddresses') + // as List; + // final changeAddresses = + // DB.instance.get(boxName: walletId, key: 'changeAddresses') + // as List; + // + // for (var i = 0; i < receivingAddresses.length; i++) { + // allAddresses.add(receivingAddresses[i] as String); + // } + // for (var i = 0; i < changeAddresses.length; i++) { + // allAddresses.add(changeAddresses[i] as String); + // } return allAddresses; } @@ -3201,57 +3399,62 @@ class FiroWallet extends CoinServiceAPI { } } - Future _fetchTransactionData() async { - final changeAddresses = - DB.instance.get(boxName: walletId, key: 'changeAddresses') - as List; - final List allAddresses = await _fetchAllOwnAddresses(); - // Logging.instance.log("receiving addresses: $receivingAddresses"); - // Logging.instance.log("change addresses: $changeAddresses"); + bool _duplicateTxCheck( + List> allTransactions, String txid) { + for (int i = 0; i < allTransactions.length; i++) { + if (allTransactions[i]["txid"] == txid) { + return true; + } + } + return false; + } - List> allTxHashes = await _fetchHistory(allAddresses); + Future _refreshTransactions() async { + final List allAddresses = + await _fetchAllOwnAddresses(); - final cachedTransactions = - DB.instance.get(boxName: walletId, key: 'latest_tx_model') - as models.TransactionData?; - int latestTxnBlockHeight = - DB.instance.get(boxName: walletId, key: "storedTxnDataHeight") - as int? ?? - 0; + final List> allTxHashes = + await _fetchHistory(allAddresses.map((e) => e.value).toList()); - final unconfirmedCachedTransactions = - cachedTransactions?.getAllTransactions() ?? {}; - unconfirmedCachedTransactions - .removeWhere((key, value) => value.confirmedStatus); + List> allTransactions = []; - if (cachedTransactions != null) { - for (final tx in allTxHashes.toList(growable: false)) { - final txHeight = tx["height"] as int; - if (txHeight > 0 && - txHeight < latestTxnBlockHeight - MINIMUM_CONFIRMATIONS) { - if (unconfirmedCachedTransactions[tx["tx_hash"] as String] == null) { - allTxHashes.remove(tx); - } + final currentHeight = await chainHeight; + + for (final txHash in allTxHashes) { + final storedTx = await db + .getTransactions(walletId) + .filter() + .txidEqualTo(txHash["tx_hash"] as String) + .findFirst(); + + if (storedTx == null || + !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)) { + final tx = await cachedElectrumXClient.getTransaction( + txHash: txHash["tx_hash"] as String, + verbose: true, + coin: coin, + ); + + if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { + tx["address"] = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(txHash["address"] as String) + .findFirst(); + tx["height"] = txHash["height"]; + allTransactions.add(tx); } } } - List hashes = []; - for (var element in allTxHashes) { - hashes.add(element['tx_hash'] as String); - } - List> allTransactions = await fastFetch(hashes); + final List> txnsData = + []; - Logging.instance.log("allTransactions length: ${allTransactions.length}", - level: LogLevel.Info); + Set changeAddresses = allAddresses + .where((e) => e.subType == isar_models.AddressSubType.change) + .map((e) => e.value) + .toSet(); - // sort thing stuff - final currentPrice = await firoPrice; - final List> midSortedArray = []; - - final locale = Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; - - Logging.instance.log("refresh the txs", level: LogLevel.Info); for (final txObject in allTransactions) { // Logging.instance.log(txObject); List sendersArray = []; @@ -3262,9 +3465,6 @@ class FiroWallet extends CoinServiceAPI { // Usually has value regardless of txType due to change addresses int outputAmtAddressedToWallet = 0; - Map midSortedTx = {}; - List aliens = []; - for (final input in txObject["vin"] as List) { final address = input["address"] as String?; if (address != null) { @@ -3275,15 +3475,16 @@ class FiroWallet extends CoinServiceAPI { // Logging.instance.log("sendersArray: $sendersArray"); for (final output in txObject["vout"] as List) { - final addresses = output["scriptPubKey"]["addresses"] as List?; - if (addresses != null && addresses.isNotEmpty) { - recipientsArray.add(addresses[0] as String); + final address = output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]?["address"] as String?; + if (address != null) { + recipientsArray.add(address); } } // Logging.instance.log("recipientsArray: $recipientsArray"); final foundInSenders = - allAddresses.any((element) => sendersArray.contains(element)); + allAddresses.any((element) => sendersArray.contains(element.value)); // Logging.instance.log("foundInSenders: $foundInSenders"); String outAddress = ""; @@ -3305,25 +3506,31 @@ class FiroWallet extends CoinServiceAPI { .toBigInt() .toInt(); } - final address = input["address"]; - final value = input["valueSat"]; + final address = input["address"] as String?; + final value = input["valueSat"] as int?; if (address != null && value != null) { - if (allAddresses.contains(address)) { - inputAmtSentFromWallet += value as int; + if (allAddresses.where((e) => e.value == address).isNotEmpty) { + inputAmtSentFromWallet += value; } } if (value != null) { - inAmount += value as int; + inAmount += value; } } for (final output in txObject["vout"] as List) { - final addresses = output["scriptPubKey"]["addresses"] as List?; + final address = output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]?["address"] as String?; final value = output["value"]; - if (addresses != null && addresses.isNotEmpty) { - final address = addresses[0] as String; - if (value != null) { + + if (value != null) { + outAmount += (Decimal.parse(value.toString()) * + Decimal.fromInt(Constants.satsPerCoin(coin))) + .toBigInt() + .toInt(); + + if (address != null) { if (changeAddresses.contains(address)) { inputAmtSentFromWallet -= (Decimal.parse(value.toString()) * Decimal.fromInt(Constants.satsPerCoin(coin))) @@ -3334,12 +3541,6 @@ class FiroWallet extends CoinServiceAPI { } } } - if (value != null) { - outAmount += (Decimal.parse(value.toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - } } fees = nFeesUsed ? fees : inAmount - outAmount; @@ -3359,10 +3560,10 @@ class FiroWallet extends CoinServiceAPI { final addresses = output["scriptPubKey"]["addresses"] as List?; if (addresses != null && addresses.isNotEmpty) { final address = addresses[0] as String; - final value = output["value"]; + final value = output["value"] ?? 0; // Logging.instance.log(address + value.toString()); - if (allAddresses.contains(address)) { + if (allAddresses.where((e) => e.value == address).isNotEmpty) { outputAmtAddressedToWallet += (Decimal.parse(value.toString()) * Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() @@ -3373,125 +3574,109 @@ class FiroWallet extends CoinServiceAPI { } } - final int confirms = txObject["confirmations"] as int? ?? 0; - - // create final tx map - midSortedTx["txid"] = txObject["txid"]; - midSortedTx["confirmed_status"] = confirms >= MINIMUM_CONFIRMATIONS; - midSortedTx["confirmations"] = confirms; - midSortedTx["timestamp"] = txObject["blocktime"] ?? - (DateTime.now().millisecondsSinceEpoch ~/ 1000); + isar_models.TransactionType type; + isar_models.TransactionSubType subType = + isar_models.TransactionSubType.none; + int amount; if (foundInSenders) { - midSortedTx["txType"] = "Sent"; - midSortedTx["amount"] = inputAmtSentFromWallet; - final String worthNow = Format.localizedStringAsFixed( - value: ((currentPrice * Decimal.fromInt(inputAmtSentFromWallet)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2), - decimalPlaces: 2, - locale: locale!); - midSortedTx["worthNow"] = worthNow; - midSortedTx["worthAtBlockTimestamp"] = worthNow; + type = isar_models.TransactionType.outgoing; + amount = inputAmtSentFromWallet; + if (txObject["vout"][0]["scriptPubKey"]["type"] == "lelantusmint") { - midSortedTx["subType"] = "mint"; + subType = isar_models.TransactionSubType.mint; } } else { - midSortedTx["txType"] = "Received"; - midSortedTx["amount"] = outputAmtAddressedToWallet; - final worthNow = Format.localizedStringAsFixed( - value: - ((currentPrice * Decimal.fromInt(outputAmtAddressedToWallet)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2), - decimalPlaces: 2, - locale: locale!); - midSortedTx["worthNow"] = worthNow; - midSortedTx["worthAtBlockTimestamp"] = worthNow; - } - midSortedTx["aliens"] = aliens; - midSortedTx["fees"] = fees; - midSortedTx["address"] = outAddress; - midSortedTx["inputSize"] = txObject["vin"].length; - midSortedTx["outputSize"] = txObject["vout"].length; - midSortedTx["inputs"] = txObject["vin"]; - midSortedTx["outputs"] = txObject["vout"]; - - final int height = txObject["height"] as int? ?? 0; - midSortedTx["height"] = height; - - if (height >= latestTxnBlockHeight) { - latestTxnBlockHeight = height; + type = isar_models.TransactionType.incoming; + amount = outputAmtAddressedToWallet; } - midSortedArray.add(midSortedTx); + final transactionAddress = + allAddresses.firstWhere((e) => e.value == outAddress, + orElse: () => isar_models.Address( + walletId: walletId, + value: outAddress, + derivationIndex: -1, + derivationPath: null, + type: isar_models.AddressType.nonWallet, + subType: isar_models.AddressSubType.nonWallet, + publicKey: [], + )); + + List outs = []; + List ins = []; + + for (final json in txObject["vin"] as List) { + bool isCoinBase = json['coinbase'] != null; + final input = isar_models.Input( + txid: json['txid'] as String? ?? "", + vout: json['vout'] as int? ?? -1, + scriptSig: json['scriptSig']?['hex'] as String?, + scriptSigAsm: json['scriptSig']?['asm'] as String?, + isCoinbase: isCoinBase ? isCoinBase : json['is_coinbase'] as bool?, + sequence: json['sequence'] as int?, + innerRedeemScriptAsm: json['innerRedeemscriptAsm'] as String?, + ); + ins.add(input); + } + + for (final json in txObject["vout"] as List) { + final output = isar_models.Output( + scriptPubKey: json['scriptPubKey']?['hex'] as String?, + scriptPubKeyAsm: json['scriptPubKey']?['asm'] as String?, + scriptPubKeyType: json['scriptPubKey']?['type'] as String?, + scriptPubKeyAddress: + json["scriptPubKey"]?["addresses"]?[0] as String? ?? + json['scriptPubKey']['type'] as String, + value: Amount.fromDecimal( + Decimal.parse(json["value"].toString()), + fractionDigits: coin.decimals, + ).raw.toInt(), + ); + outs.add(output); + } + + final tx = isar_models.Transaction( + walletId: walletId, + txid: txObject["txid"] as String, + timestamp: txObject["blocktime"] as int? ?? + (DateTime.now().millisecondsSinceEpoch ~/ 1000), + type: type, + subType: subType, + amount: amount, + amountString: Amount( + rawValue: BigInt.from(amount), + fractionDigits: Coin.firo.decimals, + ).toJsonString(), + fee: fees, + height: txObject["height"] as int? ?? 0, + isCancelled: false, + isLelantus: false, + slateId: null, + otherData: null, + nonce: null, + inputs: ins, + outputs: outs, + ); + + txnsData.add(Tuple2(tx, transactionAddress)); } - // sort by date ---- //TODO not sure if needed - // shouldn't be any issues with a null timestamp but I got one at some point? - midSortedArray.sort((a, b) { - final aT = a["timestamp"]; - final bT = b["timestamp"]; + await db.addNewTransactionData(txnsData, walletId); - if (aT == null && bT == null) { - return 0; - } else if (aT == null) { - return -1; - } else if (bT == null) { - return 1; - } else { - return (bT as int) - (aT as int); - } - }); - - // buildDateTimeChunks - final Map result = {"dateTimeChunks": []}; - final dateArray = []; - - for (int i = 0; i < midSortedArray.length; i++) { - final txObject = midSortedArray[i]; - final date = - models.extractDateFromTimestamp(txObject["timestamp"] as int); - final txTimeArray = [txObject["timestamp"], date]; - - if (dateArray.contains(txTimeArray[1])) { - result["dateTimeChunks"].forEach((dynamic chunk) { - if (models.extractDateFromTimestamp(chunk["timestamp"] as int) == - txTimeArray[1]) { - if (chunk["transactions"] == null) { - chunk["transactions"] = >[]; - } - chunk["transactions"].add(txObject); - } - }); - } else { - dateArray.add(txTimeArray[1]); - final chunk = { - "timestamp": txTimeArray[0], - "transactions": [txObject], - }; - result["dateTimeChunks"].add(chunk); - } + // quick hack to notify manager to call notifyListeners if + // transactions changed + if (txnsData.isNotEmpty) { + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "Transactions updated/added for: $walletId $walletName ", + walletId, + ), + ); } - - final transactionsMap = cachedTransactions?.getAllTransactions() ?? {}; - transactionsMap - .addAll(models.TransactionData.fromJson(result).getAllTransactions()); - - final txModel = models.TransactionData.fromMap(transactionsMap); - - await DB.instance.put( - boxName: walletId, - key: 'storedTxnDataHeight', - value: latestTxnBlockHeight); - await DB.instance.put( - boxName: walletId, key: 'latest_tx_model', value: txModel); - - cachedTxData = txModel; - return txModel; } - Future _fetchUtxoData() async { - final List allAddresses = await _fetchAllOwnAddresses(); + Future _refreshUTXOs() async { + final allAddresses = await _fetchAllOwnAddresses(); try { final fetchedUtxoList = >>[]; @@ -3504,7 +3689,7 @@ class FiroWallet extends CoinServiceAPI { batches[batchNumber] = {}; } final scripthash = - AddressUtils.convertToScriptHash(allAddresses[i], _network); + AddressUtils.convertToScriptHash(allAddresses[i].value, _network); batches[batchNumber]!.addAll({ scripthash: [scripthash] }); @@ -3522,325 +3707,198 @@ class FiroWallet extends CoinServiceAPI { } } } - final priceData = - await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final List> outputArray = []; - int satoshiBalance = 0; - int satoshiBalancePending = 0; + + final currentChainHeight = await chainHeight; + + final List outputArray = []; + Amount satoshiBalanceTotal = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); + Amount satoshiBalancePending = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); + Amount satoshiBalanceSpendable = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); + Amount satoshiBalanceBlocked = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); for (int i = 0; i < fetchedUtxoList.length; i++) { for (int j = 0; j < fetchedUtxoList[i].length; j++) { - int value = fetchedUtxoList[i][j]["value"] as int; - satoshiBalance += value; - final txn = await cachedElectrumXClient.getTransaction( txHash: fetchedUtxoList[i][j]["tx_hash"] as String, verbose: true, coin: coin, ); - final Map utxo = {}; - final int confirmations = txn["confirmations"] as int? ?? 0; - final bool confirmed = confirmations >= MINIMUM_CONFIRMATIONS; - if (!confirmed) { - satoshiBalancePending += value; + // todo check here if we should mark as blocked + final utxo = isar_models.UTXO( + walletId: walletId, + txid: txn["txid"] as String, + vout: fetchedUtxoList[i][j]["tx_pos"] as int, + value: fetchedUtxoList[i][j]["value"] as int, + name: "", + isBlocked: false, + blockedReason: null, + isCoinbase: txn["is_coinbase"] as bool? ?? false, + blockHash: txn["blockhash"] as String?, + blockHeight: fetchedUtxoList[i][j]["height"] as int?, + blockTime: txn["blocktime"] as int?, + ); + + final utxoAmount = Amount( + rawValue: BigInt.from(utxo.value), + fractionDigits: coin.decimals, + ); + satoshiBalanceTotal = satoshiBalanceTotal + utxoAmount; + + if (utxo.isBlocked) { + satoshiBalanceBlocked = satoshiBalanceBlocked + utxoAmount; + } else { + if (utxo.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { + satoshiBalanceSpendable = satoshiBalanceSpendable + utxoAmount; + } else { + satoshiBalancePending = satoshiBalancePending + utxoAmount; + } } - utxo["txid"] = txn["txid"]; - utxo["vout"] = fetchedUtxoList[i][j]["tx_pos"]; - utxo["value"] = value; - - utxo["status"] = {}; - utxo["status"]["confirmed"] = confirmed; - utxo["status"]["confirmations"] = confirmations; - utxo["status"]["confirmed"] = - txn["confirmations"] == null ? false : txn["confirmations"] > 0; - - utxo["status"]["block_height"] = fetchedUtxoList[i][j]["height"]; - utxo["status"]["block_hash"] = txn["blockhash"]; - utxo["status"]["block_time"] = txn["blocktime"]; - - final fiatValue = ((Decimal.fromInt(value) * currentPrice) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2); - utxo["rawWorth"] = fiatValue; - utxo["fiatWorth"] = fiatValue.toString(); - utxo["is_coinbase"] = txn['vin'][0]['coinbase'] != null; outputArray.add(utxo); } } - Decimal currencyBalanceRaw = - ((Decimal.fromInt(satoshiBalance) * currentPrice) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2); - - final Map result = { - "total_user_currency": currencyBalanceRaw.toString(), - "total_sats": satoshiBalance, - "total_btc": (Decimal.fromInt(satoshiBalance) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal( - scaleOnInfinitePrecision: Constants.decimalPlacesForCoin(coin)) - .toString(), - "outputArray": outputArray, - "unconfirmed": satoshiBalancePending, - }; - - final dataModel = UtxoData.fromJson(result); - - final List allOutputs = dataModel.unspentOutputArray; Logging.instance - .log('Outputs fetched: $allOutputs', level: LogLevel.Info); - await _sortOutputs(allOutputs); - await DB.instance.put( - boxName: walletId, key: 'latest_utxo_model', value: dataModel); - // await DB.instance.put( - // boxName: walletId, - // key: 'totalBalance', - // value: dataModel.satoshiBalance); - return dataModel; + .log('Outputs fetched: $outputArray', level: LogLevel.Info); + + // TODO move this out of here and into IDB + await db.isar.writeTxn(() async { + await db.isar.utxos.where().walletIdEqualTo(walletId).deleteAll(); + await db.isar.utxos.putAll(outputArray); + }); + + // finally update public balance + _balance = Balance( + total: satoshiBalanceTotal, + spendable: satoshiBalanceSpendable, + blockedTotal: satoshiBalanceBlocked, + pendingSpendable: satoshiBalancePending, + ); + await updateCachedBalance(_balance!); } catch (e, s) { Logging.instance .log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error); - final latestTxModel = - DB.instance.get(boxName: walletId, key: 'latest_utxo_model') - as models.UtxoData?; - - if (latestTxModel == null) { - final emptyModel = { - "total_user_currency": "0.00", - "total_sats": 0, - "total_btc": "0", - "outputArray": [] - }; - return UtxoData.fromJson(emptyModel); - } else { - Logging.instance - .log("Old output model located", level: LogLevel.Warning); - return latestTxModel; - } - } - } - - Future _getLelantusTransactionData() async { - final latestModel = DB.instance.get( - boxName: walletId, - key: 'latest_lelantus_tx_model') as models.TransactionData?; - - if (latestModel == null) { - final emptyModel = {"dateTimeChunks": []}; - return models.TransactionData.fromJson(emptyModel); - } else { - Logging.instance - .log("Old transaction model located", level: LogLevel.Warning); - return latestModel; } } /// Returns the latest receiving/change (external/internal) address for the wallet depending on [chain] /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! Future _getCurrentAddressForChain(int chain) async { - if (chain == 0) { - final externalChainArray = (DB.instance.get( - boxName: walletId, key: 'receivingAddresses')) as List; - return externalChainArray.last as String; - } else { - // Here, we assume that chain == 1 - final internalChainArray = - (DB.instance.get(boxName: walletId, key: 'changeAddresses')) - as List; - return internalChainArray.last as String; - } - } + final subType = chain == 0 // Here, we assume that chain == 1 if it isn't 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change; - Future fillAddresses(String suppliedMnemonic, - {int perBatch = 50, int numberOfThreads = 4}) async { - if (numberOfThreads <= 0) { - numberOfThreads = 1; - } - if (Platform.environment["FLUTTER_TEST"] == "true" || integrationTestFlag) { - perBatch = 10; - numberOfThreads = 4; - } + isar_models.Address? address = await db + .getAddresses(walletId) + .filter() + .typeEqualTo(isar_models.AddressType.p2pkh) + .subTypeEqualTo(subType) + .sortByDerivationIndexDesc() + .findFirst(); - final receiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivations"); - final changeDerivationsString = - await _secureStore.read(key: "${walletId}_changeDerivations"); - - var receiveDerivations = Map.from( - jsonDecode(receiveDerivationsString ?? "{}") as Map); - var changeDerivations = Map.from( - jsonDecode(changeDerivationsString ?? "{}") as Map); - - final int start = receiveDerivations.length; - - List ports = List.empty(growable: true); - for (int i = 0; i < numberOfThreads; i++) { - ReceivePort receivePort = await getIsolate({ - "function": "isolateDerive", - "mnemonic": suppliedMnemonic, - "from": start + i * perBatch, - "to": start + (i + 1) * perBatch, - "network": _network, - }); - ports.add(receivePort); - } - for (int i = 0; i < numberOfThreads; i++) { - ReceivePort receivePort = ports.elementAt(i); - var message = await receivePort.first; - if (message is String) { - Logging.instance.log("this is a string", level: LogLevel.Error); - stop(receivePort); - throw Exception("isolateDerive isolate failed"); - } - stop(receivePort); - Logging.instance.log('Closing isolateDerive!', level: LogLevel.Info); - receiveDerivations.addAll(message['receive'] as Map); - changeDerivations.addAll(message['change'] as Map); - } - Logging.instance.log("isolate derives", level: LogLevel.Info); - // Logging.instance.log(receiveDerivations); - // Logging.instance.log(changeDerivations); - - final newReceiveDerivationsString = jsonEncode(receiveDerivations); - final newChangeDerivationsString = jsonEncode(changeDerivations); - - await _secureStore.write( - key: "${walletId}_receiveDerivations", - value: newReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivations", - value: newChangeDerivationsString); + return address!.value; } /// Generates a new internal or external chain address for the wallet using a BIP84 derivation path. /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! /// [index] - This can be any integer >= 0 - Future _generateAddressForChain(int chain, int index) async { - // final wallet = await Hive.openBox(this._walletId); - final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic'); - Map? derivations; - if (chain == 0) { - final receiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivations"); - derivations = Map.from( - jsonDecode(receiveDerivationsString ?? "{}") as Map); - } else if (chain == 1) { - final changeDerivationsString = - await _secureStore.read(key: "${walletId}_changeDerivations"); - derivations = Map.from( - jsonDecode(changeDerivationsString ?? "{}") as Map); - } - - if (derivations!.isNotEmpty) { - if (derivations["$index"] == null) { - await fillAddresses(mnemonic!, - numberOfThreads: Platform.numberOfProcessors - isolates.length - 1); - Logging.instance.log("calling _generateAddressForChain recursively", - level: LogLevel.Info); - return _generateAddressForChain(chain, index); - } - return derivations["$index"]['address'] as String; - } else { - final node = await compute( - getBip32NodeWrapper, Tuple4(chain, index, mnemonic!, _network)); - return P2PKH(network: _network, data: PaymentData(pubkey: node.publicKey)) - .data - .address!; - } - } - - /// Increases the index for either the internal or external chain, depending on [chain]. - /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! - Future incrementAddressIndexForChain(int chain) async { - if (chain == 0) { - final newIndex = - DB.instance.get(boxName: walletId, key: 'receivingIndex') + - 1; - await DB.instance.put( - boxName: walletId, key: 'receivingIndex', value: newIndex); - } else { - // Here we assume chain == 1 since it can only be either 0 or 1 - final newIndex = - DB.instance.get(boxName: walletId, key: 'changeIndex') + 1; - await DB.instance - .put(boxName: walletId, key: 'changeIndex', value: newIndex); - } - } - - /// Adds [address] to the relevant chain's address array, which is determined by [chain]. - /// [address] - Expects a standard native segwit address - /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! - Future addToAddressesArrayForChain(String address, int chain) async { - String chainArray = ''; - if (chain == 0) { - chainArray = 'receivingAddresses'; - } else { - chainArray = 'changeAddresses'; - } - - final addressArray = - DB.instance.get(boxName: walletId, key: chainArray); - if (addressArray == null) { + Future _generateAddressForChain( + int chain, int index) async { + final _mnemonic = await mnemonicString; + final _mnemonicPassphrase = await mnemonicPassphrase; + if (_mnemonicPassphrase == null) { Logging.instance.log( - 'Attempting to add the following to array for chain $chain:${[ - address - ]}', - level: LogLevel.Info); - await DB.instance - .put(boxName: walletId, key: chainArray, value: [address]); - } else { - // Make a deep copy of the existing list - final List newArray = []; - addressArray - .forEach((dynamic _address) => newArray.add(_address as String)); - newArray.add(address); // Add the address passed into the method - await DB.instance - .put(boxName: walletId, key: chainArray, value: newArray); + "Exception in _generateAddressForChain: mnemonic passphrase null," + " possible migration issue; if using internal builds, delete " + "wallet and restore from seed, if using a release build, " + "please file bug report", + level: LogLevel.Error); } + + final derivePath = constructDerivePath( + networkWIF: _network.wif, + chain: chain, + index: index, + ); + + final node = await Bip32Utils.getBip32Node( + _mnemonic!, + _mnemonicPassphrase!, + _network, + derivePath, + ); + + final address = P2PKH( + network: _network, + data: PaymentData( + pubkey: node.publicKey, + ), + ).data.address!; + + return isar_models.Address( + walletId: walletId, + value: address, + publicKey: node.publicKey, + type: isar_models.AddressType.p2pkh, + derivationIndex: index, + derivationPath: isar_models.DerivationPath()..value = derivePath, + subType: chain == 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change, + ); } - /// Takes in a list of UtxoObjects and adds a name (dependent on object index within list) - /// and checks for the txid associated with the utxo being blocked and marks it accordingly. - /// Now also checks for output labeling. - Future _sortOutputs(List utxos) async { - final blockedHashArray = - DB.instance.get(boxName: walletId, key: 'blocked_tx_hashes') - as List?; - final List lst = []; - if (blockedHashArray != null) { - for (var hash in blockedHashArray) { - lst.add(hash as String); - } - } - final labels = - DB.instance.get(boxName: walletId, key: 'labels') as Map? ?? - {}; - - _outputsList = []; - - for (var i = 0; i < utxos.length; i++) { - if (labels[utxos[i].txid] != null) { - utxos[i].txName = labels[utxos[i].txid] as String? ?? ""; - } else { - utxos[i].txName = 'Output #$i'; - } - - if (utxos[i].status.confirmed == false) { - _outputsList.add(utxos[i]); - } else { - if (lst.contains(utxos[i].txid)) { - utxos[i].blocked = true; - _outputsList.add(utxos[i]); - } else if (!lst.contains(utxos[i].txid)) { - _outputsList.add(utxos[i]); - } - } - } - } + // /// Takes in a list of isar_models.UTXOs and adds a name (dependent on object index within list) + // /// and checks for the txid associated with the utxo being blocked and marks it accordingly. + // /// Now also checks for output labeling. + // Future _sortOutputs(List utxos) async { + // final blockedHashArray = + // DB.instance.get(boxName: walletId, key: 'blocked_tx_hashes') + // as List?; + // final List lst = []; + // if (blockedHashArray != null) { + // for (var hash in blockedHashArray) { + // lst.add(hash as String); + // } + // } + // final labels = + // DB.instance.get(boxName: walletId, key: 'labels') as Map? ?? + // {}; + // + // _outputsList = []; + // + // for (var i = 0; i < utxos.length; i++) { + // if (labels[utxos[i].txid] != null) { + // utxos[i].txName = labels[utxos[i].txid] as String? ?? ""; + // } else { + // utxos[i].txName = 'Output #$i'; + // } + // + // if (utxos[i].status.confirmed == false) { + // _outputsList.add(utxos[i]); + // } else { + // if (lst.contains(utxos[i].txid)) { + // utxos[i].blocked = true; + // _outputsList.add(utxos[i]); + // } else if (!lst.contains(utxos[i].txid)) { + // _outputsList.add(utxos[i]); + // } + // } + // } + // } @override Future fullRescan( @@ -3866,13 +3924,31 @@ class FiroWallet extends CoinServiceAPI { await _cachedElectrumXClient.clearSharedTransactionCache(coin: coin); // back up data - await _rescanBackup(); + // await _rescanBackup(); + + // clear blockchain info + await db.deleteWalletBlockchainData(walletId); + await _deleteDerivations(); try { - final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic'); - await _recoverWalletFromBIP32SeedPhrase(mnemonic!, maxUnusedAddressGap); + final _mnemonic = await mnemonicString; + final _mnemonicPassphrase = await mnemonicPassphrase; + if (_mnemonicPassphrase == null) { + Logging.instance.log( + "Exception in fullRescan: mnemonic passphrase null, possible migration issue; if using internal builds, delete wallet and restore from seed, if using a release build, please file bug report", + level: LogLevel.Error); + } + + await _recoverWalletFromBIP32SeedPhrase( + _mnemonic!, + _mnemonicPassphrase!, + maxUnusedAddressGap, + maxNumberOfIndexesToCheck, + true, + ); longMutex = false; + await refresh(); Logging.instance.log("Full rescan complete!", level: LogLevel.Info); GlobalEventBus.instance.fire( WalletSyncStatusChangedEvent( @@ -3891,7 +3967,7 @@ class FiroWallet extends CoinServiceAPI { ); // restore from backup - await _rescanRestore(); + // await _rescanRestore(); longMutex = false; Logging.instance.log("Exception rethrown from fullRescan(): $e\n$s", @@ -3900,155 +3976,17 @@ class FiroWallet extends CoinServiceAPI { } } - Future _rescanBackup() async { - Logging.instance.log("starting rescan backup", level: LogLevel.Info); - - // backup current and clear data - final tempReceivingAddresses = - DB.instance.get(boxName: walletId, key: 'receivingAddresses'); - await DB.instance.delete( - key: 'receivingAddresses', - boxName: walletId, - ); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddresses_BACKUP', - value: tempReceivingAddresses); - - final tempChangeAddresses = - DB.instance.get(boxName: walletId, key: 'changeAddresses'); - await DB.instance.delete( - key: 'changeAddresses', - boxName: walletId, - ); - await DB.instance.put( - boxName: walletId, - key: 'changeAddresses_BACKUP', - value: tempChangeAddresses); - - final tempReceivingIndex = - DB.instance.get(boxName: walletId, key: 'receivingIndex'); - await DB.instance.delete( - key: 'receivingIndex', - boxName: walletId, - ); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndex_BACKUP', - value: tempReceivingIndex); - - final tempChangeIndex = - DB.instance.get(boxName: walletId, key: 'changeIndex'); - await DB.instance.delete( - key: 'changeIndex', - boxName: walletId, - ); - await DB.instance.put( - boxName: walletId, key: 'changeIndex_BACKUP', value: tempChangeIndex); - - final receiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivations"); - final changeDerivationsString = - await _secureStore.read(key: "${walletId}_changeDerivations"); - - await _secureStore.write( - key: "${walletId}_receiveDerivations_BACKUP", - value: receiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivations_BACKUP", - value: changeDerivationsString); - - await _secureStore.write( - key: "${walletId}_receiveDerivations", value: null); - await _secureStore.write(key: "${walletId}_changeDerivations", value: null); - - // back up but no need to delete - final tempMintIndex = - DB.instance.get(boxName: walletId, key: 'mintIndex'); - await DB.instance.put( - boxName: walletId, key: 'mintIndex_BACKUP', value: tempMintIndex); - - final tempLelantusCoins = - DB.instance.get(boxName: walletId, key: '_lelantus_coins'); - await DB.instance.put( - boxName: walletId, - key: '_lelantus_coins_BACKUP', - value: tempLelantusCoins); - - final tempJIndex = - DB.instance.get(boxName: walletId, key: 'jindex'); - await DB.instance.put( - boxName: walletId, key: 'jindex_BACKUP', value: tempJIndex); - - final tempLelantusTxModel = DB.instance - .get(boxName: walletId, key: 'latest_lelantus_tx_model'); - await DB.instance.put( - boxName: walletId, - key: 'latest_lelantus_tx_model_BACKUP', - value: tempLelantusTxModel); - - Logging.instance.log("rescan backup complete", level: LogLevel.Info); - } - - Future _rescanRestore() async { - Logging.instance.log("starting rescan restore", level: LogLevel.Info); - - // restore from backup - final tempReceivingAddresses = DB.instance - .get(boxName: walletId, key: 'receivingAddresses_BACKUP'); - final tempChangeAddresses = DB.instance - .get(boxName: walletId, key: 'changeAddresses_BACKUP'); - final tempReceivingIndex = DB.instance - .get(boxName: walletId, key: 'receivingIndex_BACKUP'); - final tempChangeIndex = - DB.instance.get(boxName: walletId, key: 'changeIndex_BACKUP'); - final tempMintIndex = - DB.instance.get(boxName: walletId, key: 'mintIndex_BACKUP'); - final tempLelantusCoins = DB.instance - .get(boxName: walletId, key: '_lelantus_coins_BACKUP'); - final tempJIndex = - DB.instance.get(boxName: walletId, key: 'jindex_BACKUP'); - final tempLelantusTxModel = DB.instance.get( - boxName: walletId, key: 'latest_lelantus_tx_model_BACKUP'); - - final receiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivations_BACKUP"); - final changeDerivationsString = - await _secureStore.read(key: "${walletId}_changeDerivations_BACKUP"); - - await _secureStore.write( - key: "${walletId}_receiveDerivations", value: receiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivations", value: changeDerivationsString); - - await DB.instance.put( - boxName: walletId, - key: 'receivingAddresses', - value: tempReceivingAddresses); - await DB.instance.put( - boxName: walletId, key: 'changeAddresses', value: tempChangeAddresses); - await DB.instance.put( - boxName: walletId, key: 'receivingIndex', value: tempReceivingIndex); - await DB.instance.put( - boxName: walletId, key: 'changeIndex', value: tempChangeIndex); - await DB.instance.put( - boxName: walletId, key: 'mintIndex', value: tempMintIndex); - await DB.instance.put( - boxName: walletId, key: '_lelantus_coins', value: tempLelantusCoins); - await DB.instance - .put(boxName: walletId, key: 'jindex', value: tempJIndex); - await DB.instance.put( - boxName: walletId, - key: 'latest_lelantus_tx_model', - value: tempLelantusTxModel); - - Logging.instance.log("rescan restore complete", level: LogLevel.Info); + Future _deleteDerivations() async { + // P2PKH derivations + await _secureStore.delete(key: "${walletId}_receiveDerivations"); + await _secureStore.delete(key: "${walletId}_changeDerivations"); } /// wrapper for _recoverWalletFromBIP32SeedPhrase() @override Future recoverFromMnemonic({ required String mnemonic, + String? mnemonicPassphrase, required int maxUnusedAddressGap, required int maxNumberOfIndexesToCheck, required int height, @@ -4089,13 +4027,24 @@ class FiroWallet extends CoinServiceAPI { // } } // this should never fail - if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) { + if ((await mnemonicString) != null || + (await this.mnemonicPassphrase) != null) { + longMutex = false; throw Exception("Attempted to overwrite mnemonic on restore!"); } await _secureStore.write( key: '${_walletId}_mnemonic', value: mnemonic.trim()); + await _secureStore.write( + key: '${_walletId}_mnemonicPassphrase', + value: mnemonicPassphrase ?? "", + ); await _recoverWalletFromBIP32SeedPhrase( - mnemonic.trim(), maxUnusedAddressGap); + mnemonic.trim(), + mnemonicPassphrase ?? "", + maxUnusedAddressGap, + maxNumberOfIndexesToCheck, + false, + ); await compute( _setTestnetWrapper, @@ -4129,140 +4078,306 @@ class FiroWallet extends CoinServiceAPI { return setDataMap; } - Future _makeDerivations( - String suppliedMnemonic, int maxUnusedAddressGap) async { - List receivingAddressArray = []; - List changeAddressArray = []; + Future> _getBatchTxCount({ + required Map addresses, + }) async { + try { + final Map> args = {}; + for (final entry in addresses.entries) { + args[entry.key] = [ + AddressUtils.convertToScriptHash(entry.value, _network) + ]; + } + final response = await electrumXClient.getBatchHistory(args: args); - int receivingIndex = -1; - int changeIndex = -1; + final Map result = {}; + for (final entry in response.entries) { + result[entry.key] = entry.value.length; + } + return result; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown in _getBatchTxCount(address: $addresses: $e\n$s", + level: LogLevel.Error); + rethrow; + } + } - // The gap limit will be capped at 20 - int receivingGapCounter = 0; - int changeGapCounter = 0; + Future, int>> _checkGaps( + int maxNumberOfIndexesToCheck, + int maxUnusedAddressGap, + int txCountBatchSize, + bip32.BIP32 root, + int chain, + ) async { + List addressArray = []; + int gapCounter = 0; + int highestIndexWithHistory = 0; - await fillAddresses(suppliedMnemonic, - numberOfThreads: Platform.numberOfProcessors - isolates.length - 1); + for (int index = 0; + index < maxNumberOfIndexesToCheck && gapCounter < maxUnusedAddressGap; + index += txCountBatchSize) { + List iterationsAddressArray = []; + Logging.instance.log( + "index: $index, \t GapCounter $chain: $gapCounter", + level: LogLevel.Info, + ); - final receiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivations"); - final changeDerivationsString = - await _secureStore.read(key: "${walletId}_changeDerivations"); + final _id = "k_$index"; + Map txCountCallArgs = {}; - final receiveDerivations = Map.from( - jsonDecode(receiveDerivationsString ?? "{}") as Map); - final changeDerivations = Map.from( - jsonDecode(changeDerivationsString ?? "{}") as Map); + for (int j = 0; j < txCountBatchSize; j++) { + final derivePath = constructDerivePath( + networkWIF: root.network.wif, + chain: chain, + index: index + j, + ); + final node = await Bip32Utils.getBip32NodeFromRoot(root, derivePath); - // log("rcv: $receiveDerivations"); - // log("chg: $changeDerivations"); + final data = PaymentData(pubkey: node.publicKey); + final String addressString = P2PKH( + data: data, + network: _network, + ).data.address!; + const isar_models.AddressType addrType = isar_models.AddressType.p2pkh; - // Deriving and checking for receiving addresses - for (var i = 0; i < receiveDerivations.length; i++) { - // Break out of loop when receivingGapCounter hits maxUnusedAddressGap - // Same gap limit for change as for receiving, breaks when it hits maxUnusedAddressGap - if (receivingGapCounter >= maxUnusedAddressGap && - changeGapCounter >= maxUnusedAddressGap) { - break; + final address = isar_models.Address( + walletId: walletId, + value: addressString, + publicKey: node.publicKey, + type: addrType, + derivationIndex: index + j, + derivationPath: isar_models.DerivationPath()..value = derivePath, + subType: chain == 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change, + ); + + addressArray.add(address); + + txCountCallArgs.addAll({ + "${_id}_$j": addressString, + }); } - final receiveDerivation = receiveDerivations["$i"]; - final address = receiveDerivation['address'] as String; + // get address tx counts + final counts = await _getBatchTxCount(addresses: txCountCallArgs); - final changeDerivation = changeDerivations["$i"]; - final _address = changeDerivation['address'] as String; - Future? futureNumTxs; - Future? _futureNumTxs; - if (receivingGapCounter < maxUnusedAddressGap) { - futureNumTxs = _getReceivedTxCount(address: address); - } - if (changeGapCounter < maxUnusedAddressGap) { - _futureNumTxs = _getReceivedTxCount(address: _address); - } - try { - if (futureNumTxs != null) { - int numTxs = await futureNumTxs; - if (numTxs >= 1) { - receivingIndex = i; - receivingAddressArray.add(address); - } else if (numTxs == 0) { - receivingGapCounter += 1; - } + // check and add appropriate addresses + for (int k = 0; k < txCountBatchSize; k++) { + int count = counts["${_id}_$k"]!; + if (count > 0) { + iterationsAddressArray.add(txCountCallArgs["${_id}_$k"]!); + + // update highest + highestIndexWithHistory = index + k; + + // reset counter + gapCounter = 0; } - } catch (e, s) { - Logging.instance.log( - "Exception rethrown from recoverWalletFromBIP32SeedPhrase(): $e\n$s", - level: LogLevel.Error); - rethrow; - } - try { - if (_futureNumTxs != null) { - int numTxs = await _futureNumTxs; - if (numTxs >= 1) { - changeIndex = i; - changeAddressArray.add(_address); - } else if (numTxs == 0) { - changeGapCounter += 1; - } + // increase counter when no tx history found + if (count == 0) { + gapCounter++; } - } catch (e, s) { - Logging.instance.log( - "Exception rethrown from recoverWalletFromBIP32SeedPhrase(): $e\n$s", - level: LogLevel.Error); - rethrow; } + // cache all the transactions while waiting for the current function to finish. + unawaited(getTransactionCacheEarly(iterationsAddressArray)); } + return Tuple2(addressArray, highestIndexWithHistory); + } - // If restoring a wallet that never received any funds, then set receivingArray manually - // If we didn't do this, it'd store an empty array - if (receivingIndex == -1) { - final String receivingAddress = await _generateAddressForChain(0, 0); - receivingAddressArray.add(receivingAddress); + Future getTransactionCacheEarly(List allAddresses) async { + try { + final List> allTxHashes = + await _fetchHistory(allAddresses); + for (final txHash in allTxHashes) { + try { + unawaited(cachedElectrumXClient.getTransaction( + txHash: txHash["tx_hash"] as String, + verbose: true, + coin: coin, + )); + } catch (e) { + continue; + } + } + } catch (e) { + // } + } - // If restoring a wallet that never sent any funds with change, then set changeArray - // manually. If we didn't do this, it'd store an empty array. - if (changeIndex == -1) { - final String changeAddress = await _generateAddressForChain(1, 0); - changeAddressArray.add(changeAddress); + Future _recoverHistory( + String suppliedMnemonic, + String mnemonicPassphrase, + int maxUnusedAddressGap, + int maxNumberOfIndexesToCheck, + bool isRescan, + ) async { + final root = await Bip32Utils.getBip32Root( + suppliedMnemonic, + mnemonicPassphrase, + _network, + ); + + final List, int>>> receiveFutures = + []; + final List, int>>> changeFutures = + []; + + const receiveChain = 0; + const changeChain = 1; + const indexZero = 0; + + // actual size is 36 due to p2pkh, p2sh, and p2wpkh so 12x3 + const txCountBatchSize = 12; + + try { + // receiving addresses + Logging.instance.log( + "checking receiving addresses...", + level: LogLevel.Info, + ); + + receiveFutures.add( + _checkGaps( + maxNumberOfIndexesToCheck, + maxUnusedAddressGap, + txCountBatchSize, + root, + receiveChain, + ), + ); + + // change addresses + Logging.instance.log( + "checking change addresses...", + level: LogLevel.Info, + ); + changeFutures.add( + _checkGaps( + maxNumberOfIndexesToCheck, + maxUnusedAddressGap, + txCountBatchSize, + root, + changeChain, + ), + ); + + // io limitations may require running these linearly instead + final futuresResult = await Future.wait([ + Future.wait(receiveFutures), + Future.wait(changeFutures), + ]); + + final receiveResults = futuresResult[0]; + final changeResults = futuresResult[1]; + + final List addressesToStore = []; + + int highestReceivingIndexWithHistory = 0; + // If restoring a wallet that never received any funds, then set receivingArray manually + // If we didn't do this, it'd store an empty array + for (final tuple in receiveResults) { + if (tuple.item1.isEmpty) { + final address = await _generateAddressForChain( + receiveChain, + indexZero, + ); + addressesToStore.add(address); + } else { + highestReceivingIndexWithHistory = + max(tuple.item2, highestReceivingIndexWithHistory); + addressesToStore.addAll(tuple.item1); + } + } + + int highestChangeIndexWithHistory = 0; + // If restoring a wallet that never sent any funds with change, then set changeArray + // manually. If we didn't do this, it'd store an empty array. + for (final tuple in changeResults) { + if (tuple.item1.isEmpty) { + final address = await _generateAddressForChain( + changeChain, + indexZero, + ); + addressesToStore.add(address); + } else { + highestChangeIndexWithHistory = + max(tuple.item2, highestChangeIndexWithHistory); + addressesToStore.addAll(tuple.item1); + } + } + + // remove extra addresses to help minimize risk of creating a large gap + addressesToStore.removeWhere((e) => + e.subType == isar_models.AddressSubType.change && + e.derivationIndex > highestChangeIndexWithHistory); + addressesToStore.removeWhere((e) => + e.subType == isar_models.AddressSubType.receiving && + e.derivationIndex > highestReceivingIndexWithHistory); + + if (isRescan) { + await db.updateOrPutAddresses(addressesToStore); + } else { + await db.putAddresses(addressesToStore); + } + + await Future.wait([ + _refreshTransactions(), + _refreshUTXOs(), + ]); + + await Future.wait([ + updateCachedId(walletId), + updateCachedIsFavorite(false), + ]); + + longMutex = false; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _recoverWalletFromBIP32SeedPhrase(): $e\n$s", + level: LogLevel.Error); + + longMutex = false; + rethrow; } - - await DB.instance.put( - boxName: walletId, - key: 'receivingAddresses', - value: receivingAddressArray); - await DB.instance.put( - boxName: walletId, key: 'changeAddresses', value: changeAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndex', - value: receivingIndex == -1 ? 0 : receivingIndex); - await DB.instance.put( - boxName: walletId, - key: 'changeIndex', - value: changeIndex == -1 ? 0 : changeIndex); } /// Recovers wallet from [suppliedMnemonic]. Expects a valid mnemonic. Future _recoverWalletFromBIP32SeedPhrase( - String suppliedMnemonic, int maxUnusedAddressGap) async { + String suppliedMnemonic, + String mnemonicPassphrase, + int maxUnusedAddressGap, + int maxNumberOfIndexesToCheck, + bool isRescan, + ) async { longMutex = true; Logging.instance .log("PROCESSORS ${Platform.numberOfProcessors}", level: LogLevel.Info); try { final latestSetId = await getLatestSetId(); final setDataMap = getSetDataMap(latestSetId); + final usedSerialNumbers = getUsedCoinSerials(); - final makeDerivations = - _makeDerivations(suppliedMnemonic, maxUnusedAddressGap); + final generateAndCheckAddresses = _recoverHistory( + suppliedMnemonic, + mnemonicPassphrase, + maxUnusedAddressGap, + maxNumberOfIndexesToCheck, + isRescan, + ); - await DB.instance - .put(boxName: walletId, key: "id", value: _walletId); - await DB.instance - .put(boxName: walletId, key: "isFavorite", value: false); + await Future.wait([ + updateCachedId(walletId), + updateCachedIsFavorite(false), + ]); - await Future.wait([usedSerialNumbers, setDataMap, makeDerivations]); + await Future.wait([ + usedSerialNumbers, + setDataMap, + generateAndCheckAddresses, + ]); await _restore(latestSetId, await setDataMap, await usedSerialNumbers); longMutex = false; @@ -4275,16 +4390,20 @@ class FiroWallet extends CoinServiceAPI { } } - Future _restore(int latestSetId, Map setDataMap, - dynamic usedSerialNumbers) async { - final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic'); - final dataFuture = _txnData; - final String currency = _prefs.currency; - final Decimal currentPrice = await firoPrice; + Future _restore( + int latestSetId, + Map setDataMap, + List usedSerialNumbers, + ) async { + final _mnemonic = await mnemonicString; + final _mnemonicPassphrase = await mnemonicPassphrase; + + final dataFuture = _refreshTransactions(); ReceivePort receivePort = await getIsolate({ "function": "restore", - "mnemonic": mnemonic, + "mnemonic": _mnemonic, + "mnemonicPassphrase": _mnemonicPassphrase, "coin": coin, "latestSetId": latestSetId, "setDataMap": setDataMap, @@ -4303,40 +4422,65 @@ class FiroWallet extends CoinServiceAPI { stop(receivePort); final message = await staticProcessRestore( - (await dataFuture), result as Map); + (await _txnData), + result as Map, + await chainHeight, + ); - await DB.instance.put( - boxName: walletId, key: 'mintIndex', value: message['mintIndex']); - await DB.instance.put( - boxName: walletId, - key: '_lelantus_coins', - value: message['_lelantus_coins']); - await DB.instance.put( - boxName: walletId, key: 'jindex', value: message['jindex']); + await Future.wait([ + firoUpdateMintIndex(message['mintIndex'] as int), + firoUpdateLelantusCoins(message['_lelantus_coins'] as List), + firoUpdateJIndex(message['jindex'] as List), + ]); final transactionMap = - message["newTxMap"] as Map; + message["newTxMap"] as Map; + Map> data = + {}; + + for (final entry in transactionMap.entries) { + data[entry.key] = Tuple2(entry.value.address.value, entry.value); + } // Create the joinsplit transactions. final spendTxs = await getJMintTransactions( - _cachedElectrumXClient, - message["spendTxIds"] as List, - currency, - coin, - currentPrice, - (Platform.isWindows ? "en_US" : await Devicelocale.currentLocale)!); + _cachedElectrumXClient, + message["spendTxIds"] as List, + coin, + ); Logging.instance.log(spendTxs, level: LogLevel.Info); - for (var element in spendTxs) { - transactionMap[element.txid] = element; + + for (var element in spendTxs.entries) { + final address = element.value.address.value ?? + data[element.value.txid]?.item1 ?? + element.key; + // isar_models.Address( + // walletId: walletId, + // value: transactionInfo["address"] as String, + // derivationIndex: -1, + // type: isar_models.AddressType.nonWallet, + // subType: isar_models.AddressSubType.nonWallet, + // publicKey: [], + // ); + + data[element.value.txid] = Tuple2(address, element.value); } - final models.TransactionData newTxData = - models.TransactionData.fromMap(transactionMap); + final List> txnsData = + []; - _lelantusTransactionData = Future(() => newTxData); + for (final value in data.values) { + final transactionAddress = value.item1!; + final outs = + value.item2.outputs.where((_) => true).toList(growable: false); + final ins = value.item2.inputs.where((_) => true).toList(growable: false); - await DB.instance.put( - boxName: walletId, key: 'latest_lelantus_tx_model', value: newTxData); + txnsData.add(Tuple2( + value.item2.copyWith(inputs: ins, outputs: outs).item1, + transactionAddress)); + } + + await db.addNewTransactionData(txnsData, walletId); } Future>> fetchAnonymitySets() async { @@ -4369,28 +4513,30 @@ class FiroWallet extends CoinServiceAPI { Future _createJoinSplitTransaction( int spendAmount, String address, bool subtractFeeFromAmount) async { - final price = await firoPrice; - final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic'); - final index = DB.instance.get(boxName: walletId, key: 'mintIndex'); + final _mnemonic = await mnemonicString; + final _mnemonicPassphrase = await mnemonicPassphrase; + final index = firoGetMintIndex(); final lelantusEntry = await _getLelantusEntry(); final anonymitySets = await fetchAnonymitySets(); final locktime = await getBlockHead(electrumXClient); - final locale = Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; + // final locale = + // Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; ReceivePort receivePort = await getIsolate({ "function": "createJoinSplit", "spendAmount": spendAmount, "address": address, "subtractFeeFromAmount": subtractFeeFromAmount, - "mnemonic": mnemonic, + "mnemonic": _mnemonic, + "mnemonicPassphrase": _mnemonicPassphrase, "index": index, - "price": price, + // "price": price, "lelantusEntries": lelantusEntry, "locktime": locktime, "coin": coin, "network": _network, "_anonymity_sets": anonymitySets, - "locale": locale, + // "locale": locale, }); var message = await receivePort.first; if (message is String) { @@ -4419,11 +4565,13 @@ class FiroWallet extends CoinServiceAPI { } } - Future> getUsedCoinSerials() async { + Future> getUsedCoinSerials() async { try { final response = await cachedElectrumXClient.getUsedCoinSerials( coin: coin, ); + print("getUsedCoinSerials"); + print(response); return response; } catch (e, s) { Logging.instance.log("Exception rethrown in firo_wallet.dart: $e\n$s", @@ -4544,7 +4692,7 @@ class FiroWallet extends CoinServiceAPI { int spendAmount, ) async { var lelantusEntry = await _getLelantusEntry(); - final balance = await availableBalance; + final balance = availablePrivateBalance().decimal; int spendAmount = (balance * Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); @@ -4601,47 +4749,60 @@ class FiroWallet extends CoinServiceAPI { // return fee; @override - Future estimateFeeFor(int satoshiAmount, int feeRate) async { - int fee = await estimateJoinSplitFee(satoshiAmount); - return fee; + Future estimateFeeFor(Amount amount, int feeRate) async { + int fee = await estimateJoinSplitFee(amount.raw.toInt()); + return Amount(rawValue: BigInt.from(fee), fractionDigits: coin.decimals); } - Future estimateFeeForPublic(int satoshiAmount, int feeRate) async { - final available = - Format.decimalAmountToSatoshis(await availablePublicBalance(), coin); + Future estimateFeeForPublic(Amount amount, int feeRate) async { + final available = balance.spendable; - if (available == satoshiAmount) { - return satoshiAmount - sweepAllEstimate(feeRate); - } else if (satoshiAmount <= 0 || satoshiAmount > available) { + if (available == amount) { + return amount - (await sweepAllEstimate(feeRate)); + } else if (amount <= Amount.zero || amount > available) { return roughFeeEstimate(1, 2, feeRate); } - int runningBalance = 0; + Amount runningBalance = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); int inputCount = 0; - for (final output in _outputsList) { - runningBalance += output.value; - inputCount++; - if (runningBalance > satoshiAmount) { - break; + for (final output in (await utxos)) { + if (!output.isBlocked) { + runningBalance = runningBalance + + Amount( + rawValue: BigInt.from(output.value), + fractionDigits: coin.decimals, + ); + inputCount++; + if (runningBalance > amount) { + break; + } } } final oneOutPutFee = roughFeeEstimate(inputCount, 1, feeRate); final twoOutPutFee = roughFeeEstimate(inputCount, 2, feeRate); - if (runningBalance - satoshiAmount > oneOutPutFee) { - if (runningBalance - satoshiAmount > oneOutPutFee + DUST_LIMIT) { - final change = runningBalance - satoshiAmount - twoOutPutFee; - if (change > DUST_LIMIT && - runningBalance - satoshiAmount - change == twoOutPutFee) { - return runningBalance - satoshiAmount - change; + final dustLimitAmount = Amount( + rawValue: BigInt.from(DUST_LIMIT), + fractionDigits: coin.decimals, + ); + + if (runningBalance - amount > oneOutPutFee) { + if (runningBalance - amount > oneOutPutFee + dustLimitAmount) { + final change = runningBalance - amount - twoOutPutFee; + if (change > dustLimitAmount && + runningBalance - amount - change == twoOutPutFee) { + return runningBalance - amount - change; } else { - return runningBalance - satoshiAmount; + return runningBalance - amount; } } else { - return runningBalance - satoshiAmount; + return runningBalance - amount; } - } else if (runningBalance - satoshiAmount == oneOutPutFee) { + } else if (runningBalance - amount == oneOutPutFee) { return oneOutPutFee; } else { return twoOutPutFee; @@ -4649,16 +4810,20 @@ class FiroWallet extends CoinServiceAPI { } // TODO: correct formula for firo? - int roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { - return ((181 * inputCount) + (34 * outputCount) + 10) * - (feeRatePerKB / 1000).ceil(); + Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { + return Amount( + rawValue: BigInt.from(((181 * inputCount) + (34 * outputCount) + 10) * + (feeRatePerKB / 1000).ceil()), + fractionDigits: coin.decimals, + ); } - int sweepAllEstimate(int feeRate) { + Future sweepAllEstimate(int feeRate) async { int available = 0; int inputCount = 0; - for (final output in _outputsList) { - if (output.status.confirmed) { + for (final output in (await utxos)) { + if (!output.isBlocked && + output.isConfirmed(storedChainHeight, MINIMUM_CONFIRMATIONS)) { available += output.value; inputCount++; } @@ -4667,7 +4832,11 @@ class FiroWallet extends CoinServiceAPI { // transaction will only have 1 output minus the fee final estimatedFee = roughFeeEstimate(inputCount, 1, feeRate); - return available - estimatedFee; + return Amount( + rawValue: BigInt.from(available), + fractionDigits: coin.decimals, + ) - + estimatedFee; } Future>> fastFetch(List allTxHashes) async { @@ -4713,16 +4882,17 @@ class FiroWallet extends CoinServiceAPI { return allTransactions; } - Future> getJMintTransactions( + Future> + getJMintTransactions( CachedElectrumX cachedClient, List transactions, - String currency, + // String currency, Coin coin, - Decimal currentPrice, - String locale, + // Decimal currentPrice, + // String locale, ) async { try { - List txs = []; + Map txs = {}; List> allTransactions = await fastFetch(transactions); @@ -4730,35 +4900,59 @@ class FiroWallet extends CoinServiceAPI { try { final tx = allTransactions[i]; - tx["confirmed_status"] = - tx["confirmations"] != null && tx["confirmations"] as int > 0; - tx["timestamp"] = tx["time"]; - tx["txType"] = "Sent"; - var sendIndex = 1; if (tx["vout"][0]["value"] != null && Decimal.parse(tx["vout"][0]["value"].toString()) > Decimal.zero) { sendIndex = 0; } tx["amount"] = tx["vout"][sendIndex]["value"]; - tx["address"] = tx["vout"][sendIndex]["scriptPubKey"]["addresses"][0]; - tx["fees"] = tx["vin"][0]["nFees"]; - tx["inputSize"] = tx["vin"].length; - tx["outputSize"] = tx["vout"].length; - final decimalAmount = Decimal.parse(tx["amount"].toString()); - - tx["worthNow"] = Format.localizedStringAsFixed( - value: currentPrice * decimalAmount, - locale: locale, - decimalPlaces: 2, + final Amount amount = Amount.fromDecimal( + Decimal.parse(tx["amount"].toString()), + fractionDigits: coin.decimals, ); - tx["worthAtBlockTimestamp"] = tx["worthNow"]; - tx["subType"] = "join"; - txs.add(models.Transaction.fromLelantusJson(tx)); + final txn = isar_models.Transaction( + walletId: walletId, + txid: tx["txid"] as String, + timestamp: tx["time"] as int? ?? + (DateTime.now().millisecondsSinceEpoch ~/ 1000), + type: isar_models.TransactionType.outgoing, + subType: isar_models.TransactionSubType.join, + amount: amount.raw.toInt(), + amountString: amount.toJsonString(), + fee: Amount.fromDecimal( + Decimal.parse(tx["fees"].toString()), + fractionDigits: coin.decimals, + ).raw.toInt(), + height: tx["height"] as int?, + isCancelled: false, + isLelantus: true, + slateId: null, + otherData: null, + nonce: null, + inputs: [], + outputs: [], + ); + + final address = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(tx["address"] as String) + .findFirst() ?? + isar_models.Address( + walletId: walletId, + value: tx["address"] as String, + derivationIndex: -2, + derivationPath: null, + type: isar_models.AddressType.nonWallet, + subType: isar_models.AddressSubType.unknown, + publicKey: [], + ); + + txs[address] = txn; } catch (e, s) { Logging.instance.log( "Exception caught in getJMintTransactions(): $e\n$s", @@ -4778,17 +4972,18 @@ class FiroWallet extends CoinServiceAPI { @override Future generateNewAddress() async { try { - await incrementAddressIndexForChain( - 0); // First increment the receiving index - final newReceivingIndex = - DB.instance.get(boxName: walletId, key: 'receivingIndex') - as int; // Check the new receiving index - final newReceivingAddress = await _generateAddressForChain(0, - newReceivingIndex); // Use new index to derive a new receiving address - await addToAddressesArrayForChain(newReceivingAddress, - 0); // Add that new receiving address to the array of receiving addresses - _currentReceivingAddress = Future(() => - newReceivingAddress); // Set the new receiving address that the service + final currentReceiving = await _currentReceivingAddress; + + final newReceivingIndex = currentReceiving.derivationIndex + 1; + + // Use new index to derive a new receiving address + final newReceivingAddress = await _generateAddressForChain( + 0, + newReceivingIndex, + ); + + // Add that new receiving address + await db.putAddress(newReceivingAddress); return true; } catch (e, s) { @@ -4799,11 +4994,60 @@ class FiroWallet extends CoinServiceAPI { } } - Future availablePrivateBalance() async { - return (await balances)[0]; + Amount availablePrivateBalance() { + return balancePrivate.spendable; } - Future availablePublicBalance() async { - return (await balances)[4]; + Amount availablePublicBalance() { + return balance.spendable; + } + + Future get chainHeight async { + try { + final result = await _electrumXClient.getBlockHeadTip(); + final height = result["height"] as int; + await updateCachedChainHeight(height); + if (height > storedChainHeight) { + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "Updated current chain height in $walletId $walletName!", + walletId, + ), + ); + } + return height; + } catch (e, s) { + Logging.instance.log("Exception caught in chainHeight: $e\n$s", + level: LogLevel.Error); + return storedChainHeight; + } + } + + @override + int get storedChainHeight => getCachedChainHeight(); + + @override + Balance get balance => _balance ??= getCachedBalance(); + Balance? _balance; + + Balance get balancePrivate => _balancePrivate ??= getCachedBalanceSecondary(); + Balance? _balancePrivate; + + @override + Future> get utxos => db.getUTXOs(walletId).findAll(); + + @override + Future> get transactions => + db.getTransactions(walletId).findAll(); + + @override + Future get xpub async { + final node = await Bip32Utils.getBip32Root( + (await mnemonic).join(" "), + await mnemonicPassphrase ?? "", + _network, + ); + + return node.neutered().toBase58(); } } diff --git a/lib/services/coins/litecoin/litecoin_wallet.dart b/lib/services/coins/litecoin/litecoin_wallet.dart index 269f59610..5e1e8064c 100644 --- a/lib/services/coins/litecoin/litecoin_wallet.dart +++ b/lib/services/coins/litecoin/litecoin_wallet.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'dart:typed_data'; import 'package:bech32/bech32.dart'; import 'package:bip32/bip32.dart' as bip32; @@ -10,82 +9,67 @@ import 'package:bitcoindart/bitcoindart.dart'; import 'package:bs58check/bs58check.dart' as bs58check; import 'package:crypto/crypto.dart'; import 'package:decimal/decimal.dart'; -import 'package:devicelocale/devicelocale.dart'; import 'package:flutter/foundation.dart'; -import 'package:http/http.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; -import 'package:stackwallet/hive/db.dart'; -import 'package:stackwallet/models/models.dart' as models; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; import 'package:stackwallet/models/paymint/fee_object_model.dart'; -import 'package:stackwallet/models/paymint/transactions_model.dart'; -import 'package:stackwallet/models/paymint/utxo_model.dart'; +import 'package:stackwallet/models/signing_data.dart'; import 'package:stackwallet/services/coins/coin_service.dart'; import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +import 'package:stackwallet/services/mixins/coin_control_interface.dart'; +import 'package:stackwallet/services/mixins/electrum_x_parsing.dart'; +import 'package:stackwallet/services/mixins/wallet_cache.dart'; +import 'package:stackwallet/services/mixins/wallet_db.dart'; +import 'package:stackwallet/services/mixins/xpubable.dart'; import 'package:stackwallet/services/node_service.dart'; -import 'package:stackwallet/services/notifications_api.dart'; -import 'package:stackwallet/services/price.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; -import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/bip32_utils.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/default_nodes.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart'; import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; +import 'package:stackwallet/widgets/crypto_notifications.dart'; import 'package:tuple/tuple.dart'; import 'package:uuid/uuid.dart'; const int MINIMUM_CONFIRMATIONS = 1; -const int DUST_LIMIT = 294; +final Amount DUST_LIMIT = Amount( + rawValue: BigInt.from(294), + fractionDigits: Coin.particl.decimals, +); +final Amount DUST_LIMIT_P2PKH = Amount( + rawValue: BigInt.from(546), + fractionDigits: Coin.particl.decimals, +); const String GENESIS_HASH_MAINNET = "12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2"; const String GENESIS_HASH_TESTNET = "4966625a4b2851d9fdee139e56211a0d88575f59ed816ff5e6a63deb4e3e29a0"; -enum DerivePathType { bip44, bip49, bip84 } - -bip32.BIP32 getBip32Node( - int chain, - int index, - String mnemonic, - NetworkType network, - DerivePathType derivePathType, -) { - final root = getBip32Root(mnemonic, network); - - final node = getBip32NodeFromRoot(chain, index, root, derivePathType); - return node; -} - -/// wrapper for compute() -bip32.BIP32 getBip32NodeWrapper( - Tuple5 args, -) { - return getBip32Node( - args.item1, - args.item2, - args.item3, - args.item4, - args.item5, - ); -} - -bip32.BIP32 getBip32NodeFromRoot( - int chain, - int index, - bip32.BIP32 root, - DerivePathType derivePathType, -) { +String constructDerivePath({ + required DerivePathType derivePathType, + required int networkWIF, + int account = 0, + required int chain, + required int index, +}) { String coinType; - switch (root.network.wif) { + switch (networkWIF) { case 0xb0: // ltc mainnet wif coinType = "2"; // ltc mainnet break; @@ -93,59 +77,69 @@ bip32.BIP32 getBip32NodeFromRoot( coinType = "1"; // ltc testnet break; default: - throw Exception("Invalid Litecoin network type used!"); + throw Exception("Invalid Litecoin network wif used!"); } + + int purpose; switch (derivePathType) { case DerivePathType.bip44: - return root.derivePath("m/44'/$coinType'/0'/$chain/$index"); + purpose = 44; + break; case DerivePathType.bip49: - return root.derivePath("m/49'/$coinType'/0'/$chain/$index"); + purpose = 49; + break; case DerivePathType.bip84: - return root.derivePath("m/84'/$coinType'/0'/$chain/$index"); + purpose = 84; + break; default: - throw Exception("DerivePathType must not be null."); + throw Exception("DerivePathType $derivePathType not supported"); } + + return "m/$purpose'/$coinType'/$account'/$chain/$index"; } -/// wrapper for compute() -bip32.BIP32 getBip32NodeFromRootWrapper( - Tuple4 args, -) { - return getBip32NodeFromRoot( - args.item1, - args.item2, - args.item3, - args.item4, - ); -} +class LitecoinWallet extends CoinServiceAPI + with WalletCache, WalletDB, ElectrumXParsing, CoinControlInterface + implements XPubAble { + LitecoinWallet({ + required String walletId, + required String walletName, + required Coin coin, + required ElectrumX client, + required CachedElectrumX cachedClient, + required TransactionNotificationTracker tracker, + required SecureStorageInterface secureStore, + MainDB? mockableOverride, + }) { + txTracker = tracker; + _walletId = walletId; + _walletName = walletName; + _coin = coin; + _electrumXClient = client; + _cachedElectrumXClient = cachedClient; + _secureStore = secureStore; + initCache(walletId, coin); + initWalletDB(mockableOverride: mockableOverride); + initCoinControlInterface( + walletId: walletId, + walletName: walletName, + coin: coin, + db: db, + getChainHeight: () => chainHeight, + refreshedBalanceCallback: (balance) async { + _balance = balance; + await updateCachedBalance(_balance!); + }, + ); + } -bip32.BIP32 getBip32Root(String mnemonic, NetworkType network) { - final seed = bip39.mnemonicToSeed(mnemonic); - final networkType = bip32.NetworkType( - wif: network.wif, - bip32: bip32.Bip32Type( - public: network.bip32.public, - private: network.bip32.private, - ), - ); - - final root = bip32.BIP32.fromSeed(seed, networkType); - return root; -} - -/// wrapper for compute() -bip32.BIP32 getBip32RootWrapper(Tuple2 args) { - return getBip32Root(args.item1, args.item2); -} - -class LitecoinWallet extends CoinServiceAPI { static const integrationTestFlag = bool.fromEnvironment("IS_INTEGRATION_TEST"); final _prefs = Prefs.instance; Timer? timer; - late Coin _coin; + late final Coin _coin; late final TransactionNotificationTracker txTracker; @@ -160,93 +154,53 @@ class LitecoinWallet extends CoinServiceAPI { } } - List outputsList = []; - @override set isFavorite(bool markFavorite) { - DB.instance.put( - boxName: walletId, key: "isFavorite", value: markFavorite); + _isFavorite = markFavorite; + updateCachedIsFavorite(markFavorite); } @override - bool get isFavorite { - try { - return DB.instance.get(boxName: walletId, key: "isFavorite") - as bool; - } catch (e, s) { - Logging.instance.log( - "isFavorite fetch failed (returning false by default): $e\n$s", - level: LogLevel.Error); - return false; - } - } + bool get isFavorite => _isFavorite ??= getCachedIsFavorite(); + + bool? _isFavorite; @override Coin get coin => _coin; @override - Future> get allOwnAddresses => - _allOwnAddresses ??= _fetchAllOwnAddresses(); - Future>? _allOwnAddresses; - - Future? _utxoData; - Future get utxoData => _utxoData ??= _fetchUtxoData(); + Future> get utxos => db.getUTXOs(walletId).findAll(); @override - Future> get unspentOutputs async => - (await utxoData).unspentOutputArray; + Future> get transactions => + db.getTransactions(walletId).sortByTimestampDesc().findAll(); @override - Future get availableBalance async { - final data = await utxoData; - return Format.satoshisToAmount( - data.satoshiBalance - data.satoshiBalanceUnconfirmed, - coin: coin); - } + Future get currentReceivingAddress async => + (await _currentReceivingAddress).value; - @override - Future get pendingBalance async { - final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalanceUnconfirmed, coin: coin); - } + Future get _currentReceivingAddress async => + (await db + .getAddresses(walletId) + .filter() + .typeEqualTo(isar_models.AddressType.p2wpkh) + .subTypeEqualTo(isar_models.AddressSubType.receiving) + .sortByDerivationIndexDesc() + .findFirst()) ?? + await _generateAddressForChain(0, 0, DerivePathTypeExt.primaryFor(coin)); - @override - Future get balanceMinusMaxFee async => - (await availableBalance) - - (Decimal.fromInt((await maxFee)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(); + Future get currentChangeAddress async => + (await _currentChangeAddress).value; - @override - Future get totalBalance async { - if (!isActive) { - final totalBalance = DB.instance - .get(boxName: walletId, key: 'totalBalance') as int?; - if (totalBalance == null) { - final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalance, coin: coin); - } else { - return Format.satoshisToAmount(totalBalance, coin: coin); - } - } - final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalance, coin: coin); - } - - @override - Future get currentReceivingAddress => _currentReceivingAddress ??= - _getCurrentAddressForChain(0, DerivePathType.bip84); - Future? _currentReceivingAddress; - - Future get currentLegacyReceivingAddress => - _currentReceivingAddressP2PKH ??= - _getCurrentAddressForChain(0, DerivePathType.bip44); - Future? _currentReceivingAddressP2PKH; - - Future get currentReceivingAddressP2SH => - _currentReceivingAddressP2SH ??= - _getCurrentAddressForChain(0, DerivePathType.bip49); - Future? _currentReceivingAddressP2SH; + Future get _currentChangeAddress async => + (await db + .getAddresses(walletId) + .filter() + .typeEqualTo(isar_models.AddressType.p2wpkh) + .subTypeEqualTo(isar_models.AddressSubType.change) + .sortByDerivationIndexDesc() + .findFirst()) ?? + await _generateAddressForChain(1, 0, DerivePathTypeExt.primaryFor(coin)); @override Future exit() async { @@ -276,27 +230,38 @@ class LitecoinWallet extends CoinServiceAPI { @override Future> get mnemonic => _getMnemonicList(); + @override + Future get mnemonicString => + _secureStore.read(key: '${_walletId}_mnemonic'); + + @override + Future get mnemonicPassphrase => _secureStore.read( + key: '${_walletId}_mnemonicPassphrase', + ); + Future get chainHeight async { try { final result = await _electrumXClient.getBlockHeadTip(); - return result["height"] as int; + final height = result["height"] as int; + await updateCachedChainHeight(height); + if (height > storedChainHeight) { + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "Updated current chain height in $walletId $walletName!", + walletId, + ), + ); + } + return height; } catch (e, s) { Logging.instance.log("Exception caught in chainHeight: $e\n$s", level: LogLevel.Error); - return -1; + return storedChainHeight; } } - int get storedChainHeight { - final storedHeight = DB.instance - .get(boxName: walletId, key: "storedChainHeight") as int?; - return storedHeight ?? 0; - } - - Future updateStoredChainHeight({required int newHeight}) async { - await DB.instance.put( - boxName: walletId, key: "storedChainHeight", value: newHeight); - } + @override + int get storedChainHeight => getCachedChainHeight(); DerivePathType addressType({required String address}) { Uint8List? decodeBase58; @@ -338,6 +303,7 @@ class LitecoinWallet extends CoinServiceAPI { @override Future recoverFromMnemonic({ required String mnemonic, + String? mnemonicPassphrase, required int maxUnusedAddressGap, required int maxNumberOfIndexesToCheck, required int height, @@ -377,14 +343,20 @@ class LitecoinWallet extends CoinServiceAPI { } // check to make sure we aren't overwriting a mnemonic // this should never fail - if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) { + if ((await mnemonicString) != null || + (await this.mnemonicPassphrase) != null) { longMutex = false; throw Exception("Attempted to overwrite mnemonic on restore!"); } await _secureStore.write( key: '${_walletId}_mnemonic', value: mnemonic.trim()); + await _secureStore.write( + key: '${_walletId}_mnemonicPassphrase', + value: mnemonicPassphrase ?? "", + ); await _recoverWalletFromBIP32SeedPhrase( mnemonic: mnemonic.trim(), + mnemonicPassphrase: mnemonicPassphrase ?? "", maxUnusedAddressGap: maxUnusedAddressGap, maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, ); @@ -409,8 +381,8 @@ class LitecoinWallet extends CoinServiceAPI { int txCountBatchSize, bip32.BIP32 root, DerivePathType type, - int account) async { - List addressArray = []; + int chain) async { + List addressArray = []; int returningIndex = -1; Map> derivations = {}; int gapCounter = 0; @@ -419,7 +391,7 @@ class LitecoinWallet extends CoinServiceAPI { index += txCountBatchSize) { List iterationsAddressArray = []; Logging.instance.log( - "index: $index, \t GapCounter $account ${type.name}: $gapCounter", + "index: $index, \t GapCounter $chain ${type.name}: $gapCounter", level: LogLevel.Info); final _id = "k_$index"; @@ -427,47 +399,60 @@ class LitecoinWallet extends CoinServiceAPI { final Map receivingNodes = {}; for (int j = 0; j < txCountBatchSize; j++) { - final node = await compute( - getBip32NodeFromRootWrapper, - Tuple4( - account, - index + j, - root, - type, - ), + final derivePath = constructDerivePath( + derivePathType: type, + networkWIF: root.network.wif, + chain: chain, + index: index + j, ); - String? address; + final node = await Bip32Utils.getBip32NodeFromRoot(root, derivePath); + + String addressString; + final data = PaymentData(pubkey: node.publicKey); + isar_models.AddressType addrType; switch (type) { case DerivePathType.bip44: - address = P2PKH( - data: PaymentData(pubkey: node.publicKey), - network: _network) - .data - .address!; + addressString = P2PKH(data: data, network: _network).data.address!; + addrType = isar_models.AddressType.p2pkh; break; case DerivePathType.bip49: - address = P2SH( + addressString = P2SH( data: PaymentData( redeem: P2WPKH( - data: PaymentData(pubkey: node.publicKey), + data: data, network: _network, overridePrefix: _network.bech32!) .data), network: _network) .data .address!; + addrType = isar_models.AddressType.p2sh; break; case DerivePathType.bip84: - address = P2WPKH( + addressString = P2WPKH( network: _network, - data: PaymentData(pubkey: node.publicKey), + data: data, overridePrefix: _network.bech32!) .data .address!; + addrType = isar_models.AddressType.p2wpkh; break; default: - throw Exception("No Path type $type exists"); + throw Exception("DerivePathType unsupported"); } + + final address = isar_models.Address( + walletId: walletId, + value: addressString, + publicKey: node.publicKey, + type: addrType, + derivationIndex: index + j, + derivationPath: isar_models.DerivationPath()..value = derivePath, + subType: chain == 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change, + ); + receivingNodes.addAll({ "${_id}_$j": { "node": node, @@ -475,7 +460,7 @@ class LitecoinWallet extends CoinServiceAPI { } }); txCountCallArgs.addAll({ - "${_id}_$j": address, + "${_id}_$j": addressString, }); } @@ -487,15 +472,16 @@ class LitecoinWallet extends CoinServiceAPI { int count = counts["${_id}_$k"]!; if (count > 0) { final node = receivingNodes["${_id}_$k"]; + final address = node["address"] as isar_models.Address; // add address to array - addressArray.add(node["address"] as String); - iterationsAddressArray.add(node["address"] as String); + addressArray.add(address); + iterationsAddressArray.add(address.value); // set current index returningIndex = index + k; // reset counter gapCounter = 0; // add info to derivations - derivations[node["address"] as String] = { + derivations[address.value] = { "pubKey": Format.uint8listToString( (node["node"] as bip32.BIP32).publicKey), "wif": (node["node"] as bip32.BIP32).toWIF(), @@ -539,8 +525,10 @@ class LitecoinWallet extends CoinServiceAPI { Future _recoverWalletFromBIP32SeedPhrase({ required String mnemonic, + required String mnemonicPassphrase, int maxUnusedAddressGap = 20, int maxNumberOfIndexesToCheck = 1000, + bool isRescan = false, }) async { longMutex = true; @@ -551,18 +539,22 @@ class LitecoinWallet extends CoinServiceAPI { Map> p2shChangeDerivations = {}; Map> p2wpkhChangeDerivations = {}; - final root = await compute(getBip32RootWrapper, Tuple2(mnemonic, _network)); + final root = await Bip32Utils.getBip32Root( + mnemonic, + mnemonicPassphrase, + _network, + ); - List p2pkhReceiveAddressArray = []; - List p2shReceiveAddressArray = []; - List p2wpkhReceiveAddressArray = []; + List p2pkhReceiveAddressArray = []; + List p2shReceiveAddressArray = []; + List p2wpkhReceiveAddressArray = []; int p2pkhReceiveIndex = -1; int p2shReceiveIndex = -1; int p2wpkhReceiveIndex = -1; - List p2pkhChangeAddressArray = []; - List p2shChangeAddressArray = []; - List p2wpkhChangeAddressArray = []; + List p2pkhChangeAddressArray = []; + List p2shChangeAddressArray = []; + List p2wpkhChangeAddressArray = []; int p2pkhChangeIndex = -1; int p2shChangeIndex = -1; int p2wpkhChangeIndex = -1; @@ -605,37 +597,37 @@ class LitecoinWallet extends CoinServiceAPI { ]); p2pkhReceiveAddressArray = - (await resultReceive44)['addressArray'] as List; + (await resultReceive44)['addressArray'] as List; p2pkhReceiveIndex = (await resultReceive44)['index'] as int; p2pkhReceiveDerivations = (await resultReceive44)['derivations'] as Map>; p2shReceiveAddressArray = - (await resultReceive49)['addressArray'] as List; + (await resultReceive49)['addressArray'] as List; p2shReceiveIndex = (await resultReceive49)['index'] as int; p2shReceiveDerivations = (await resultReceive49)['derivations'] as Map>; p2wpkhReceiveAddressArray = - (await resultReceive84)['addressArray'] as List; + (await resultReceive84)['addressArray'] as List; p2wpkhReceiveIndex = (await resultReceive84)['index'] as int; p2wpkhReceiveDerivations = (await resultReceive84)['derivations'] as Map>; p2pkhChangeAddressArray = - (await resultChange44)['addressArray'] as List; + (await resultChange44)['addressArray'] as List; p2pkhChangeIndex = (await resultChange44)['index'] as int; p2pkhChangeDerivations = (await resultChange44)['derivations'] as Map>; p2shChangeAddressArray = - (await resultChange49)['addressArray'] as List; + (await resultChange49)['addressArray'] as List; p2shChangeIndex = (await resultChange49)['index'] as int; p2shChangeDerivations = (await resultChange49)['derivations'] as Map>; p2wpkhChangeAddressArray = - (await resultChange84)['addressArray'] as List; + (await resultChange84)['addressArray'] as List; p2wpkhChangeIndex = (await resultChange84)['index'] as int; p2wpkhChangeDerivations = (await resultChange84)['derivations'] as Map>; @@ -684,19 +676,16 @@ class LitecoinWallet extends CoinServiceAPI { final address = await _generateAddressForChain(0, 0, DerivePathType.bip44); p2pkhReceiveAddressArray.add(address); - p2pkhReceiveIndex = 0; } if (p2shReceiveIndex == -1) { final address = await _generateAddressForChain(0, 0, DerivePathType.bip49); p2shReceiveAddressArray.add(address); - p2shReceiveIndex = 0; } if (p2wpkhReceiveIndex == -1) { final address = await _generateAddressForChain(0, 0, DerivePathType.bip84); p2wpkhReceiveAddressArray.add(address); - p2wpkhReceiveIndex = 0; } // If restoring a wallet that never sent any funds with change, then set changeArray @@ -705,69 +694,44 @@ class LitecoinWallet extends CoinServiceAPI { final address = await _generateAddressForChain(1, 0, DerivePathType.bip44); p2pkhChangeAddressArray.add(address); - p2pkhChangeIndex = 0; } if (p2shChangeIndex == -1) { final address = await _generateAddressForChain(1, 0, DerivePathType.bip49); p2shChangeAddressArray.add(address); - p2shChangeIndex = 0; } if (p2wpkhChangeIndex == -1) { final address = await _generateAddressForChain(1, 0, DerivePathType.bip84); p2wpkhChangeAddressArray.add(address); - p2wpkhChangeIndex = 0; } - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2WPKH', - value: p2wpkhReceiveAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2WPKH', - value: p2wpkhChangeAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2PKH', - value: p2pkhReceiveAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2PKH', - value: p2pkhChangeAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2SH', - value: p2shReceiveAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2SH', - value: p2shChangeAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2WPKH', - value: p2wpkhReceiveIndex); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2WPKH', - value: p2wpkhChangeIndex); - await DB.instance.put( - boxName: walletId, key: 'changeIndexP2PKH', value: p2pkhChangeIndex); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2PKH', - value: p2pkhReceiveIndex); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2SH', - value: p2shReceiveIndex); - await DB.instance.put( - boxName: walletId, key: 'changeIndexP2SH', value: p2shChangeIndex); - await DB.instance - .put(boxName: walletId, key: "id", value: _walletId); - await DB.instance - .put(boxName: walletId, key: "isFavorite", value: false); + if (isRescan) { + await db.updateOrPutAddresses([ + ...p2wpkhReceiveAddressArray, + ...p2wpkhChangeAddressArray, + ...p2pkhReceiveAddressArray, + ...p2pkhChangeAddressArray, + ...p2shReceiveAddressArray, + ...p2shChangeAddressArray, + ]); + } else { + await db.putAddresses([ + ...p2wpkhReceiveAddressArray, + ...p2wpkhChangeAddressArray, + ...p2pkhReceiveAddressArray, + ...p2pkhChangeAddressArray, + ...p2shReceiveAddressArray, + ...p2shChangeAddressArray, + ]); + } + + await _updateUTXOs(); + + await Future.wait([ + updateCachedId(walletId), + updateCachedIsFavorite(false), + ]); longMutex = false; } catch (e, s) { @@ -807,11 +771,15 @@ class LitecoinWallet extends CoinServiceAPI { } if (!needsRefresh) { var allOwnAddresses = await _fetchAllOwnAddresses(); - List> allTxs = - await _fetchHistory(allOwnAddresses); - final txData = await transactionData; + List> allTxs = await _fetchHistory( + allOwnAddresses.map((e) => e.value).toList(growable: false)); for (Map transaction in allTxs) { - if (txData.findTransaction(transaction['tx_hash'] as String) == + final txid = transaction['tx_hash'] as String; + if ((await db + .getTransactions(walletId) + .filter() + .txidMatches(txid) + .findFirst()) == null) { Logging.instance.log( " txid not found in address history already ${transaction['tx_hash']}", @@ -830,16 +798,25 @@ class LitecoinWallet extends CoinServiceAPI { } } - Future getAllTxsToWatch( - TransactionData txData, - ) async { + Future getAllTxsToWatch() async { if (_hasCalledExit) return; - List unconfirmedTxnsToNotifyPending = []; - List unconfirmedTxnsToNotifyConfirmed = []; + List unconfirmedTxnsToNotifyPending = []; + List unconfirmedTxnsToNotifyConfirmed = []; - for (final chunk in txData.txChunks) { - for (final tx in chunk.transactions) { - if (tx.confirmedStatus) { + final currentChainHeight = await chainHeight; + + final txCount = await db.getTransactions(walletId).count(); + + const paginateLimit = 50; + + for (int i = 0; i < txCount; i += paginateLimit) { + final transactions = await db + .getTransactions(walletId) + .offset(i) + .limit(paginateLimit) + .findAll(); + for (final tx in transactions) { + if (tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { // get all transactions that were notified as pending but not as confirmed if (txTracker.wasNotifiedPending(tx.txid) && !txTracker.wasNotifiedConfirmed(tx.txid)) { @@ -856,60 +833,72 @@ class LitecoinWallet extends CoinServiceAPI { // notify on unconfirmed transactions for (final tx in unconfirmedTxnsToNotifyPending) { - if (tx.txType == "Received") { - unawaited(NotificationApi.showNotification( - title: "Incoming transaction", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS, - coinName: coin.name, - txid: tx.txid, - confirmations: tx.confirmations, - requiredConfirmations: MINIMUM_CONFIRMATIONS, - )); + final confirmations = tx.getConfirmations(currentChainHeight); + + if (tx.type == isar_models.TransactionType.incoming) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Incoming transaction", + walletId: walletId, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, + txid: tx.txid, + confirmations: confirmations, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); await txTracker.addNotifiedPending(tx.txid); - } else if (tx.txType == "Sent") { - unawaited(NotificationApi.showNotification( - title: "Sending transaction", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS, - coinName: coin.name, - txid: tx.txid, - confirmations: tx.confirmations, - requiredConfirmations: MINIMUM_CONFIRMATIONS, - )); + } else if (tx.type == isar_models.TransactionType.outgoing) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Sending transaction", + walletId: walletId, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, + txid: tx.txid, + confirmations: confirmations, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); + await txTracker.addNotifiedPending(tx.txid); } } // notify on confirmed for (final tx in unconfirmedTxnsToNotifyConfirmed) { - if (tx.txType == "Received") { - unawaited(NotificationApi.showNotification( - title: "Incoming transaction confirmed", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: false, - coinName: coin.name, - )); + if (tx.type == isar_models.TransactionType.incoming) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Incoming transaction confirmed", + walletId: walletId, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: false, + txid: tx.txid, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); + await txTracker.addNotifiedConfirmed(tx.txid); - } else if (tx.txType == "Sent") { - unawaited(NotificationApi.showNotification( - title: "Outgoing transaction confirmed", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: false, - coinName: coin.name, - )); + } else if (tx.type == isar_models.TransactionType.outgoing) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Outgoing transaction confirmed", + walletId: walletId, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: false, + txid: tx.txid, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); await txTracker.addNotifiedConfirmed(tx.txid); } } @@ -974,46 +963,31 @@ class LitecoinWallet extends CoinServiceAPI { .log("cached height: $storedHeight", level: LogLevel.Info); if (currentHeight != storedHeight) { - if (currentHeight != -1) { - // -1 failed to fetch current height - unawaited(updateStoredChainHeight(newHeight: currentHeight)); - } - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId)); - final changeAddressForTransactions = - _checkChangeAddressForTransactions(DerivePathType.bip84); + await _checkChangeAddressForTransactions(); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId)); - final currentReceivingAddressesForTransactions = - _checkCurrentReceivingAddressesForTransactions(); + await _checkCurrentReceivingAddressesForTransactions(); - final newTxData = _fetchTransactionData(); + final fetchFuture = _refreshTransactions(); + final utxosRefreshFuture = _updateUTXOs(); GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.50, walletId)); - final newUtxoData = _fetchUtxoData(); final feeObj = _getFees(); GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.60, walletId)); - _transactionData = Future(() => newTxData); - GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.70, walletId)); _feeObject = Future(() => feeObj); - _utxoData = Future(() => newUtxoData); + + await utxosRefreshFuture; GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.80, walletId)); - final allTxsToWatch = getAllTxsToWatch(await newTxData); - await Future.wait([ - newTxData, - changeAddressForTransactions, - currentReceivingAddressesForTransactions, - newUtxoData, - feeObj, - allTxsToWatch, - ]); + await fetchFuture; + await getAllTxsToWatch(); GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.90, walletId)); } @@ -1069,12 +1043,13 @@ class LitecoinWallet extends CoinServiceAPI { @override Future> prepareSend({ required String address, - required int satoshiAmount, + required Amount amount, Map? args, }) async { try { final feeRateType = args?["feeRate"]; final feeRateAmount = args?["feeRateAmount"]; + final utxos = args?["UTXOs"] as Set?; if (feeRateType is FeeRateType || feeRateAmount is int) { late final int rate; if (feeRateType is FeeRateType) { @@ -1098,14 +1073,20 @@ class LitecoinWallet extends CoinServiceAPI { // check for send all bool isSendAll = false; - final balance = - Format.decimalAmountToSatoshis(await availableBalance, coin); - if (satoshiAmount == balance) { + if (amount == balance.spendable) { isSendAll = true; } - final txData = - await coinSelection(satoshiAmount, rate, address, isSendAll); + final bool coinControl = utxos != null; + + final txData = await coinSelection( + satoshiAmountToSend: amount.raw.toInt(), + selectedTxFeeRate: rate, + recipientAddress: address, + isSendAll: isSendAll, + utxos: utxos?.toList(), + coinControl: coinControl, + ); Logging.instance.log("prepare send: $txData", level: LogLevel.Info); try { @@ -1168,6 +1149,11 @@ class LitecoinWallet extends CoinServiceAPI { final txHash = await _electrumXClient.broadcastTransaction(rawTx: hex); Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info); + final utxos = txData["usedUTXOs"] as List; + + // mark utxos as used + await db.putUTXOs(utxos.map((e) => e.copyWith(used: true)).toList()); + return txHash; } catch (e, s) { Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s", @@ -1176,24 +1162,6 @@ class LitecoinWallet extends CoinServiceAPI { } } - @override - Future send({ - required String toAddress, - required int amount, - Map args = const {}, - }) async { - try { - final txData = await prepareSend( - address: toAddress, satoshiAmount: amount, args: args); - final txHash = await confirmSend(txData: txData); - return txHash; - } catch (e, s) { - Logging.instance - .log("Exception rethrown from send(): $e\n$s", level: LogLevel.Error); - rethrow; - } - } - @override Future testNetworkConnection() async { try { @@ -1246,7 +1214,7 @@ class LitecoinWallet extends CoinServiceAPI { Logging.instance .log("Generating new ${coin.prettyName} wallet.", level: LogLevel.Info); - if ((DB.instance.get(boxName: walletId, key: "id")) != null) { + if (getCachedId() != null) { throw Exception( "Attempted to initialize a new wallet using an existing wallet ID!"); } @@ -1259,82 +1227,62 @@ class LitecoinWallet extends CoinServiceAPI { level: LogLevel.Fatal); rethrow; } + await Future.wait([ - DB.instance.put(boxName: walletId, key: "id", value: walletId), - DB.instance - .put(boxName: walletId, key: "isFavorite", value: false), + updateCachedId(walletId), + updateCachedIsFavorite(false), ]); } @override Future initializeExisting() async { - Logging.instance.log("Opening existing ${coin.prettyName} wallet.", + Logging.instance.log("initializeExisting() ${coin.prettyName} wallet.", level: LogLevel.Info); - if ((DB.instance.get(boxName: walletId, key: "id")) == null) { + if (getCachedId() == null) { throw Exception( "Attempted to initialize an existing wallet using an unknown wallet ID!"); } await _prefs.init(); - final data = - DB.instance.get(boxName: walletId, key: "latest_tx_model") - as TransactionData?; - if (data != null) { - _transactionData = Future(() => data); - } + // await _checkCurrentChangeAddressesForTransactions(); + // await _checkCurrentReceivingAddressesForTransactions(); } - @override - Future get transactionData => - _transactionData ??= _fetchTransactionData(); - Future? _transactionData; - - TransactionData? cachedTxData; - // hack to add tx to txData before refresh completes // required based on current app architecture where we don't properly store // transactions locally in a good way @override Future updateSentCachedTxData(Map txData) async { - final priceData = - await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final locale = Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; - final String worthNow = Format.localizedStringAsFixed( - value: - ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2), - decimalPlaces: 2, - locale: locale!); - - final tx = models.Transaction( + final transaction = isar_models.Transaction( + walletId: walletId, txid: txData["txid"] as String, - confirmedStatus: false, timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, - txType: "Sent", - amount: txData["recipientAmt"] as int, - worthNow: worthNow, - worthAtBlockTimestamp: worthNow, - fees: txData["fee"] as int, - inputSize: 0, - outputSize: 0, + type: isar_models.TransactionType.outgoing, + subType: isar_models.TransactionSubType.none, + // precision may be lost here hence the following amountString + amount: (txData["recipientAmt"] as Amount).raw.toInt(), + amountString: (txData["recipientAmt"] as Amount).toJsonString(), + fee: txData["fee"] as int, + height: null, + isCancelled: false, + isLelantus: false, + otherData: null, + slateId: null, + nonce: null, inputs: [], outputs: [], - address: txData["address"] as String, - height: -1, - confirmations: 0, ); - if (cachedTxData == null) { - final data = await _fetchTransactionData(); - _transactionData = Future(() => data); - } + final address = txData["address"] is String + ? await db.getAddress(walletId, txData["address"] as String) + : null; - final transactions = cachedTxData!.getAllTransactions(); - transactions[tx.txid] = tx; - cachedTxData = models.TransactionData.fromMap(transactions); - _transactionData = Future(() => cachedTxData!); + await db.addNewTransactionData( + [ + Tuple2(transaction, address), + ], + walletId, + ); } @override @@ -1344,7 +1292,7 @@ class LitecoinWallet extends CoinServiceAPI { @override String get walletId => _walletId; - late String _walletId; + late final String _walletId; @override String get walletName => _walletName; @@ -1364,29 +1312,6 @@ class LitecoinWallet extends CoinServiceAPI { late SecureStorageInterface _secureStore; - late PriceAPI _priceAPI; - - LitecoinWallet({ - required String walletId, - required String walletName, - required Coin coin, - required ElectrumX client, - required CachedElectrumX cachedClient, - required TransactionNotificationTracker tracker, - PriceAPI? priceAPI, - required SecureStorageInterface secureStore, - }) { - txTracker = tracker; - _walletId = walletId; - _walletName = walletName; - _coin = coin; - _electrumXClient = client; - _cachedElectrumXClient = cachedClient; - - _priceAPI = priceAPI ?? PriceAPI(Client()); - _secureStore = secureStore; - } - @override Future updateNode(bool shouldRefresh) async { final failovers = NodeService(secureStorageInterface: _secureStore) @@ -1417,12 +1342,11 @@ class LitecoinWallet extends CoinServiceAPI { } Future> _getMnemonicList() async { - final mnemonicString = - await _secureStore.read(key: '${_walletId}_mnemonic'); - if (mnemonicString == null) { + final _mnemonicString = await mnemonicString; + if (_mnemonicString == null) { return []; } - final List data = mnemonicString.split(' '); + final List data = _mnemonicString.split(' '); return data; } @@ -1440,53 +1364,64 @@ class LitecoinWallet extends CoinServiceAPI { ); } - Future> _fetchAllOwnAddresses() async { - final List allAddresses = []; - final receivingAddresses = DB.instance.get( - boxName: walletId, key: 'receivingAddressesP2WPKH') as List; - final changeAddresses = DB.instance.get( - boxName: walletId, key: 'changeAddressesP2WPKH') as List; - final receivingAddressesP2PKH = DB.instance.get( - boxName: walletId, key: 'receivingAddressesP2PKH') as List; - final changeAddressesP2PKH = - DB.instance.get(boxName: walletId, key: 'changeAddressesP2PKH') - as List; - final receivingAddressesP2SH = DB.instance.get( - boxName: walletId, key: 'receivingAddressesP2SH') as List; - final changeAddressesP2SH = - DB.instance.get(boxName: walletId, key: 'changeAddressesP2SH') - as List; - - for (var i = 0; i < receivingAddresses.length; i++) { - if (!allAddresses.contains(receivingAddresses[i])) { - allAddresses.add(receivingAddresses[i] as String); - } - } - for (var i = 0; i < changeAddresses.length; i++) { - if (!allAddresses.contains(changeAddresses[i])) { - allAddresses.add(changeAddresses[i] as String); - } - } - for (var i = 0; i < receivingAddressesP2PKH.length; i++) { - if (!allAddresses.contains(receivingAddressesP2PKH[i])) { - allAddresses.add(receivingAddressesP2PKH[i] as String); - } - } - for (var i = 0; i < changeAddressesP2PKH.length; i++) { - if (!allAddresses.contains(changeAddressesP2PKH[i])) { - allAddresses.add(changeAddressesP2PKH[i] as String); - } - } - for (var i = 0; i < receivingAddressesP2SH.length; i++) { - if (!allAddresses.contains(receivingAddressesP2SH[i])) { - allAddresses.add(receivingAddressesP2SH[i] as String); - } - } - for (var i = 0; i < changeAddressesP2SH.length; i++) { - if (!allAddresses.contains(changeAddressesP2SH[i])) { - allAddresses.add(changeAddressesP2SH[i] as String); - } - } + Future> _fetchAllOwnAddresses() async { + final allAddresses = await db + .getAddresses(walletId) + .filter() + .not() + .typeEqualTo(isar_models.AddressType.nonWallet) + .and() + .group((q) => q + .subTypeEqualTo(isar_models.AddressSubType.receiving) + .or() + .subTypeEqualTo(isar_models.AddressSubType.change)) + .findAll(); + // final List allAddresses = []; + // final receivingAddresses = DB.instance.get( + // boxName: walletId, key: 'receivingAddressesP2WPKH') as List; + // final changeAddresses = DB.instance.get( + // boxName: walletId, key: 'changeAddressesP2WPKH') as List; + // final receivingAddressesP2PKH = DB.instance.get( + // boxName: walletId, key: 'receivingAddressesP2PKH') as List; + // final changeAddressesP2PKH = + // DB.instance.get(boxName: walletId, key: 'changeAddressesP2PKH') + // as List; + // final receivingAddressesP2SH = DB.instance.get( + // boxName: walletId, key: 'receivingAddressesP2SH') as List; + // final changeAddressesP2SH = + // DB.instance.get(boxName: walletId, key: 'changeAddressesP2SH') + // as List; + // + // for (var i = 0; i < receivingAddresses.length; i++) { + // if (!allAddresses.contains(receivingAddresses[i])) { + // allAddresses.add(receivingAddresses[i] as String); + // } + // } + // for (var i = 0; i < changeAddresses.length; i++) { + // if (!allAddresses.contains(changeAddresses[i])) { + // allAddresses.add(changeAddresses[i] as String); + // } + // } + // for (var i = 0; i < receivingAddressesP2PKH.length; i++) { + // if (!allAddresses.contains(receivingAddressesP2PKH[i])) { + // allAddresses.add(receivingAddressesP2PKH[i] as String); + // } + // } + // for (var i = 0; i < changeAddressesP2PKH.length; i++) { + // if (!allAddresses.contains(changeAddressesP2PKH[i])) { + // allAddresses.add(changeAddressesP2PKH[i] as String); + // } + // } + // for (var i = 0; i < receivingAddressesP2SH.length; i++) { + // if (!allAddresses.contains(receivingAddressesP2SH[i])) { + // allAddresses.add(receivingAddressesP2SH[i] as String); + // } + // } + // for (var i = 0; i < changeAddressesP2SH.length; i++) { + // if (!allAddresses.contains(changeAddressesP2SH[i])) { + // allAddresses.add(changeAddressesP2SH[i] as String); + // } + // } return allAddresses; } @@ -1503,9 +1438,18 @@ class LitecoinWallet extends CoinServiceAPI { numberOfBlocksFast: f, numberOfBlocksAverage: m, numberOfBlocksSlow: s, - fast: Format.decimalAmountToSatoshis(fast, coin), - medium: Format.decimalAmountToSatoshis(medium, coin), - slow: Format.decimalAmountToSatoshis(slow, coin), + fast: Amount.fromDecimal( + fast, + fractionDigits: coin.decimals, + ).raw.toInt(), + medium: Amount.fromDecimal( + medium, + fractionDigits: coin.decimals, + ).raw.toInt(), + slow: Amount.fromDecimal( + slow, + fractionDigits: coin.decimals, + ).raw.toInt(), ); Logging.instance.log("fetched fees: $feeObject", level: LogLevel.Info); @@ -1529,7 +1473,7 @@ class LitecoinWallet extends CoinServiceAPI { switch (coin) { case Coin.litecoin: if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { - print(features['genesis_hash']); + // print(features['genesis_hash']); throw Exception("genesis hash does not match main net!"); } break; @@ -1548,123 +1492,34 @@ class LitecoinWallet extends CoinServiceAPI { } // this should never fail - if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) { + if ((await mnemonicString) != null || (await mnemonicPassphrase) != null) { throw Exception( "Attempted to overwrite mnemonic on generate new wallet!"); } await _secureStore.write( key: '${_walletId}_mnemonic', value: bip39.generateMnemonic(strength: 256)); - - // Set relevant indexes - await DB.instance - .put(boxName: walletId, key: "receivingIndexP2WPKH", value: 0); - await DB.instance - .put(boxName: walletId, key: "changeIndexP2WPKH", value: 0); - await DB.instance - .put(boxName: walletId, key: "receivingIndexP2PKH", value: 0); - await DB.instance - .put(boxName: walletId, key: "changeIndexP2PKH", value: 0); - await DB.instance - .put(boxName: walletId, key: "receivingIndexP2SH", value: 0); - await DB.instance - .put(boxName: walletId, key: "changeIndexP2SH", value: 0); - await DB.instance.put( - boxName: walletId, - key: 'blocked_tx_hashes', - value: ["0xdefault"], - ); // A list of transaction hashes to represent frozen utxos in wallet - // initialize address book entries - await DB.instance.put( - boxName: walletId, - key: 'addressBookEntries', - value: {}); + await _secureStore.write( + key: '${_walletId}_mnemonicPassphrase', + value: "", + ); // Generate and add addresses to relevant arrays - await Future.wait([ + final initialAddresses = await Future.wait([ // P2WPKH - _generateAddressForChain(0, 0, DerivePathType.bip84).then( - (initialReceivingAddressP2WPKH) { - _addToAddressesArrayForChain( - initialReceivingAddressP2WPKH, 0, DerivePathType.bip84); - _currentReceivingAddress = - Future(() => initialReceivingAddressP2WPKH); - }, - ), - _generateAddressForChain(1, 0, DerivePathType.bip84).then( - (initialChangeAddressP2WPKH) => _addToAddressesArrayForChain( - initialChangeAddressP2WPKH, - 1, - DerivePathType.bip84, - ), - ), + _generateAddressForChain(0, 0, DerivePathType.bip84), + _generateAddressForChain(1, 0, DerivePathType.bip84), // P2PKH - _generateAddressForChain(0, 0, DerivePathType.bip44).then( - (initialReceivingAddressP2PKH) { - _addToAddressesArrayForChain( - initialReceivingAddressP2PKH, 0, DerivePathType.bip44); - _currentReceivingAddressP2PKH = - Future(() => initialReceivingAddressP2PKH); - }, - ), - _generateAddressForChain(1, 0, DerivePathType.bip44).then( - (initialChangeAddressP2PKH) => _addToAddressesArrayForChain( - initialChangeAddressP2PKH, - 1, - DerivePathType.bip44, - ), - ), + _generateAddressForChain(0, 0, DerivePathType.bip44), + _generateAddressForChain(1, 0, DerivePathType.bip44), // P2SH - _generateAddressForChain(0, 0, DerivePathType.bip49).then( - (initialReceivingAddressP2SH) { - _addToAddressesArrayForChain( - initialReceivingAddressP2SH, 0, DerivePathType.bip49); - _currentReceivingAddressP2SH = - Future(() => initialReceivingAddressP2SH); - }, - ), - _generateAddressForChain(1, 0, DerivePathType.bip49).then( - (initialChangeAddressP2SH) => _addToAddressesArrayForChain( - initialChangeAddressP2SH, - 1, - DerivePathType.bip49, - ), - ), + _generateAddressForChain(0, 0, DerivePathType.bip49), + _generateAddressForChain(1, 0, DerivePathType.bip49), ]); - // // P2PKH - // _generateAddressForChain(0, 0, DerivePathType.bip44).then( - // (initialReceivingAddressP2PKH) { - // _addToAddressesArrayForChain( - // initialReceivingAddressP2PKH, 0, DerivePathType.bip44); - // this._currentReceivingAddressP2PKH = - // Future(() => initialReceivingAddressP2PKH); - // }, - // ); - // _generateAddressForChain(1, 0, DerivePathType.bip44) - // .then((initialChangeAddressP2PKH) => _addToAddressesArrayForChain( - // initialChangeAddressP2PKH, - // 1, - // DerivePathType.bip44, - // )); - // - // // P2SH - // _generateAddressForChain(0, 0, DerivePathType.bip49).then( - // (initialReceivingAddressP2SH) { - // _addToAddressesArrayForChain( - // initialReceivingAddressP2SH, 0, DerivePathType.bip49); - // this._currentReceivingAddressP2SH = - // Future(() => initialReceivingAddressP2SH); - // }, - // ); - // _generateAddressForChain(1, 0, DerivePathType.bip49) - // .then((initialChangeAddressP2SH) => _addToAddressesArrayForChain( - // initialChangeAddressP2SH, - // 1, - // DerivePathType.bip49, - // )); + await db.putAddresses(initialAddresses); Logging.instance.log("_generateNewWalletFinished", level: LogLevel.Info); } @@ -1672,28 +1527,40 @@ class LitecoinWallet extends CoinServiceAPI { /// Generates a new internal or external chain address for the wallet using a BIP84, BIP44, or BIP49 derivation path. /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! /// [index] - This can be any integer >= 0 - Future _generateAddressForChain( + Future _generateAddressForChain( int chain, int index, DerivePathType derivePathType, ) async { - final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic'); - final node = await compute( - getBip32NodeWrapper, - Tuple5( - chain, - index, - mnemonic!, - _network, - derivePathType, - ), + final _mnemonic = await mnemonicString; + final _mnemonicPassphrase = await mnemonicPassphrase; + if (_mnemonicPassphrase == null) { + Logging.instance.log( + "Exception in _generateAddressForChain: mnemonic passphrase null, possible migration issue; if using internal builds, delete wallet and restore from seed, if using a release build, please file bug report", + level: LogLevel.Error); + } + + final derivePath = constructDerivePath( + derivePathType: derivePathType, + networkWIF: _network.wif, + chain: chain, + index: index, ); + final node = await Bip32Utils.getBip32Node( + _mnemonic!, + _mnemonicPassphrase!, + _network, + derivePath, + ); + final data = PaymentData(pubkey: node.publicKey); String address; + isar_models.AddressType addrType; switch (derivePathType) { case DerivePathType.bip44: address = P2PKH(data: data, network: _network).data.address!; + addrType = isar_models.AddressType.p2pkh; break; case DerivePathType.bip49: address = P2SH( @@ -1706,13 +1573,17 @@ class LitecoinWallet extends CoinServiceAPI { network: _network) .data .address!; + addrType = isar_models.AddressType.p2sh; break; case DerivePathType.bip84: address = P2WPKH( network: _network, data: data, overridePrefix: _network.bech32!) .data .address!; + addrType = isar_models.AddressType.p2wpkh; break; + default: + throw Exception("DerivePathType unsupported"); } // add generated address & info to derivations @@ -1724,98 +1595,53 @@ class LitecoinWallet extends CoinServiceAPI { derivePathType: derivePathType, ); - return address; - } - - /// Increases the index for either the internal or external chain, depending on [chain]. - /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! - Future _incrementAddressIndexForChain( - int chain, DerivePathType derivePathType) async { - // Here we assume chain == 1 if it isn't 0 - String indexKey = chain == 0 ? "receivingIndex" : "changeIndex"; - switch (derivePathType) { - case DerivePathType.bip44: - indexKey += "P2PKH"; - break; - case DerivePathType.bip49: - indexKey += "P2SH"; - break; - case DerivePathType.bip84: - indexKey += "P2WPKH"; - break; - } - - final newIndex = - (DB.instance.get(boxName: walletId, key: indexKey)) + 1; - await DB.instance - .put(boxName: walletId, key: indexKey, value: newIndex); - } - - /// Adds [address] to the relevant chain's address array, which is determined by [chain]. - /// [address] - Expects a standard native segwit address - /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! - Future _addToAddressesArrayForChain( - String address, int chain, DerivePathType derivePathType) async { - String chainArray = ''; - if (chain == 0) { - chainArray = 'receivingAddresses'; - } else { - chainArray = 'changeAddresses'; - } - switch (derivePathType) { - case DerivePathType.bip44: - chainArray += "P2PKH"; - break; - case DerivePathType.bip49: - chainArray += "P2SH"; - break; - case DerivePathType.bip84: - chainArray += "P2WPKH"; - break; - } - - final addressArray = - DB.instance.get(boxName: walletId, key: chainArray); - if (addressArray == null) { - Logging.instance.log( - 'Attempting to add the following to $chainArray array for chain $chain:${[ - address - ]}', - level: LogLevel.Info); - await DB.instance - .put(boxName: walletId, key: chainArray, value: [address]); - } else { - // Make a deep copy of the existing list - final List newArray = []; - addressArray - .forEach((dynamic _address) => newArray.add(_address as String)); - newArray.add(address); // Add the address passed into the method - await DB.instance - .put(boxName: walletId, key: chainArray, value: newArray); - } + return isar_models.Address( + walletId: walletId, + value: address, + publicKey: node.publicKey, + type: addrType, + derivationIndex: index, + derivationPath: isar_models.DerivationPath()..value = derivePath, + subType: chain == 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change, + ); } /// Returns the latest receiving/change (external/internal) address for the wallet depending on [chain] /// and /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! Future _getCurrentAddressForChain( - int chain, DerivePathType derivePathType) async { - // Here, we assume that chain == 1 if it isn't 0 - String arrayKey = chain == 0 ? "receivingAddresses" : "changeAddresses"; + int chain, + DerivePathType derivePathType, + ) async { + final subType = chain == 0 // Here, we assume that chain == 1 if it isn't 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change; + + isar_models.AddressType type; + isar_models.Address? address; switch (derivePathType) { case DerivePathType.bip44: - arrayKey += "P2PKH"; + type = isar_models.AddressType.p2pkh; break; case DerivePathType.bip49: - arrayKey += "P2SH"; + type = isar_models.AddressType.p2sh; break; case DerivePathType.bip84: - arrayKey += "P2WPKH"; + type = isar_models.AddressType.p2wpkh; break; + default: + throw Exception("DerivePathType unsupported"); } - final internalChainArray = - DB.instance.get(boxName: walletId, key: arrayKey); - return internalChainArray.last as String; + address = await db + .getAddresses(walletId) + .filter() + .typeEqualTo(type) + .subTypeEqualTo(subType) + .sortByDerivationIndexDesc() + .findFirst(); + return address!.value; } String _buildDerivationStorageKey({ @@ -1834,6 +1660,8 @@ class LitecoinWallet extends CoinServiceAPI { case DerivePathType.bip84: key = "${walletId}_${chainId}DerivationsP2WPKH"; break; + default: + throw Exception("DerivePathType unsupported"); } return key; } @@ -1920,8 +1748,8 @@ class LitecoinWallet extends CoinServiceAPI { await _secureStore.write(key: key, value: newReceiveDerivationsString); } - Future _fetchUtxoData() async { - final List allAddresses = await _fetchAllOwnAddresses(); + Future _updateUTXOs() async { + final allAddresses = await _fetchAllOwnAddresses(); try { final fetchedUtxoList = >>[]; @@ -1933,7 +1761,8 @@ class LitecoinWallet extends CoinServiceAPI { if (batches[batchNumber] == null) { batches[batchNumber] = {}; } - final scripthash = _convertToScriptHash(allAddresses[i], _network); + final scripthash = + _convertToScriptHash(allAddresses[i].value, _network); batches[batchNumber]!.addAll({ scripthash: [scripthash] }); @@ -1951,143 +1780,112 @@ class LitecoinWallet extends CoinServiceAPI { } } } - final priceData = - await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final List> outputArray = []; - int satoshiBalance = 0; - int satoshiBalancePending = 0; + + final List outputArray = []; for (int i = 0; i < fetchedUtxoList.length; i++) { for (int j = 0; j < fetchedUtxoList[i].length; j++) { - int value = fetchedUtxoList[i][j]["value"] as int; - satoshiBalance += value; + final jsonUTXO = fetchedUtxoList[i][j]; final txn = await cachedElectrumXClient.getTransaction( - txHash: fetchedUtxoList[i][j]["tx_hash"] as String, + txHash: jsonUTXO["tx_hash"] as String, verbose: true, coin: coin, ); - final Map utxo = {}; - final int confirmations = txn["confirmations"] as int? ?? 0; - final bool confirmed = confirmations >= MINIMUM_CONFIRMATIONS; - if (!confirmed) { - satoshiBalancePending += value; + final vout = jsonUTXO["tx_pos"] as int; + + final outputs = txn["vout"] as List; + + String? utxoOwnerAddress; + // get UTXO owner address + for (final output in outputs) { + if (output["n"] == vout) { + utxoOwnerAddress = + output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]?["address"] as String?; + } } - utxo["txid"] = txn["txid"]; - utxo["vout"] = fetchedUtxoList[i][j]["tx_pos"]; - utxo["value"] = value; + final utxo = isar_models.UTXO( + walletId: walletId, + txid: txn["txid"] as String, + vout: vout, + value: jsonUTXO["value"] as int, + name: "", + isBlocked: false, + blockedReason: null, + isCoinbase: txn["is_coinbase"] as bool? ?? false, + blockHash: txn["blockhash"] as String?, + blockHeight: jsonUTXO["height"] as int?, + blockTime: txn["blocktime"] as int?, + address: utxoOwnerAddress, + ); - utxo["status"] = {}; - utxo["status"]["confirmed"] = confirmed; - utxo["status"]["confirmations"] = confirmations; - utxo["status"]["block_height"] = fetchedUtxoList[i][j]["height"]; - utxo["status"]["block_hash"] = txn["blockhash"]; - utxo["status"]["block_time"] = txn["blocktime"]; - - final fiatValue = ((Decimal.fromInt(value) * currentPrice) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2); - utxo["rawWorth"] = fiatValue; - utxo["fiatWorth"] = fiatValue.toString(); outputArray.add(utxo); } } - Decimal currencyBalanceRaw = - ((Decimal.fromInt(satoshiBalance) * currentPrice) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2); - - final Map result = { - "total_user_currency": currencyBalanceRaw.toString(), - "total_sats": satoshiBalance, - "total_btc": (Decimal.fromInt(satoshiBalance) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal( - scaleOnInfinitePrecision: Constants.decimalPlacesForCoin(coin)) - .toString(), - "outputArray": outputArray, - "unconfirmed": satoshiBalancePending, - }; - - final dataModel = UtxoData.fromJson(result); - - final List allOutputs = dataModel.unspentOutputArray; Logging.instance - .log('Outputs fetched: $allOutputs', level: LogLevel.Info); - await _sortOutputs(allOutputs); - await DB.instance.put( - boxName: walletId, key: 'latest_utxo_model', value: dataModel); - await DB.instance.put( - boxName: walletId, - key: 'totalBalance', - value: dataModel.satoshiBalance); - return dataModel; + .log('Outputs fetched: $outputArray', level: LogLevel.Info); + + await db.updateUTXOs(walletId, outputArray); + + // finally update balance + await _updateBalance(); } catch (e, s) { Logging.instance .log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error); - final latestTxModel = - DB.instance.get(boxName: walletId, key: 'latest_utxo_model') - as models.UtxoData?; - - if (latestTxModel == null) { - final emptyModel = { - "total_user_currency": "0.00", - "total_sats": 0, - "total_btc": "0", - "outputArray": [] - }; - return UtxoData.fromJson(emptyModel); - } else { - Logging.instance - .log("Old output model located", level: LogLevel.Warning); - return latestTxModel; - } } } - /// Takes in a list of UtxoObjects and adds a name (dependent on object index within list) - /// and checks for the txid associated with the utxo being blocked and marks it accordingly. - /// Now also checks for output labeling. - Future _sortOutputs(List utxos) async { - final blockedHashArray = - DB.instance.get(boxName: walletId, key: 'blocked_tx_hashes') - as List?; - final List lst = []; - if (blockedHashArray != null) { - for (var hash in blockedHashArray) { - lst.add(hash as String); - } - } - final labels = - DB.instance.get(boxName: walletId, key: 'labels') as Map? ?? - {}; - - outputsList = []; - - for (var i = 0; i < utxos.length; i++) { - if (labels[utxos[i].txid] != null) { - utxos[i].txName = labels[utxos[i].txid] as String? ?? ""; - } else { - utxos[i].txName = 'Output #$i'; - } - - if (utxos[i].status.confirmed == false) { - outputsList.add(utxos[i]); - } else { - if (lst.contains(utxos[i].txid)) { - utxos[i].blocked = true; - outputsList.add(utxos[i]); - } else if (!lst.contains(utxos[i].txid)) { - outputsList.add(utxos[i]); - } - } - } + Future _updateBalance() async { + await refreshBalance(); } + @override + Balance get balance => _balance ??= getCachedBalance(); + Balance? _balance; + + // /// Takes in a list of UtxoObjects and adds a name (dependent on object index within list) + // /// and checks for the txid associated with the utxo being blocked and marks it accordingly. + // /// Now also checks for output labeling. + // Future _sortOutputs(List utxos) async { + // final blockedHashArray = + // DB.instance.get(boxName: walletId, key: 'blocked_tx_hashes') + // as List?; + // final List lst = []; + // if (blockedHashArray != null) { + // for (var hash in blockedHashArray) { + // lst.add(hash as String); + // } + // } + // final labels = + // DB.instance.get(boxName: walletId, key: 'labels') as Map? ?? + // {}; + // + // outputsList = []; + // + // for (var i = 0; i < utxos.length; i++) { + // if (labels[utxos[i].txid] != null) { + // utxos[i].txName = labels[utxos[i].txid] as String? ?? ""; + // } else { + // utxos[i].txName = 'Output #$i'; + // } + // + // if (utxos[i].status.confirmed == false) { + // outputsList.add(utxos[i]); + // } else { + // if (lst.contains(utxos[i].txid)) { + // utxos[i].blocked = true; + // outputsList.add(utxos[i]); + // } else if (!lst.contains(utxos[i].txid)) { + // outputsList.add(utxos[i]); + // } + // } + // } + // } + Future getTxCount({required String address}) async { String? scripthash; try { @@ -2126,111 +1924,85 @@ class LitecoinWallet extends CoinServiceAPI { } } - Future _checkReceivingAddressForTransactions( - DerivePathType derivePathType) async { + Future _checkReceivingAddressForTransactions() async { try { - final String currentExternalAddr = - await _getCurrentAddressForChain(0, derivePathType); - final int txCount = await getTxCount(address: currentExternalAddr); + final currentReceiving = await _currentReceivingAddress; + + final int txCount = await getTxCount(address: currentReceiving.value); Logging.instance.log( - 'Number of txs for current receiving address $currentExternalAddr: $txCount', + 'Number of txs for current receiving address $currentReceiving: $txCount', level: LogLevel.Info); - if (txCount >= 1) { + if (txCount >= 1 || currentReceiving.derivationIndex < 0) { // First increment the receiving index - await _incrementAddressIndexForChain(0, derivePathType); - - // Check the new receiving index - String indexKey = "receivingIndex"; - switch (derivePathType) { - case DerivePathType.bip44: - indexKey += "P2PKH"; - break; - case DerivePathType.bip49: - indexKey += "P2SH"; - break; - case DerivePathType.bip84: - indexKey += "P2WPKH"; - break; - } - final newReceivingIndex = - DB.instance.get(boxName: walletId, key: indexKey) as int; + final newReceivingIndex = currentReceiving.derivationIndex + 1; // Use new index to derive a new receiving address final newReceivingAddress = await _generateAddressForChain( - 0, newReceivingIndex, derivePathType); + 0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin)); - // Add that new receiving address to the array of receiving addresses - await _addToAddressesArrayForChain( - newReceivingAddress, 0, derivePathType); - - // Set the new receiving address that the service - - switch (derivePathType) { - case DerivePathType.bip44: - _currentReceivingAddressP2PKH = Future(() => newReceivingAddress); - break; - case DerivePathType.bip49: - _currentReceivingAddressP2SH = Future(() => newReceivingAddress); - break; - case DerivePathType.bip84: - _currentReceivingAddress = Future(() => newReceivingAddress); - break; + final existing = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(newReceivingAddress.value) + .findFirst(); + if (existing == null) { + // Add that new change address + await db.putAddress(newReceivingAddress); + } else { + // we need to update the address + await db.updateAddress(existing, newReceivingAddress); } + // keep checking until address with no tx history is set as current + await _checkReceivingAddressForTransactions(); } } catch (e, s) { Logging.instance.log( - "Exception rethrown from _checkReceivingAddressForTransactions($derivePathType): $e\n$s", + "Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s", level: LogLevel.Error); rethrow; } } - Future _checkChangeAddressForTransactions( - DerivePathType derivePathType) async { + Future _checkChangeAddressForTransactions() async { try { - final String currentExternalAddr = - await _getCurrentAddressForChain(1, derivePathType); - final int txCount = await getTxCount(address: currentExternalAddr); + final currentChange = await _currentChangeAddress; + final int txCount = await getTxCount(address: currentChange.value); Logging.instance.log( - 'Number of txs for current change address $currentExternalAddr: $txCount', + 'Number of txs for current change address $currentChange: $txCount', level: LogLevel.Info); - if (txCount >= 1) { + if (txCount >= 1 || currentChange.derivationIndex < 0) { // First increment the change index - await _incrementAddressIndexForChain(1, derivePathType); - - // Check the new change index - String indexKey = "changeIndex"; - switch (derivePathType) { - case DerivePathType.bip44: - indexKey += "P2PKH"; - break; - case DerivePathType.bip49: - indexKey += "P2SH"; - break; - case DerivePathType.bip84: - indexKey += "P2WPKH"; - break; - } - final newChangeIndex = - DB.instance.get(boxName: walletId, key: indexKey) as int; + final newChangeIndex = currentChange.derivationIndex + 1; // Use new index to derive a new change address - final newChangeAddress = - await _generateAddressForChain(1, newChangeIndex, derivePathType); + final newChangeAddress = await _generateAddressForChain( + 1, newChangeIndex, DerivePathTypeExt.primaryFor(coin)); - // Add that new receiving address to the array of change addresses - await _addToAddressesArrayForChain(newChangeAddress, 1, derivePathType); + final existing = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(newChangeAddress.value) + .findFirst(); + if (existing == null) { + // Add that new change address + await db.putAddress(newChangeAddress); + } else { + // we need to update the address + await db.updateAddress(existing, newChangeAddress); + } + // keep checking until address with no tx history is set as current + await _checkChangeAddressForTransactions(); } } on SocketException catch (se, s) { Logging.instance.log( - "SocketException caught in _checkReceivingAddressForTransactions($derivePathType): $se\n$s", + "SocketException caught in _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $se\n$s", level: LogLevel.Error); return; } catch (e, s) { Logging.instance.log( - "Exception rethrown from _checkReceivingAddressForTransactions($derivePathType): $e\n$s", + "Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s", level: LogLevel.Error); rethrow; } @@ -2238,9 +2010,9 @@ class LitecoinWallet extends CoinServiceAPI { Future _checkCurrentReceivingAddressesForTransactions() async { try { - for (final type in DerivePathType.values) { - await _checkReceivingAddressForTransactions(type); - } + // for (final type in DerivePathType.values) { + await _checkReceivingAddressForTransactions(); + // } } catch (e, s) { Logging.instance.log( "Exception rethrown from _checkCurrentReceivingAddressesForTransactions(): $e\n$s", @@ -2262,9 +2034,9 @@ class LitecoinWallet extends CoinServiceAPI { Future _checkCurrentChangeAddressesForTransactions() async { try { - for (final type in DerivePathType.values) { - await _checkChangeAddressForTransactions(type); - } + // for (final type in DerivePathType.values) { + await _checkChangeAddressForTransactions(); + // } } catch (e, s) { Logging.instance.log( "Exception rethrown from _checkCurrentChangeAddressesForTransactions(): $e\n$s", @@ -2351,16 +2123,6 @@ class LitecoinWallet extends CoinServiceAPI { } } - bool _duplicateTxCheck( - List> allTransactions, String txid) { - for (int i = 0; i < allTransactions.length; i++) { - if (allTransactions[i]["txid"] == txid) { - return true; - } - } - return false; - } - Future>> fastFetch(List allTxHashes) async { List> allTransactions = []; @@ -2398,87 +2160,61 @@ class LitecoinWallet extends CoinServiceAPI { return allTransactions; } - Future _fetchTransactionData() async { - final List allAddresses = await _fetchAllOwnAddresses(); - - final changeAddresses = DB.instance.get( - boxName: walletId, key: 'changeAddressesP2WPKH') as List; - final changeAddressesP2PKH = - DB.instance.get(boxName: walletId, key: 'changeAddressesP2PKH') - as List; - final changeAddressesP2SH = - DB.instance.get(boxName: walletId, key: 'changeAddressesP2SH') - as List; - - for (var i = 0; i < changeAddressesP2PKH.length; i++) { - changeAddresses.add(changeAddressesP2PKH[i] as String); - } - for (var i = 0; i < changeAddressesP2SH.length; i++) { - changeAddresses.add(changeAddressesP2SH[i] as String); - } - - final List> allTxHashes = - await _fetchHistory(allAddresses); - - final cachedTransactions = - DB.instance.get(boxName: walletId, key: 'latest_tx_model') - as TransactionData?; - int latestTxnBlockHeight = - DB.instance.get(boxName: walletId, key: "storedTxnDataHeight") - as int? ?? - 0; - - final unconfirmedCachedTransactions = - cachedTransactions?.getAllTransactions() ?? {}; - unconfirmedCachedTransactions - .removeWhere((key, value) => value.confirmedStatus); - - if (cachedTransactions != null) { - for (final tx in allTxHashes.toList(growable: false)) { - final txHeight = tx["height"] as int; - if (txHeight > 0 && - txHeight < latestTxnBlockHeight - MINIMUM_CONFIRMATIONS) { - if (unconfirmedCachedTransactions[tx["tx_hash"] as String] == null) { - allTxHashes.remove(tx); - } - } + bool _duplicateTxCheck( + List> allTransactions, String txid) { + for (int i = 0; i < allTransactions.length; i++) { + if (allTransactions[i]["txid"] == txid) { + return true; } } + return false; + } + + Future _refreshTransactions() async { + final List allAddresses = + await _fetchAllOwnAddresses(); + + final List> allTxHashes = + await _fetchHistory(allAddresses.map((e) => e.value).toList()); Set hashes = {}; for (var element in allTxHashes) { hashes.add(element['tx_hash'] as String); } await fastFetch(hashes.toList()); + List> allTransactions = []; + final currentHeight = await chainHeight; for (final txHash in allTxHashes) { - final tx = await cachedElectrumXClient.getTransaction( - txHash: txHash["tx_hash"] as String, - verbose: true, - coin: coin, - ); + final storedTx = await db + .getTransactions(walletId) + .filter() + .txidEqualTo(txHash["tx_hash"] as String) + .findFirst(); - // Logging.instance.log("TRANSACTION: ${jsonEncode(tx)}"); - // TODO fix this for sent to self transactions? - if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { - tx["address"] = txHash["address"]; - tx["height"] = txHash["height"]; - allTransactions.add(tx); + if (storedTx == null || + !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)) { + final tx = await cachedElectrumXClient.getTransaction( + txHash: txHash["tx_hash"] as String, + verbose: true, + coin: coin, + ); + + // Logging.instance.log("TRANSACTION: ${jsonEncode(tx)}"); + if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { + tx["address"] = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(txHash["address"] as String) + .findFirst(); + tx["height"] = txHash["height"]; + allTransactions.add(tx); + } } } - Logging.instance.log("addAddresses: $allAddresses", level: LogLevel.Info); - Logging.instance.log("allTxHashes: $allTxHashes", level: LogLevel.Info); - - Logging.instance.log("allTransactions length: ${allTransactions.length}", - level: LogLevel.Info); - - final priceData = - await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final List> midSortedArray = []; - + // prefetch/cache Set vHashes = {}; for (final txObject in allTransactions) { for (int i = 0; i < (txObject["vin"] as List).length; i++) { @@ -2489,251 +2225,33 @@ class LitecoinWallet extends CoinServiceAPI { } await fastFetch(vHashes.toList()); + final List> txnsData = + []; + for (final txObject in allTransactions) { - List sendersArray = []; - List recipientsArray = []; + final data = await parseTransaction( + txObject, + cachedElectrumXClient, + allAddresses, + coin, + MINIMUM_CONFIRMATIONS, + walletId, + ); - // Usually only has value when txType = 'Send' - int inputAmtSentFromWallet = 0; - // Usually has value regardless of txType due to change addresses - int outputAmtAddressedToWallet = 0; - int fee = 0; - - Map midSortedTx = {}; - - for (int i = 0; i < (txObject["vin"] as List).length; i++) { - final input = txObject["vin"]![i] as Map; - final prevTxid = input["txid"] as String; - final prevOut = input["vout"] as int; - - final tx = await _cachedElectrumXClient.getTransaction( - txHash: prevTxid, - coin: coin, - ); - - for (final out in tx["vout"] as List) { - if (prevOut == out["n"]) { - final address = out["scriptPubKey"]["addresses"][0] as String?; - if (address != null) { - sendersArray.add(address); - } - } - } - } - - Logging.instance.log("sendersArray: $sendersArray", level: LogLevel.Info); - - for (final output in txObject["vout"] as List) { - final address = output["scriptPubKey"]["addresses"][0] as String?; - if (address != null) { - recipientsArray.add(address); - } - } - - Logging.instance - .log("recipientsArray: $recipientsArray", level: LogLevel.Info); - - final foundInSenders = - allAddresses.any((element) => sendersArray.contains(element)); - Logging.instance - .log("foundInSenders: $foundInSenders", level: LogLevel.Info); - - // If txType = Sent, then calculate inputAmtSentFromWallet - if (foundInSenders) { - int totalInput = 0; - for (int i = 0; i < (txObject["vin"] as List).length; i++) { - final input = txObject["vin"]![i] as Map; - final prevTxid = input["txid"] as String; - final prevOut = input["vout"] as int; - final tx = await _cachedElectrumXClient.getTransaction( - txHash: prevTxid, - coin: coin, - ); - - for (final out in tx["vout"] as List) { - if (prevOut == out["n"]) { - inputAmtSentFromWallet += - (Decimal.parse(out["value"]!.toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - } - } - } - totalInput = inputAmtSentFromWallet; - int totalOutput = 0; - - for (final output in txObject["vout"] as List) { - final String address = - output["scriptPubKey"]!["addresses"][0] as String; - final value = output["value"]!; - final _value = (Decimal.parse(value.toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - totalOutput += _value; - if (changeAddresses.contains(address)) { - inputAmtSentFromWallet -= _value; - } else { - // change address from 'sent from' to the 'sent to' address - txObject["address"] = address; - } - } - // calculate transaction fee - fee = totalInput - totalOutput; - // subtract fee from sent to calculate correct value of sent tx - inputAmtSentFromWallet -= fee; - } else { - // counters for fee calculation - int totalOut = 0; - int totalIn = 0; - - // add up received tx value - for (final output in txObject["vout"] as List) { - final address = output["scriptPubKey"]["addresses"][0]; - if (address != null) { - final value = (Decimal.parse(output["value"].toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - totalOut += value; - if (allAddresses.contains(address)) { - outputAmtAddressedToWallet += value; - } - } - } - - // calculate fee for received tx - for (int i = 0; i < (txObject["vin"] as List).length; i++) { - final input = txObject["vin"][i] as Map; - final prevTxid = input["txid"] as String; - final prevOut = input["vout"] as int; - final tx = await _cachedElectrumXClient.getTransaction( - txHash: prevTxid, - coin: coin, - ); - - for (final out in tx["vout"] as List) { - if (prevOut == out["n"]) { - totalIn += (Decimal.parse(out["value"].toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - } - } - } - fee = totalIn - totalOut; - } - - // create final tx map - midSortedTx["txid"] = txObject["txid"]; - midSortedTx["confirmed_status"] = (txObject["confirmations"] != null) && - (txObject["confirmations"] as int >= MINIMUM_CONFIRMATIONS); - midSortedTx["confirmations"] = txObject["confirmations"] ?? 0; - midSortedTx["timestamp"] = txObject["blocktime"] ?? - (DateTime.now().millisecondsSinceEpoch ~/ 1000); - - if (foundInSenders) { - midSortedTx["txType"] = "Sent"; - midSortedTx["amount"] = inputAmtSentFromWallet; - final String worthNow = - ((currentPrice * Decimal.fromInt(inputAmtSentFromWallet)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2) - .toStringAsFixed(2); - midSortedTx["worthNow"] = worthNow; - midSortedTx["worthAtBlockTimestamp"] = worthNow; - } else { - midSortedTx["txType"] = "Received"; - midSortedTx["amount"] = outputAmtAddressedToWallet; - final worthNow = - ((currentPrice * Decimal.fromInt(outputAmtAddressedToWallet)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2) - .toStringAsFixed(2); - midSortedTx["worthNow"] = worthNow; - } - midSortedTx["aliens"] = []; - midSortedTx["fees"] = fee; - midSortedTx["address"] = txObject["address"]; - midSortedTx["inputSize"] = txObject["vin"].length; - midSortedTx["outputSize"] = txObject["vout"].length; - midSortedTx["inputs"] = txObject["vin"]; - midSortedTx["outputs"] = txObject["vout"]; - - final int height = txObject["height"] as int; - midSortedTx["height"] = height; - - if (height >= latestTxnBlockHeight) { - latestTxnBlockHeight = height; - } - - midSortedArray.add(midSortedTx); + txnsData.add(data); } + await db.addNewTransactionData(txnsData, walletId); - // sort by date ---- //TODO not sure if needed - // shouldn't be any issues with a null timestamp but I got one at some point? - midSortedArray - .sort((a, b) => (b["timestamp"] as int) - (a["timestamp"] as int)); - // { - // final aT = a["timestamp"]; - // final bT = b["timestamp"]; - // - // if (aT == null && bT == null) { - // return 0; - // } else if (aT == null) { - // return -1; - // } else if (bT == null) { - // return 1; - // } else { - // return bT - aT; - // } - // }); - - // buildDateTimeChunks - final Map result = {"dateTimeChunks": []}; - final dateArray = []; - - for (int i = 0; i < midSortedArray.length; i++) { - final txObject = midSortedArray[i]; - final date = extractDateFromTimestamp(txObject["timestamp"] as int); - final txTimeArray = [txObject["timestamp"], date]; - - if (dateArray.contains(txTimeArray[1])) { - result["dateTimeChunks"].forEach((dynamic chunk) { - if (extractDateFromTimestamp(chunk["timestamp"] as int) == - txTimeArray[1]) { - if (chunk["transactions"] == null) { - chunk["transactions"] = >[]; - } - chunk["transactions"].add(txObject); - } - }); - } else { - dateArray.add(txTimeArray[1]); - final chunk = { - "timestamp": txTimeArray[0], - "transactions": [txObject], - }; - result["dateTimeChunks"].add(chunk); - } + // quick hack to notify manager to call notifyListeners if + // transactions changed + if (txnsData.isNotEmpty) { + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "Transactions updated/added for: $walletId $walletName ", + walletId, + ), + ); } - - final transactionsMap = cachedTransactions?.getAllTransactions() ?? {}; - transactionsMap - .addAll(TransactionData.fromJson(result).getAllTransactions()); - - final txModel = TransactionData.fromMap(transactionsMap); - - await DB.instance.put( - boxName: walletId, - key: 'storedTxnDataHeight', - value: latestTxnBlockHeight); - await DB.instance.put( - boxName: walletId, key: 'latest_tx_model', value: txModel); - - cachedTxData = txModel; - return txModel; } int estimateTxFee({required int vSize, required int feeRatePerKB}) { @@ -2744,32 +2262,43 @@ class LitecoinWallet extends CoinServiceAPI { /// with [satoshiAmountToSend] and [selectedTxFeeRate]. If so, it will call buildTrasaction() and return /// a map containing the tx hex along with other important information. If not, then it will return /// an integer (1 or 2) - dynamic coinSelection( - int satoshiAmountToSend, - int selectedTxFeeRate, - String _recipientAddress, - bool isSendAll, { + dynamic coinSelection({ + required int satoshiAmountToSend, + required int selectedTxFeeRate, + required String recipientAddress, + required bool coinControl, + required bool isSendAll, int additionalOutputs = 0, - List? utxos, + List? utxos, }) async { Logging.instance .log("Starting coinSelection ----------", level: LogLevel.Info); - final List availableOutputs = utxos ?? outputsList; - final List spendableOutputs = []; + final List availableOutputs = utxos ?? await this.utxos; + final currentChainHeight = await chainHeight; + final List spendableOutputs = []; int spendableSatoshiValue = 0; // Build list of spendable outputs and totaling their satoshi amount - for (var i = 0; i < availableOutputs.length; i++) { - if (availableOutputs[i].blocked == false && - availableOutputs[i].status.confirmed == true) { - spendableOutputs.add(availableOutputs[i]); - spendableSatoshiValue += availableOutputs[i].value; + for (final utxo in availableOutputs) { + if (utxo.isBlocked == false && + utxo.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) && + utxo.used != true) { + spendableOutputs.add(utxo); + spendableSatoshiValue += utxo.value; } } - // sort spendable by age (oldest first) - spendableOutputs.sort( - (a, b) => b.status.confirmations.compareTo(a.status.confirmations)); + if (coinControl) { + if (spendableOutputs.length < availableOutputs.length) { + throw ArgumentError("Attempted to use an unavailable utxo"); + } + } + + // don't care about sorting if using all utxos + if (!coinControl) { + // sort spendable by age (oldest first) + spendableOutputs.sort((a, b) => b.blockTime!.compareTo(a.blockTime!)); + } Logging.instance.log("spendableOutputs.length: ${spendableOutputs.length}", level: LogLevel.Info); @@ -2796,21 +2325,29 @@ class LitecoinWallet extends CoinServiceAPI { // Possible situation right here int satoshisBeingUsed = 0; int inputsBeingConsumed = 0; - List utxoObjectsToUse = []; + List utxoObjectsToUse = []; - for (var i = 0; - satoshisBeingUsed < satoshiAmountToSend && i < spendableOutputs.length; - i++) { - utxoObjectsToUse.add(spendableOutputs[i]); - satoshisBeingUsed += spendableOutputs[i].value; - inputsBeingConsumed += 1; - } - for (int i = 0; - i < additionalOutputs && inputsBeingConsumed < spendableOutputs.length; - i++) { - utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]); - satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value; - inputsBeingConsumed += 1; + if (!coinControl) { + for (var i = 0; + satoshisBeingUsed < satoshiAmountToSend && + i < spendableOutputs.length; + i++) { + utxoObjectsToUse.add(spendableOutputs[i]); + satoshisBeingUsed += spendableOutputs[i].value; + inputsBeingConsumed += 1; + } + for (int i = 0; + i < additionalOutputs && + inputsBeingConsumed < spendableOutputs.length; + i++) { + utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]); + satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value; + inputsBeingConsumed += 1; + } + } else { + satoshisBeingUsed = spendableSatoshiValue; + utxoObjectsToUse = spendableOutputs; + inputsBeingConsumed = spendableOutputs.length; } Logging.instance @@ -2821,7 +2358,7 @@ class LitecoinWallet extends CoinServiceAPI { .log('utxoObjectsToUse: $utxoObjectsToUse', level: LogLevel.Info); // numberOfOutputs' length must always be equal to that of recipientsArray and recipientsAmtArray - List recipientsArray = [_recipientAddress]; + List recipientsArray = [recipientAddress]; List recipientsAmtArray = [satoshiAmountToSend]; // gather required signing data @@ -2832,9 +2369,8 @@ class LitecoinWallet extends CoinServiceAPI { .log("Attempting to send all $coin", level: LogLevel.Info); final int vSizeForOneOutput = (await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, - recipients: [_recipientAddress], + recipients: [recipientAddress], satoshiAmounts: [satoshisBeingUsed - 1], ))["vSize"] as int; int feeForOneOutput = estimateTxFee( @@ -2842,15 +2378,17 @@ class LitecoinWallet extends CoinServiceAPI { feeRatePerKB: selectedTxFeeRate, ); - final int roughEstimate = - roughFeeEstimate(spendableOutputs.length, 1, selectedTxFeeRate); + final int roughEstimate = roughFeeEstimate( + spendableOutputs.length, + 1, + selectedTxFeeRate, + ).raw.toInt(); if (feeForOneOutput < roughEstimate) { feeForOneOutput = roughEstimate; } final int amount = satoshiAmountToSend - feeForOneOutput; dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: [amount], @@ -2858,25 +2396,27 @@ class LitecoinWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": amount, + "recipientAmt": Amount( + rawValue: BigInt.from(amount), + fractionDigits: coin.decimals, + ), "fee": feeForOneOutput, "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), }; return transactionObject; } final int vSizeForOneOutput = (await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, - recipients: [_recipientAddress], + recipients: [recipientAddress], satoshiAmounts: [satoshisBeingUsed - 1], ))["vSize"] as int; final int vSizeForTwoOutPuts = (await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: [ - _recipientAddress, - await _getCurrentAddressForChain(1, DerivePathType.bip84), + recipientAddress, + await _getCurrentAddressForChain(1, DerivePathTypeExt.primaryFor(coin)), ], satoshiAmounts: [ satoshiAmountToSend, @@ -2902,7 +2442,7 @@ class LitecoinWallet extends CoinServiceAPI { if (satoshisBeingUsed - satoshiAmountToSend > feeForOneOutput) { if (satoshisBeingUsed - satoshiAmountToSend > - feeForOneOutput + DUST_LIMIT) { + feeForOneOutput + DUST_LIMIT.raw.toInt()) { // Here, we know that theoretically, we may be able to include another output(change) but we first need to // factor in the value of this output in satoshis. int changeOutputSize = @@ -2910,13 +2450,13 @@ class LitecoinWallet extends CoinServiceAPI { // We check to see if the user can pay for the new transaction with 2 outputs instead of one. If they can and // the second output's size > DUST_LIMIT satoshis, we perform the mechanics required to properly generate and use a new // change address. - if (changeOutputSize > DUST_LIMIT && + if (changeOutputSize > DUST_LIMIT.raw.toInt() && satoshisBeingUsed - satoshiAmountToSend - changeOutputSize == feeForTwoOutputs) { // generate new change address if current change address has been used - await _checkChangeAddressForTransactions(DerivePathType.bip84); - final String newChangeAddress = - await _getCurrentAddressForChain(1, DerivePathType.bip84); + await _checkChangeAddressForTransactions(); + final String newChangeAddress = await _getCurrentAddressForChain( + 1, DerivePathTypeExt.primaryFor(coin)); int feeBeingPaid = satoshisBeingUsed - satoshiAmountToSend - changeOutputSize; @@ -2938,7 +2478,6 @@ class LitecoinWallet extends CoinServiceAPI { Logging.instance .log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info); dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -2966,7 +2505,6 @@ class LitecoinWallet extends CoinServiceAPI { Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs', level: LogLevel.Info); txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -2976,9 +2514,13 @@ class LitecoinWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": feeBeingPaid, "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), }; return transactionObject; } else { @@ -2995,7 +2537,6 @@ class LitecoinWallet extends CoinServiceAPI { Logging.instance .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -3003,9 +2544,13 @@ class LitecoinWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": satoshisBeingUsed - satoshiAmountToSend, "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), }; return transactionObject; } @@ -3024,7 +2569,6 @@ class LitecoinWallet extends CoinServiceAPI { Logging.instance .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -3032,9 +2576,13 @@ class LitecoinWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": satoshisBeingUsed - satoshiAmountToSend, "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), }; return transactionObject; } @@ -3053,7 +2601,6 @@ class LitecoinWallet extends CoinServiceAPI { Logging.instance .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -3061,9 +2608,13 @@ class LitecoinWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": feeForOneOutput, "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), }; return transactionObject; } else { @@ -3075,251 +2626,160 @@ class LitecoinWallet extends CoinServiceAPI { level: LogLevel.Warning); // try adding more outputs if (spendableOutputs.length > inputsBeingConsumed) { - return coinSelection(satoshiAmountToSend, selectedTxFeeRate, - _recipientAddress, isSendAll, - additionalOutputs: additionalOutputs + 1, utxos: utxos); + return coinSelection( + satoshiAmountToSend: satoshiAmountToSend, + selectedTxFeeRate: selectedTxFeeRate, + recipientAddress: recipientAddress, + isSendAll: isSendAll, + additionalOutputs: additionalOutputs + 1, + utxos: utxos, + coinControl: coinControl, + ); } return 2; } } - Future> fetchBuildTxData( - List utxosToUse, + Future> fetchBuildTxData( + List utxosToUse, ) async { // return data - Map results = {}; - Map> addressTxid = {}; - - // addresses to check - List addressesP2PKH = []; - List addressesP2SH = []; - List addressesP2WPKH = []; + List signingData = []; try { // Populating the addresses to check for (var i = 0; i < utxosToUse.length; i++) { - final txid = utxosToUse[i].txid; - final tx = await _cachedElectrumXClient.getTransaction( - txHash: txid, - coin: coin, - ); - - for (final output in tx["vout"] as List) { - final n = output["n"]; - if (n != null && n == utxosToUse[i].vout) { - final address = output["scriptPubKey"]["addresses"][0] as String; - if (!addressTxid.containsKey(address)) { - addressTxid[address] = []; - } - (addressTxid[address] as List).add(txid); - switch (addressType(address: address)) { - case DerivePathType.bip44: - addressesP2PKH.add(address); - break; - case DerivePathType.bip49: - addressesP2SH.add(address); - break; - case DerivePathType.bip84: - addressesP2WPKH.add(address); - break; + if (utxosToUse[i].address == null) { + final txid = utxosToUse[i].txid; + final tx = await _cachedElectrumXClient.getTransaction( + txHash: txid, + coin: coin, + ); + for (final output in tx["vout"] as List) { + final n = output["n"]; + if (n != null && n == utxosToUse[i].vout) { + utxosToUse[i] = utxosToUse[i].copyWith( + address: output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]["address"] as String, + ); } } } + + final derivePathType = addressType(address: utxosToUse[i].address!); + + signingData.add( + SigningData( + derivePathType: derivePathType, + utxo: utxosToUse[i], + ), + ); } - // p2pkh / bip44 - final p2pkhLength = addressesP2PKH.length; - if (p2pkhLength > 0) { - final receiveDerivations = await _fetchDerivations( - chain: 0, - derivePathType: DerivePathType.bip44, - ); - final changeDerivations = await _fetchDerivations( - chain: 1, - derivePathType: DerivePathType.bip44, - ); - for (int i = 0; i < p2pkhLength; i++) { - // receives - final receiveDerivation = receiveDerivations[addressesP2PKH[i]]; - // if a match exists it will not be null - if (receiveDerivation != null) { - final data = P2PKH( - data: PaymentData( - pubkey: Format.stringToUint8List( - receiveDerivation["pubKey"] as String)), - network: _network, - ).data; + Map> receiveDerivations = {}; + Map> changeDerivations = {}; - for (String tx in addressTxid[addressesP2PKH[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - receiveDerivation["wif"] as String, - network: _network, - ), - }; - } - } else { - // if its not a receive, check change - final changeDerivation = changeDerivations[addressesP2PKH[i]]; - // if a match exists it will not be null - if (changeDerivation != null) { - final data = P2PKH( + for (final sd in signingData) { + String? pubKey; + String? wif; + + // fetch receiving derivations if null + receiveDerivations[sd.derivePathType] ??= await _fetchDerivations( + chain: 0, + derivePathType: sd.derivePathType, + ); + final receiveDerivation = + receiveDerivations[sd.derivePathType]![sd.utxo.address!]; + + if (receiveDerivation != null) { + pubKey = receiveDerivation["pubKey"] as String; + wif = receiveDerivation["wif"] as String; + } else { + // fetch change derivations if null + changeDerivations[sd.derivePathType] ??= await _fetchDerivations( + chain: 1, + derivePathType: sd.derivePathType, + ); + final changeDerivation = + changeDerivations[sd.derivePathType]![sd.utxo.address!]; + if (changeDerivation != null) { + pubKey = changeDerivation["pubKey"] as String; + wif = changeDerivation["wif"] as String; + } + } + + if (wif == null || pubKey == null) { + final address = await db.getAddress(walletId, sd.utxo.address!); + if (address?.derivationPath != null) { + final node = await Bip32Utils.getBip32Node( + (await mnemonicString)!, + (await mnemonicPassphrase)!, + _network, + address!.derivationPath!.value, + ); + + wif = node.toWIF(); + pubKey = Format.uint8listToString(node.publicKey); + } + } + + if (wif != null && pubKey != null) { + final PaymentData data; + final Uint8List? redeemScript; + + switch (sd.derivePathType) { + case DerivePathType.bip44: + data = P2PKH( data: PaymentData( - pubkey: Format.stringToUint8List( - changeDerivation["pubKey"] as String)), + pubkey: Format.stringToUint8List(pubKey), + ), network: _network, ).data; + redeemScript = null; + break; - for (String tx in addressTxid[addressesP2PKH[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - changeDerivation["wif"] as String, - network: _network, - ), - }; - } - } - } - } - } - - // p2sh / bip49 - final p2shLength = addressesP2SH.length; - if (p2shLength > 0) { - final receiveDerivations = await _fetchDerivations( - chain: 0, - derivePathType: DerivePathType.bip49, - ); - final changeDerivations = await _fetchDerivations( - chain: 1, - derivePathType: DerivePathType.bip49, - ); - for (int i = 0; i < p2shLength; i++) { - // receives - final receiveDerivation = receiveDerivations[addressesP2SH[i]]; - // if a match exists it will not be null - if (receiveDerivation != null) { - final p2wpkh = P2WPKH( - data: PaymentData( - pubkey: Format.stringToUint8List( - receiveDerivation["pubKey"] as String)), - network: _network, - overridePrefix: _network.bech32!) - .data; - - final redeemScript = p2wpkh.output; - - final data = - P2SH(data: PaymentData(redeem: p2wpkh), network: _network).data; - - for (String tx in addressTxid[addressesP2SH[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - receiveDerivation["wif"] as String, - network: _network, - ), - "redeemScript": redeemScript, - }; - } - } else { - // if its not a receive, check change - final changeDerivation = changeDerivations[addressesP2SH[i]]; - // if a match exists it will not be null - if (changeDerivation != null) { + case DerivePathType.bip49: final p2wpkh = P2WPKH( - data: PaymentData( - pubkey: Format.stringToUint8List( - changeDerivation["pubKey"] as String)), - network: _network, - overridePrefix: _network.bech32!) - .data; - - final redeemScript = p2wpkh.output; - - final data = - P2SH(data: PaymentData(redeem: p2wpkh), network: _network) - .data; - - for (String tx in addressTxid[addressesP2SH[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - changeDerivation["wif"] as String, - network: _network, - ), - "redeemScript": redeemScript, - }; - } - } - } - } - } - - // p2wpkh / bip84 - final p2wpkhLength = addressesP2WPKH.length; - if (p2wpkhLength > 0) { - final receiveDerivations = await _fetchDerivations( - chain: 0, - derivePathType: DerivePathType.bip84, - ); - final changeDerivations = await _fetchDerivations( - chain: 1, - derivePathType: DerivePathType.bip84, - ); - - for (int i = 0; i < p2wpkhLength; i++) { - // receives - final receiveDerivation = receiveDerivations[addressesP2WPKH[i]]; - // if a match exists it will not be null - if (receiveDerivation != null) { - final data = P2WPKH( - data: PaymentData( - pubkey: Format.stringToUint8List( - receiveDerivation["pubKey"] as String)), - network: _network, - overridePrefix: _network.bech32!) - .data; - - for (String tx in addressTxid[addressesP2WPKH[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - receiveDerivation["wif"] as String, - network: _network, + data: PaymentData( + pubkey: Format.stringToUint8List(pubKey), ), - }; - } - } else { - // if its not a receive, check change - final changeDerivation = changeDerivations[addressesP2WPKH[i]]; - // if a match exists it will not be null - if (changeDerivation != null) { - final data = P2WPKH( - data: PaymentData( - pubkey: Format.stringToUint8List( - changeDerivation["pubKey"] as String)), - network: _network, - overridePrefix: _network.bech32!) - .data; + network: _network, + overridePrefix: _network.bech32!, + ).data; + redeemScript = p2wpkh.output; + data = P2SH( + data: PaymentData(redeem: p2wpkh), + network: _network, + ).data; + break; - for (String tx in addressTxid[addressesP2WPKH[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - changeDerivation["wif"] as String, - network: _network, - ), - }; - } - } + case DerivePathType.bip84: + data = P2WPKH( + data: PaymentData( + pubkey: Format.stringToUint8List(pubKey), + ), + network: _network, + overridePrefix: _network.bech32!, + ).data; + redeemScript = null; + break; + + default: + throw Exception("DerivePathType unsupported"); } + + final keyPair = ECPair.fromWIF( + wif, + network: _network, + ); + + sd.redeemScript = redeemScript; + sd.output = data.output; + sd.keyPair = keyPair; } } - return results; + return signingData; } catch (e, s) { Logging.instance .log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error); @@ -3329,8 +2789,7 @@ class LitecoinWallet extends CoinServiceAPI { /// Builds and signs a transaction Future> buildTransaction({ - required List utxosToUse, - required Map utxoSigningData, + required List utxoSigningData, required List recipients, required List satoshiAmounts, }) async { @@ -3341,10 +2800,15 @@ class LitecoinWallet extends CoinServiceAPI { txb.setVersion(1); // Add transaction inputs - for (var i = 0; i < utxosToUse.length; i++) { - final txid = utxosToUse[i].txid; - txb.addInput(txid, utxosToUse[i].vout, null, - utxoSigningData[txid]["output"] as Uint8List, _network.bech32!); + for (var i = 0; i < utxoSigningData.length; i++) { + final txid = utxoSigningData[i].utxo.txid; + txb.addInput( + txid, + utxoSigningData[i].utxo.vout, + null, + utxoSigningData[i].output!, + _network.bech32!, + ); } // Add transaction output @@ -3354,14 +2818,14 @@ class LitecoinWallet extends CoinServiceAPI { try { // Sign the transaction accordingly - for (var i = 0; i < utxosToUse.length; i++) { - final txid = utxosToUse[i].txid; + for (var i = 0; i < utxoSigningData.length; i++) { txb.sign( - vin: i, - keyPair: utxoSigningData[txid]["keyPair"] as ECPair, - witnessValue: utxosToUse[i].value, - redeemScript: utxoSigningData[txid]["redeemScript"] as Uint8List?, - overridePrefix: _network.bech32!); + vin: i, + keyPair: utxoSigningData[i].keyPair!, + witnessValue: utxoSigningData[i].utxo.value, + redeemScript: utxoSigningData[i].redeemScript, + overridePrefix: _network.bech32!, + ); } } catch (e, s) { Logging.instance.log("Caught exception while signing transaction: $e\n$s", @@ -3394,17 +2858,31 @@ class LitecoinWallet extends CoinServiceAPI { await _cachedElectrumXClient.clearSharedTransactionCache(coin: coin); // back up data - await _rescanBackup(); + // await _rescanBackup(); + + // clear blockchain info + await db.deleteWalletBlockchainData(walletId); + await _deleteDerivations(); try { - final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic'); + final _mnemonic = await mnemonicString; + final _mnemonicPassphrase = await mnemonicPassphrase; + if (_mnemonicPassphrase == null) { + Logging.instance.log( + "Exception in fullRescan: mnemonic passphrase null, possible migration issue; if using internal builds, delete wallet and restore from seed, if using a release build, please file bug report", + level: LogLevel.Error); + } + await _recoverWalletFromBIP32SeedPhrase( - mnemonic: mnemonic!, + mnemonic: _mnemonic!, + mnemonicPassphrase: _mnemonicPassphrase!, maxUnusedAddressGap: maxUnusedAddressGap, maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + isRescan: true, ); longMutex = false; + await refresh(); Logging.instance.log("Full rescan complete!", level: LogLevel.Info); GlobalEventBus.instance.fire( WalletSyncStatusChangedEvent( @@ -3423,7 +2901,7 @@ class LitecoinWallet extends CoinServiceAPI { ); // restore from backup - await _rescanRestore(); + // await _rescanRestore(); longMutex = false; Logging.instance.log("Exception rethrown from fullRescan(): $e\n$s", @@ -3432,346 +2910,360 @@ class LitecoinWallet extends CoinServiceAPI { } } - Future _rescanRestore() async { - Logging.instance.log("starting rescan restore", level: LogLevel.Info); - - // restore from backup - // p2pkh - final tempReceivingAddressesP2PKH = DB.instance - .get(boxName: walletId, key: 'receivingAddressesP2PKH_BACKUP'); - final tempChangeAddressesP2PKH = DB.instance - .get(boxName: walletId, key: 'changeAddressesP2PKH_BACKUP'); - final tempReceivingIndexP2PKH = DB.instance - .get(boxName: walletId, key: 'receivingIndexP2PKH_BACKUP'); - final tempChangeIndexP2PKH = DB.instance - .get(boxName: walletId, key: 'changeIndexP2PKH_BACKUP'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2PKH', - value: tempReceivingAddressesP2PKH); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2PKH', - value: tempChangeAddressesP2PKH); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2PKH', - value: tempReceivingIndexP2PKH); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2PKH', - value: tempChangeIndexP2PKH); - await DB.instance.delete( - key: 'receivingAddressesP2PKH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'changeAddressesP2PKH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'receivingIndexP2PKH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'changeIndexP2PKH_BACKUP', boxName: walletId); - - // p2Sh - final tempReceivingAddressesP2SH = DB.instance - .get(boxName: walletId, key: 'receivingAddressesP2SH_BACKUP'); - final tempChangeAddressesP2SH = DB.instance - .get(boxName: walletId, key: 'changeAddressesP2SH_BACKUP'); - final tempReceivingIndexP2SH = DB.instance - .get(boxName: walletId, key: 'receivingIndexP2SH_BACKUP'); - final tempChangeIndexP2SH = DB.instance - .get(boxName: walletId, key: 'changeIndexP2SH_BACKUP'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2SH', - value: tempReceivingAddressesP2SH); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2SH', - value: tempChangeAddressesP2SH); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2SH', - value: tempReceivingIndexP2SH); - await DB.instance.put( - boxName: walletId, key: 'changeIndexP2SH', value: tempChangeIndexP2SH); - await DB.instance.delete( - key: 'receivingAddressesP2SH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'changeAddressesP2SH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'receivingIndexP2SH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'changeIndexP2SH_BACKUP', boxName: walletId); - - // p2wpkh - final tempReceivingAddressesP2WPKH = DB.instance.get( - boxName: walletId, key: 'receivingAddressesP2WPKH_BACKUP'); - final tempChangeAddressesP2WPKH = DB.instance - .get(boxName: walletId, key: 'changeAddressesP2WPKH_BACKUP'); - final tempReceivingIndexP2WPKH = DB.instance - .get(boxName: walletId, key: 'receivingIndexP2WPKH_BACKUP'); - final tempChangeIndexP2WPKH = DB.instance - .get(boxName: walletId, key: 'changeIndexP2WPKH_BACKUP'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2WPKH', - value: tempReceivingAddressesP2WPKH); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2WPKH', - value: tempChangeAddressesP2WPKH); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2WPKH', - value: tempReceivingIndexP2WPKH); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2WPKH', - value: tempChangeIndexP2WPKH); - await DB.instance.delete( - key: 'receivingAddressesP2WPKH_BACKUP', boxName: walletId); - await DB.instance.delete( - key: 'changeAddressesP2WPKH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'receivingIndexP2WPKH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'changeIndexP2WPKH_BACKUP', boxName: walletId); - + Future _deleteDerivations() async { // P2PKH derivations - final p2pkhReceiveDerivationsString = await _secureStore.read( - key: "${walletId}_receiveDerivationsP2PKH_BACKUP"); - final p2pkhChangeDerivationsString = await _secureStore.read( - key: "${walletId}_changeDerivationsP2PKH_BACKUP"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2PKH", - value: p2pkhReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2PKH", - value: p2pkhChangeDerivationsString); - - await _secureStore.delete( - key: "${walletId}_receiveDerivationsP2PKH_BACKUP"); - await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH_BACKUP"); - - // P2SH derivations - final p2shReceiveDerivationsString = await _secureStore.read( - key: "${walletId}_receiveDerivationsP2SH_BACKUP"); - final p2shChangeDerivationsString = await _secureStore.read( - key: "${walletId}_changeDerivationsP2SH_BACKUP"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2SH", - value: p2shReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2SH", - value: p2shChangeDerivationsString); - - await _secureStore.delete(key: "${walletId}_receiveDerivationsP2SH_BACKUP"); - await _secureStore.delete(key: "${walletId}_changeDerivationsP2SH_BACKUP"); - - // P2WPKH derivations - final p2wpkhReceiveDerivationsString = await _secureStore.read( - key: "${walletId}_receiveDerivationsP2WPKH_BACKUP"); - final p2wpkhChangeDerivationsString = await _secureStore.read( - key: "${walletId}_changeDerivationsP2WPKH_BACKUP"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2WPKH", - value: p2wpkhReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2WPKH", - value: p2wpkhChangeDerivationsString); - - await _secureStore.delete( - key: "${walletId}_receiveDerivationsP2WPKH_BACKUP"); - await _secureStore.delete( - key: "${walletId}_changeDerivationsP2WPKH_BACKUP"); - - // UTXOs - final utxoData = DB.instance - .get(boxName: walletId, key: 'latest_utxo_model_BACKUP'); - await DB.instance.put( - boxName: walletId, key: 'latest_utxo_model', value: utxoData); - await DB.instance - .delete(key: 'latest_utxo_model_BACKUP', boxName: walletId); - - Logging.instance.log("rescan restore complete", level: LogLevel.Info); - } - - Future _rescanBackup() async { - Logging.instance.log("starting rescan backup", level: LogLevel.Info); - - // backup current and clear data - // p2pkh - final tempReceivingAddressesP2PKH = DB.instance - .get(boxName: walletId, key: 'receivingAddressesP2PKH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2PKH_BACKUP', - value: tempReceivingAddressesP2PKH); - await DB.instance - .delete(key: 'receivingAddressesP2PKH', boxName: walletId); - - final tempChangeAddressesP2PKH = DB.instance - .get(boxName: walletId, key: 'changeAddressesP2PKH'); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2PKH_BACKUP', - value: tempChangeAddressesP2PKH); - await DB.instance - .delete(key: 'changeAddressesP2PKH', boxName: walletId); - - final tempReceivingIndexP2PKH = - DB.instance.get(boxName: walletId, key: 'receivingIndexP2PKH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2PKH_BACKUP', - value: tempReceivingIndexP2PKH); - await DB.instance - .delete(key: 'receivingIndexP2PKH', boxName: walletId); - - final tempChangeIndexP2PKH = - DB.instance.get(boxName: walletId, key: 'changeIndexP2PKH'); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2PKH_BACKUP', - value: tempChangeIndexP2PKH); - await DB.instance - .delete(key: 'changeIndexP2PKH', boxName: walletId); - - // p2sh - final tempReceivingAddressesP2SH = DB.instance - .get(boxName: walletId, key: 'receivingAddressesP2SH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2SH_BACKUP', - value: tempReceivingAddressesP2SH); - await DB.instance - .delete(key: 'receivingAddressesP2SH', boxName: walletId); - - final tempChangeAddressesP2SH = - DB.instance.get(boxName: walletId, key: 'changeAddressesP2SH'); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2SH_BACKUP', - value: tempChangeAddressesP2SH); - await DB.instance - .delete(key: 'changeAddressesP2SH', boxName: walletId); - - final tempReceivingIndexP2SH = - DB.instance.get(boxName: walletId, key: 'receivingIndexP2SH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2SH_BACKUP', - value: tempReceivingIndexP2SH); - await DB.instance - .delete(key: 'receivingIndexP2SH', boxName: walletId); - - final tempChangeIndexP2SH = - DB.instance.get(boxName: walletId, key: 'changeIndexP2SH'); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2SH_BACKUP', - value: tempChangeIndexP2SH); - await DB.instance - .delete(key: 'changeIndexP2SH', boxName: walletId); - - // p2wpkh - final tempReceivingAddressesP2WPKH = DB.instance - .get(boxName: walletId, key: 'receivingAddressesP2WPKH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2WPKH_BACKUP', - value: tempReceivingAddressesP2WPKH); - await DB.instance - .delete(key: 'receivingAddressesP2WPKH', boxName: walletId); - - final tempChangeAddressesP2WPKH = DB.instance - .get(boxName: walletId, key: 'changeAddressesP2WPKH'); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2WPKH_BACKUP', - value: tempChangeAddressesP2WPKH); - await DB.instance - .delete(key: 'changeAddressesP2WPKH', boxName: walletId); - - final tempReceivingIndexP2WPKH = DB.instance - .get(boxName: walletId, key: 'receivingIndexP2WPKH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2WPKH_BACKUP', - value: tempReceivingIndexP2WPKH); - await DB.instance - .delete(key: 'receivingIndexP2WPKH', boxName: walletId); - - final tempChangeIndexP2WPKH = - DB.instance.get(boxName: walletId, key: 'changeIndexP2WPKH'); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2WPKH_BACKUP', - value: tempChangeIndexP2WPKH); - await DB.instance - .delete(key: 'changeIndexP2WPKH', boxName: walletId); - - // P2PKH derivations - final p2pkhReceiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivationsP2PKH"); - final p2pkhChangeDerivationsString = - await _secureStore.read(key: "${walletId}_changeDerivationsP2PKH"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2PKH_BACKUP", - value: p2pkhReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2PKH_BACKUP", - value: p2pkhChangeDerivationsString); - await _secureStore.delete(key: "${walletId}_receiveDerivationsP2PKH"); await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH"); // P2SH derivations - final p2shReceiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivationsP2SH"); - final p2shChangeDerivationsString = - await _secureStore.read(key: "${walletId}_changeDerivationsP2SH"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2SH_BACKUP", - value: p2shReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2SH_BACKUP", - value: p2shChangeDerivationsString); - await _secureStore.delete(key: "${walletId}_receiveDerivationsP2SH"); await _secureStore.delete(key: "${walletId}_changeDerivationsP2SH"); // P2WPKH derivations - final p2wpkhReceiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivationsP2WPKH"); - final p2wpkhChangeDerivationsString = - await _secureStore.read(key: "${walletId}_changeDerivationsP2WPKH"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2WPKH_BACKUP", - value: p2wpkhReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2WPKH_BACKUP", - value: p2wpkhChangeDerivationsString); - await _secureStore.delete(key: "${walletId}_receiveDerivationsP2WPKH"); await _secureStore.delete(key: "${walletId}_changeDerivationsP2WPKH"); - - // UTXOs - final utxoData = - DB.instance.get(boxName: walletId, key: 'latest_utxo_model'); - await DB.instance.put( - boxName: walletId, key: 'latest_utxo_model_BACKUP', value: utxoData); - await DB.instance - .delete(key: 'latest_utxo_model', boxName: walletId); - - Logging.instance.log("rescan backup complete", level: LogLevel.Info); } + // Future _rescanRestore() async { + // Logging.instance.log("starting rescan restore", level: LogLevel.Info); + // + // // restore from backup + // // p2pkh + // final tempReceivingAddressesP2PKH = DB.instance + // .get(boxName: walletId, key: 'receivingAddressesP2PKH_BACKUP'); + // final tempChangeAddressesP2PKH = DB.instance + // .get(boxName: walletId, key: 'changeAddressesP2PKH_BACKUP'); + // final tempReceivingIndexP2PKH = DB.instance + // .get(boxName: walletId, key: 'receivingIndexP2PKH_BACKUP'); + // final tempChangeIndexP2PKH = DB.instance + // .get(boxName: walletId, key: 'changeIndexP2PKH_BACKUP'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingAddressesP2PKH', + // value: tempReceivingAddressesP2PKH); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeAddressesP2PKH', + // value: tempChangeAddressesP2PKH); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingIndexP2PKH', + // value: tempReceivingIndexP2PKH); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeIndexP2PKH', + // value: tempChangeIndexP2PKH); + // await DB.instance.delete( + // key: 'receivingAddressesP2PKH_BACKUP', boxName: walletId); + // await DB.instance + // .delete(key: 'changeAddressesP2PKH_BACKUP', boxName: walletId); + // await DB.instance + // .delete(key: 'receivingIndexP2PKH_BACKUP', boxName: walletId); + // await DB.instance + // .delete(key: 'changeIndexP2PKH_BACKUP', boxName: walletId); + // + // // p2Sh + // final tempReceivingAddressesP2SH = DB.instance + // .get(boxName: walletId, key: 'receivingAddressesP2SH_BACKUP'); + // final tempChangeAddressesP2SH = DB.instance + // .get(boxName: walletId, key: 'changeAddressesP2SH_BACKUP'); + // final tempReceivingIndexP2SH = DB.instance + // .get(boxName: walletId, key: 'receivingIndexP2SH_BACKUP'); + // final tempChangeIndexP2SH = DB.instance + // .get(boxName: walletId, key: 'changeIndexP2SH_BACKUP'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingAddressesP2SH', + // value: tempReceivingAddressesP2SH); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeAddressesP2SH', + // value: tempChangeAddressesP2SH); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingIndexP2SH', + // value: tempReceivingIndexP2SH); + // await DB.instance.put( + // boxName: walletId, key: 'changeIndexP2SH', value: tempChangeIndexP2SH); + // await DB.instance.delete( + // key: 'receivingAddressesP2SH_BACKUP', boxName: walletId); + // await DB.instance + // .delete(key: 'changeAddressesP2SH_BACKUP', boxName: walletId); + // await DB.instance + // .delete(key: 'receivingIndexP2SH_BACKUP', boxName: walletId); + // await DB.instance + // .delete(key: 'changeIndexP2SH_BACKUP', boxName: walletId); + // + // // p2wpkh + // final tempReceivingAddressesP2WPKH = DB.instance.get( + // boxName: walletId, key: 'receivingAddressesP2WPKH_BACKUP'); + // final tempChangeAddressesP2WPKH = DB.instance + // .get(boxName: walletId, key: 'changeAddressesP2WPKH_BACKUP'); + // final tempReceivingIndexP2WPKH = DB.instance + // .get(boxName: walletId, key: 'receivingIndexP2WPKH_BACKUP'); + // final tempChangeIndexP2WPKH = DB.instance + // .get(boxName: walletId, key: 'changeIndexP2WPKH_BACKUP'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingAddressesP2WPKH', + // value: tempReceivingAddressesP2WPKH); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeAddressesP2WPKH', + // value: tempChangeAddressesP2WPKH); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingIndexP2WPKH', + // value: tempReceivingIndexP2WPKH); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeIndexP2WPKH', + // value: tempChangeIndexP2WPKH); + // await DB.instance.delete( + // key: 'receivingAddressesP2WPKH_BACKUP', boxName: walletId); + // await DB.instance.delete( + // key: 'changeAddressesP2WPKH_BACKUP', boxName: walletId); + // await DB.instance + // .delete(key: 'receivingIndexP2WPKH_BACKUP', boxName: walletId); + // await DB.instance + // .delete(key: 'changeIndexP2WPKH_BACKUP', boxName: walletId); + // + // // P2PKH derivations + // final p2pkhReceiveDerivationsString = await _secureStore.read( + // key: "${walletId}_receiveDerivationsP2PKH_BACKUP"); + // final p2pkhChangeDerivationsString = await _secureStore.read( + // key: "${walletId}_changeDerivationsP2PKH_BACKUP"); + // + // await _secureStore.write( + // key: "${walletId}_receiveDerivationsP2PKH", + // value: p2pkhReceiveDerivationsString); + // await _secureStore.write( + // key: "${walletId}_changeDerivationsP2PKH", + // value: p2pkhChangeDerivationsString); + // + // await _secureStore.delete( + // key: "${walletId}_receiveDerivationsP2PKH_BACKUP"); + // await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH_BACKUP"); + // + // // P2SH derivations + // final p2shReceiveDerivationsString = await _secureStore.read( + // key: "${walletId}_receiveDerivationsP2SH_BACKUP"); + // final p2shChangeDerivationsString = await _secureStore.read( + // key: "${walletId}_changeDerivationsP2SH_BACKUP"); + // + // await _secureStore.write( + // key: "${walletId}_receiveDerivationsP2SH", + // value: p2shReceiveDerivationsString); + // await _secureStore.write( + // key: "${walletId}_changeDerivationsP2SH", + // value: p2shChangeDerivationsString); + // + // await _secureStore.delete(key: "${walletId}_receiveDerivationsP2SH_BACKUP"); + // await _secureStore.delete(key: "${walletId}_changeDerivationsP2SH_BACKUP"); + // + // // P2WPKH derivations + // final p2wpkhReceiveDerivationsString = await _secureStore.read( + // key: "${walletId}_receiveDerivationsP2WPKH_BACKUP"); + // final p2wpkhChangeDerivationsString = await _secureStore.read( + // key: "${walletId}_changeDerivationsP2WPKH_BACKUP"); + // + // await _secureStore.write( + // key: "${walletId}_receiveDerivationsP2WPKH", + // value: p2wpkhReceiveDerivationsString); + // await _secureStore.write( + // key: "${walletId}_changeDerivationsP2WPKH", + // value: p2wpkhChangeDerivationsString); + // + // await _secureStore.delete( + // key: "${walletId}_receiveDerivationsP2WPKH_BACKUP"); + // await _secureStore.delete( + // key: "${walletId}_changeDerivationsP2WPKH_BACKUP"); + // + // // UTXOs + // final utxoData = DB.instance + // .get(boxName: walletId, key: 'latest_utxo_model_BACKUP'); + // await DB.instance.put( + // boxName: walletId, key: 'latest_utxo_model', value: utxoData); + // await DB.instance + // .delete(key: 'latest_utxo_model_BACKUP', boxName: walletId); + // + // Logging.instance.log("rescan restore complete", level: LogLevel.Info); + // } + // + // Future _rescanBackup() async { + // Logging.instance.log("starting rescan backup", level: LogLevel.Info); + // + // // backup current and clear data + // // p2pkh + // final tempReceivingAddressesP2PKH = DB.instance + // .get(boxName: walletId, key: 'receivingAddressesP2PKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingAddressesP2PKH_BACKUP', + // value: tempReceivingAddressesP2PKH); + // await DB.instance + // .delete(key: 'receivingAddressesP2PKH', boxName: walletId); + // + // final tempChangeAddressesP2PKH = DB.instance + // .get(boxName: walletId, key: 'changeAddressesP2PKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeAddressesP2PKH_BACKUP', + // value: tempChangeAddressesP2PKH); + // await DB.instance + // .delete(key: 'changeAddressesP2PKH', boxName: walletId); + // + // final tempReceivingIndexP2PKH = + // DB.instance.get(boxName: walletId, key: 'receivingIndexP2PKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingIndexP2PKH_BACKUP', + // value: tempReceivingIndexP2PKH); + // await DB.instance + // .delete(key: 'receivingIndexP2PKH', boxName: walletId); + // + // final tempChangeIndexP2PKH = + // DB.instance.get(boxName: walletId, key: 'changeIndexP2PKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeIndexP2PKH_BACKUP', + // value: tempChangeIndexP2PKH); + // await DB.instance + // .delete(key: 'changeIndexP2PKH', boxName: walletId); + // + // // p2sh + // final tempReceivingAddressesP2SH = DB.instance + // .get(boxName: walletId, key: 'receivingAddressesP2SH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingAddressesP2SH_BACKUP', + // value: tempReceivingAddressesP2SH); + // await DB.instance + // .delete(key: 'receivingAddressesP2SH', boxName: walletId); + // + // final tempChangeAddressesP2SH = + // DB.instance.get(boxName: walletId, key: 'changeAddressesP2SH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeAddressesP2SH_BACKUP', + // value: tempChangeAddressesP2SH); + // await DB.instance + // .delete(key: 'changeAddressesP2SH', boxName: walletId); + // + // final tempReceivingIndexP2SH = + // DB.instance.get(boxName: walletId, key: 'receivingIndexP2SH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingIndexP2SH_BACKUP', + // value: tempReceivingIndexP2SH); + // await DB.instance + // .delete(key: 'receivingIndexP2SH', boxName: walletId); + // + // final tempChangeIndexP2SH = + // DB.instance.get(boxName: walletId, key: 'changeIndexP2SH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeIndexP2SH_BACKUP', + // value: tempChangeIndexP2SH); + // await DB.instance + // .delete(key: 'changeIndexP2SH', boxName: walletId); + // + // // p2wpkh + // final tempReceivingAddressesP2WPKH = DB.instance + // .get(boxName: walletId, key: 'receivingAddressesP2WPKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingAddressesP2WPKH_BACKUP', + // value: tempReceivingAddressesP2WPKH); + // await DB.instance + // .delete(key: 'receivingAddressesP2WPKH', boxName: walletId); + // + // final tempChangeAddressesP2WPKH = DB.instance + // .get(boxName: walletId, key: 'changeAddressesP2WPKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeAddressesP2WPKH_BACKUP', + // value: tempChangeAddressesP2WPKH); + // await DB.instance + // .delete(key: 'changeAddressesP2WPKH', boxName: walletId); + // + // final tempReceivingIndexP2WPKH = DB.instance + // .get(boxName: walletId, key: 'receivingIndexP2WPKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingIndexP2WPKH_BACKUP', + // value: tempReceivingIndexP2WPKH); + // await DB.instance + // .delete(key: 'receivingIndexP2WPKH', boxName: walletId); + // + // final tempChangeIndexP2WPKH = + // DB.instance.get(boxName: walletId, key: 'changeIndexP2WPKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeIndexP2WPKH_BACKUP', + // value: tempChangeIndexP2WPKH); + // await DB.instance + // .delete(key: 'changeIndexP2WPKH', boxName: walletId); + // + // // P2PKH derivations + // final p2pkhReceiveDerivationsString = + // await _secureStore.read(key: "${walletId}_receiveDerivationsP2PKH"); + // final p2pkhChangeDerivationsString = + // await _secureStore.read(key: "${walletId}_changeDerivationsP2PKH"); + // + // await _secureStore.write( + // key: "${walletId}_receiveDerivationsP2PKH_BACKUP", + // value: p2pkhReceiveDerivationsString); + // await _secureStore.write( + // key: "${walletId}_changeDerivationsP2PKH_BACKUP", + // value: p2pkhChangeDerivationsString); + // + // await _secureStore.delete(key: "${walletId}_receiveDerivationsP2PKH"); + // await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH"); + // + // // P2SH derivations + // final p2shReceiveDerivationsString = + // await _secureStore.read(key: "${walletId}_receiveDerivationsP2SH"); + // final p2shChangeDerivationsString = + // await _secureStore.read(key: "${walletId}_changeDerivationsP2SH"); + // + // await _secureStore.write( + // key: "${walletId}_receiveDerivationsP2SH_BACKUP", + // value: p2shReceiveDerivationsString); + // await _secureStore.write( + // key: "${walletId}_changeDerivationsP2SH_BACKUP", + // value: p2shChangeDerivationsString); + // + // await _secureStore.delete(key: "${walletId}_receiveDerivationsP2SH"); + // await _secureStore.delete(key: "${walletId}_changeDerivationsP2SH"); + // + // // P2WPKH derivations + // final p2wpkhReceiveDerivationsString = + // await _secureStore.read(key: "${walletId}_receiveDerivationsP2WPKH"); + // final p2wpkhChangeDerivationsString = + // await _secureStore.read(key: "${walletId}_changeDerivationsP2WPKH"); + // + // await _secureStore.write( + // key: "${walletId}_receiveDerivationsP2WPKH_BACKUP", + // value: p2wpkhReceiveDerivationsString); + // await _secureStore.write( + // key: "${walletId}_changeDerivationsP2WPKH_BACKUP", + // value: p2wpkhChangeDerivationsString); + // + // await _secureStore.delete(key: "${walletId}_receiveDerivationsP2WPKH"); + // await _secureStore.delete(key: "${walletId}_changeDerivationsP2WPKH"); + // + // // UTXOs + // final utxoData = + // DB.instance.get(boxName: walletId, key: 'latest_utxo_model'); + // await DB.instance.put( + // boxName: walletId, key: 'latest_utxo_model_BACKUP', value: utxoData); + // await DB.instance + // .delete(key: 'latest_utxo_model', boxName: walletId); + // + // Logging.instance.log("rescan backup complete", level: LogLevel.Info); + // } + bool isActive = false; @override @@ -3779,58 +3271,71 @@ class LitecoinWallet extends CoinServiceAPI { (isActive) => this.isActive = isActive; @override - Future estimateFeeFor(int satoshiAmount, int feeRate) async { - final available = - Format.decimalAmountToSatoshis(await availableBalance, coin); + Future estimateFeeFor(Amount amount, int feeRate) async { + final available = balance.spendable; - if (available == satoshiAmount) { - return satoshiAmount - sweepAllEstimate(feeRate); - } else if (satoshiAmount <= 0 || satoshiAmount > available) { + if (available == amount) { + return amount - (await sweepAllEstimate(feeRate)); + } else if (amount <= Amount.zero || amount > available) { return roughFeeEstimate(1, 2, feeRate); } - int runningBalance = 0; + Amount runningBalance = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); int inputCount = 0; - for (final output in outputsList) { - runningBalance += output.value; - inputCount++; - if (runningBalance > satoshiAmount) { - break; + for (final output in (await utxos)) { + if (!output.isBlocked) { + runningBalance = runningBalance + + Amount( + rawValue: BigInt.from(output.value), + fractionDigits: coin.decimals, + ); + inputCount++; + if (runningBalance > amount) { + break; + } } } final oneOutPutFee = roughFeeEstimate(inputCount, 1, feeRate); final twoOutPutFee = roughFeeEstimate(inputCount, 2, feeRate); - if (runningBalance - satoshiAmount > oneOutPutFee) { - if (runningBalance - satoshiAmount > oneOutPutFee + DUST_LIMIT) { - final change = runningBalance - satoshiAmount - twoOutPutFee; + if (runningBalance - amount > oneOutPutFee) { + if (runningBalance - amount > oneOutPutFee + DUST_LIMIT) { + final change = runningBalance - amount - twoOutPutFee; if (change > DUST_LIMIT && - runningBalance - satoshiAmount - change == twoOutPutFee) { - return runningBalance - satoshiAmount - change; + runningBalance - amount - change == twoOutPutFee) { + return runningBalance - amount - change; } else { - return runningBalance - satoshiAmount; + return runningBalance - amount; } } else { - return runningBalance - satoshiAmount; + return runningBalance - amount; } - } else if (runningBalance - satoshiAmount == oneOutPutFee) { + } else if (runningBalance - amount == oneOutPutFee) { return oneOutPutFee; } else { return twoOutPutFee; } } - int roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { - return ((42 + (272 * inputCount) + (128 * outputCount)) / 4).ceil() * - (feeRatePerKB / 1000).ceil(); + Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { + return Amount( + rawValue: BigInt.from( + ((42 + (272 * inputCount) + (128 * outputCount)) / 4).ceil() * + (feeRatePerKB / 1000).ceil()), + fractionDigits: coin.decimals, + ); } - int sweepAllEstimate(int feeRate) { + Future sweepAllEstimate(int feeRate) async { int available = 0; int inputCount = 0; - for (final output in outputsList) { - if (output.status.confirmed) { + for (final output in (await utxos)) { + if (!output.isBlocked && + output.isConfirmed(storedChainHeight, MINIMUM_CONFIRMATIONS)) { available += output.value; inputCount++; } @@ -3839,29 +3344,26 @@ class LitecoinWallet extends CoinServiceAPI { // transaction will only have 1 output minus the fee final estimatedFee = roughFeeEstimate(inputCount, 1, feeRate); - return available - estimatedFee; + return Amount( + rawValue: BigInt.from(available), + fractionDigits: coin.decimals, + ) - + estimatedFee; } @override Future generateNewAddress() async { try { - await _incrementAddressIndexForChain( - 0, DerivePathType.bip84); // First increment the receiving index - final newReceivingIndex = DB.instance.get( - boxName: walletId, - key: 'receivingIndexP2WPKH') as int; // Check the new receiving index + final currentReceiving = await _currentReceivingAddress; + + final newReceivingIndex = currentReceiving.derivationIndex + 1; + + // Use new index to derive a new receiving address final newReceivingAddress = await _generateAddressForChain( - 0, - newReceivingIndex, - DerivePathType - .bip84); // Use new index to derive a new receiving address - await _addToAddressesArrayForChain( - newReceivingAddress, - 0, - DerivePathType - .bip84); // Add that new receiving address to the array of receiving addresses - _currentReceivingAddress = Future(() => - newReceivingAddress); // Set the new receiving address that the service + 0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin)); + + // Add that new receiving address + await db.putAddress(newReceivingAddress); return true; } catch (e, s) { @@ -3871,6 +3373,17 @@ class LitecoinWallet extends CoinServiceAPI { return false; } } + + @override + Future get xpub async { + final node = await Bip32Utils.getBip32Root( + (await mnemonic).join(" "), + await mnemonicPassphrase ?? "", + _network, + ); + + return node.neutered().toBase58(); + } } final litecoin = NetworkType( diff --git a/lib/services/coins/manager.dart b/lib/services/coins/manager.dart index c850358eb..b8bb6d6d4 100644 --- a/lib/services/coins/manager.dart +++ b/lib/services/coins/manager.dart @@ -1,13 +1,19 @@ import 'dart:async'; -import 'package:decimal/decimal.dart'; import 'package:event_bus/event_bus.dart'; import 'package:flutter/material.dart'; +import 'package:stackwallet/db/hive/db.dart'; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; import 'package:stackwallet/models/models.dart'; import 'package:stackwallet/services/coins/coin_service.dart'; import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +import 'package:stackwallet/services/mixins/coin_control_interface.dart'; +import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart'; +import 'package:stackwallet/services/mixins/xpubable.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; @@ -59,7 +65,6 @@ class Manager with ChangeNotifier { Future updateNode(bool shouldRefresh) async { await _currentWallet.updateNode(shouldRefresh); } - // Function(bool isActive)? onIsActiveWalletChanged; CoinServiceAPI get wallet => _currentWallet; @@ -88,13 +93,13 @@ class Manager with ChangeNotifier { Future> prepareSend({ required String address, - required int satoshiAmount, + required Amount amount, Map? args, }) async { try { final txInfo = await _currentWallet.prepareSend( address: address, - satoshiAmount: satoshiAmount, + amount: amount, args: args, ); // notifyListeners(); @@ -109,8 +114,15 @@ class Manager with ChangeNotifier { try { final txid = await _currentWallet.confirmSend(txData: txData); - txData["txid"] = txid; - await _currentWallet.updateSentCachedTxData(txData); + try { + txData["txid"] = txid; + await _currentWallet.updateSentCachedTxData(txData); + } catch (e, s) { + // do not rethrow as that would get handled as a send failure further up + // also this is not critical code and transaction should show up on \ + // refresh regardless + Logging.instance.log("$e\n$s", level: LogLevel.Warning); + } notifyListeners(); return txid; @@ -120,73 +132,17 @@ class Manager with ChangeNotifier { } } - /// create and submit tx to network - /// - /// Returns the txid of the sent tx - /// will throw exceptions on failure - Future send({ - required String toAddress, - required int amount, - Map args = const {}, - }) async { - try { - final txid = await _currentWallet.send( - toAddress: toAddress, - amount: amount, - args: args, - ); - notifyListeners(); - return txid; - } catch (e, s) { - Logging.instance.log("$e\n $s", level: LogLevel.Error); - // rethrow to pass error in alert - rethrow; - } - } - Future get fees => _currentWallet.fees; Future get maxFee => _currentWallet.maxFee; Future get currentReceivingAddress => _currentWallet.currentReceivingAddress; - // Future get currentLegacyReceivingAddress => - // _currentWallet.currentLegacyReceivingAddress; - Future get availableBalance async { - _cachedAvailableBalance = await _currentWallet.availableBalance; - return _cachedAvailableBalance; - } + Balance get balance => _currentWallet.balance; - Decimal _cachedAvailableBalance = Decimal.zero; - Decimal get cachedAvailableBalance => _cachedAvailableBalance; - - Future get pendingBalance => _currentWallet.pendingBalance; - Future get balanceMinusMaxFee => _currentWallet.balanceMinusMaxFee; - - Future get totalBalance async { - _cachedTotalBalance = await _currentWallet.totalBalance; - return _cachedTotalBalance; - } - - Decimal _cachedTotalBalance = Decimal.zero; - Decimal get cachedTotalBalance => _cachedTotalBalance; - - // Future get fiatBalance async { - // final balance = await _currentWallet.availableBalance; - // final price = await _currentWallet.basePrice; - // return balance * price; - // } - // - // Future get fiatTotalBalance async { - // final balance = await _currentWallet.totalBalance; - // final price = await _currentWallet.basePrice; - // return balance * price; - // } - - Future> get allOwnAddresses => _currentWallet.allOwnAddresses; - - Future get transactionData => _currentWallet.transactionData; - Future> get unspentOutputs => _currentWallet.unspentOutputs; + Future> get transactions => + _currentWallet.transactions; + Future> get utxos => _currentWallet.utxos; Future refresh() async { await _currentWallet.refresh(); @@ -208,6 +164,7 @@ class Manager with ChangeNotifier { _currentWallet.validateAddress(address); Future> get mnemonic => _currentWallet.mnemonic; + Future get mnemonicPassphrase => _currentWallet.mnemonicPassphrase; Future testNetworkConnection() => _currentWallet.testNetworkConnection(); @@ -216,6 +173,7 @@ class Manager with ChangeNotifier { Future initializeExisting() => _currentWallet.initializeExisting(); Future recoverFromMnemonic({ required String mnemonic, + String? mnemonicPassphrase, required int maxUnusedAddressGap, required int maxNumberOfIndexesToCheck, required int height, @@ -223,6 +181,7 @@ class Manager with ChangeNotifier { try { await _currentWallet.recoverFromMnemonic( mnemonic: mnemonic, + mnemonicPassphrase: mnemonicPassphrase, maxUnusedAddressGap: maxUnusedAddressGap, maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, height: height, @@ -233,11 +192,6 @@ class Manager with ChangeNotifier { } } - // Future initializeWallet() async { - // final success = await _currentWallet.initializeWallet(); - // return success; - // } - Future exitCurrentWallet() async { final name = _currentWallet.walletName; final id = _currentWallet.walletId; @@ -260,15 +214,10 @@ class Manager with ChangeNotifier { } } - Future isOwnAddress(String address) async { - final allOwnAddresses = await this.allOwnAddresses; - return allOwnAddresses.contains(address); - } - bool get isConnected => _currentWallet.isConnected; - Future estimateFeeFor(int satoshiAmount, int feeRate) async { - return _currentWallet.estimateFeeFor(satoshiAmount, feeRate); + Future estimateFeeFor(Amount amount, int feeRate) async { + return _currentWallet.estimateFeeFor(amount, feeRate); } Future generateNewAddress() async { @@ -278,4 +227,39 @@ class Manager with ChangeNotifier { } return success; } + + int get currentHeight => _currentWallet.storedChainHeight; + + bool get hasPaynymSupport => _currentWallet is PaynymWalletInterface; + + bool get hasCoinControlSupport => _currentWallet is CoinControlInterface; + + bool get hasTokenSupport => _currentWallet.coin == Coin.ethereum; + + bool get hasWhirlpoolSupport => false; + + int get rescanOnOpenVersion => + DB.instance.get( + boxName: DB.boxNameDBInfo, + key: "rescan_on_open_$walletId", + ) as int? ?? + 0; + + Future resetRescanOnOpen() async { + await DB.instance.delete( + key: "rescan_on_open_$walletId", + boxName: DB.boxNameDBInfo, + ); + } + + // TODO: re enable once xpubs have been redone + bool get hasXPub => false; //_currentWallet is XPubAble; + + Future get xpub async { + if (!hasXPub) { + throw Exception( + "Tried to read xpub from wallet that does not support it"); + } + return (_currentWallet as XPubAble).xpub; + } } diff --git a/lib/services/coins/monero/monero_wallet.dart b/lib/services/coins/monero/monero_wallet.dart index 91a6c5ad3..29debe753 100644 --- a/lib/services/coins/monero/monero_wallet.dart +++ b/lib/services/coins/monero/monero_wallet.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'dart:io'; +import 'dart:math'; -import 'package:cw_core/monero_amount_format.dart'; import 'package:cw_core/monero_transaction_priority.dart'; import 'package:cw_core/node.dart'; import 'package:cw_core/pending_transaction.dart'; @@ -13,84 +13,97 @@ import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cw_monero/api/exceptions/creation_transaction_exception.dart'; -import 'package:cw_monero/api/wallet.dart'; import 'package:cw_monero/monero_wallet.dart'; import 'package:cw_monero/pending_monero_transaction.dart'; -import 'package:dart_numerics/dart_numerics.dart'; import 'package:decimal/decimal.dart'; -import 'package:flutter/foundation.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter_libmonero/core/key_service.dart'; import 'package:flutter_libmonero/core/wallet_creation_service.dart'; import 'package:flutter_libmonero/monero/monero.dart'; import 'package:flutter_libmonero/view_model/send/output.dart' as monero_output; -import 'package:http/http.dart'; +import 'package:isar/isar.dart'; import 'package:mutex/mutex.dart'; -import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/db/hive/db.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/models/paymint/fee_object_model.dart'; -import 'package:stackwallet/models/paymint/transactions_model.dart'; -import 'package:stackwallet/models/paymint/utxo_model.dart'; import 'package:stackwallet/services/coins/coin_service.dart'; import 'package:stackwallet/services/event_bus/events/global/blocks_remaining_event.dart'; -import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +import 'package:stackwallet/services/mixins/wallet_cache.dart'; +import 'package:stackwallet/services/mixins/wallet_db.dart'; import 'package:stackwallet/services/node_service.dart'; -import 'package:stackwallet/services/price.dart'; -import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/default_nodes.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; -import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/stack_file_system.dart'; +import 'package:tuple/tuple.dart'; const int MINIMUM_CONFIRMATIONS = 10; -//https://github.com/monero-project/monero/blob/8361d60aef6e17908658128284899e3a11d808d4/src/cryptonote_config.h#L162 -const String GENESIS_HASH_MAINNET = - "013c01ff0001ffffffffffff03029b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd08807121017767aafcde9be00dcfd098715ebcf7f410daebc582fda69d24a28e9d0bc890d1"; -const String GENESIS_HASH_TESTNET = - "013c01ff0001ffffffffffff03029b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd08807121017767aafcde9be00dcfd098715ebcf7f410daebc582fda69d24a28e9d0bc890d1"; - -class MoneroWallet extends CoinServiceAPI { - static const integrationTestFlag = - bool.fromEnvironment("IS_INTEGRATION_TEST"); - final _prefs = Prefs.instance; - - Timer? timer; - Timer? moneroAutosaveTimer; - late Coin _coin; - - late SecureStorageInterface _secureStore; - - late PriceAPI _priceAPI; - - Future getCurrentNode() async { - return NodeService(secureStorageInterface: _secureStore) - .getPrimaryNodeFor(coin: coin) ?? - DefaultNodes.getNodeFor(coin); - } - - MoneroWallet( - {required String walletId, - required String walletName, - required Coin coin, - PriceAPI? priceAPI, - required SecureStorageInterface secureStore}) { +class MoneroWallet extends CoinServiceAPI with WalletCache, WalletDB { + MoneroWallet({ + required String walletId, + required String walletName, + required Coin coin, + required SecureStorageInterface secureStorage, + Prefs? prefs, + MainDB? mockableOverride, + }) { _walletId = walletId; _walletName = walletName; _coin = coin; - - _priceAPI = priceAPI ?? PriceAPI(Client()); - _secureStore = secureStore; + _secureStorage = secureStorage; + _prefs = prefs ?? Prefs.instance; + initCache(walletId, coin); + initWalletDB(mockableOverride: mockableOverride); } + late final String _walletId; + late final Coin _coin; + late final SecureStorageInterface _secureStorage; + late final Prefs _prefs; + + late String _walletName; + bool _shouldAutoSync = false; + bool _isConnected = false; + bool _hasCalledExit = false; + bool refreshMutex = false; + bool longMutex = false; + + WalletService? walletService; + KeyService? keysStorage; + MoneroWalletBase? walletBase; + WalletCreationService? _walletCreationService; + Timer? _autoSaveTimer; + + Future get _currentReceivingAddress => + db.getAddresses(walletId).sortByDerivationIndexDesc().findFirst(); + Future? _feeObject; + + Mutex prepareSendMutex = Mutex(); + Mutex estimateFeeMutex = Mutex(); + + @override + set isFavorite(bool markFavorite) { + _isFavorite = markFavorite; + updateCachedIsFavorite(markFavorite); + } + + @override + bool get isFavorite => _isFavorite ??= getCachedIsFavorite(); + + bool? _isFavorite; @override bool get shouldAutoSync => _shouldAutoSync; @@ -99,1301 +112,30 @@ class MoneroWallet extends CoinServiceAPI { set shouldAutoSync(bool shouldAutoSync) { if (_shouldAutoSync != shouldAutoSync) { _shouldAutoSync = shouldAutoSync; - if (!shouldAutoSync) { - timer?.cancel(); - moneroAutosaveTimer?.cancel(); - timer = null; - moneroAutosaveTimer = null; - stopNetworkAlivePinging(); - } else { - startNetworkAlivePinging(); - // Walletbase needs to be open for this to work - refresh(); - } - } - } - - @override - Future updateNode(bool shouldRefresh) async { - final node = await getCurrentNode(); - - final host = Uri.parse(node.host).host; - await walletBase?.connectToNode( - node: Node(uri: "$host:${node.port}", type: WalletType.monero)); - - // TODO: is this sync call needed? Do we need to notify ui here? - await walletBase?.startSync(); - - if (shouldRefresh) { - await refresh(); - } - } - - Future> _getMnemonicList() async { - final mnemonicString = - await _secureStore.read(key: '${_walletId}_mnemonic'); - if (mnemonicString == null) { - return []; - } - final List data = mnemonicString.split(' '); - return data; - } - - @override - Future> get mnemonic => _getMnemonicList(); - - Future get currentNodeHeight async { - try { - if (walletBase!.syncStatus! is SyncedSyncStatus && - walletBase!.syncStatus!.progress() == 1.0) { - return await walletBase!.getNodeHeight(); - } - } catch (e, s) {} - int _height = -1; - try { - _height = (walletBase!.syncStatus as SyncingSyncStatus).height; - } catch (e, s) { - // Logging.instance.log("$e $s", level: LogLevel.Warning); - } - - int blocksRemaining = -1; - - try { - blocksRemaining = - (walletBase!.syncStatus as SyncingSyncStatus).blocksLeft; - } catch (e, s) { - // Logging.instance.log("$e $s", level: LogLevel.Warning); - } - int currentHeight = _height + blocksRemaining; - if (_height == -1 || blocksRemaining == -1) { - currentHeight = int64MaxValue; - } - final cachedHeight = DB.instance - .get(boxName: walletId, key: "storedNodeHeight") as int? ?? - 0; - - if (currentHeight > cachedHeight && currentHeight != int64MaxValue) { - await DB.instance.put( - boxName: walletId, key: "storedNodeHeight", value: currentHeight); - return currentHeight; - } else { - return cachedHeight; - } - } - - Future get currentSyncingHeight async { - //TODO return the tip of the monero blockchain - try { - if (walletBase!.syncStatus! is SyncedSyncStatus && - walletBase!.syncStatus!.progress() == 1.0) { - // Logging.instance - // .log("currentSyncingHeight lol", level: LogLevel.Warning); - return getSyncingHeight(); - } - } catch (e, s) {} - int syncingHeight = -1; - try { - syncingHeight = (walletBase!.syncStatus as SyncingSyncStatus).height; - } catch (e, s) { - // Logging.instance.log("$e $s", level: LogLevel.Warning); - } - final cachedHeight = - DB.instance.get(boxName: walletId, key: "storedSyncingHeight") - as int? ?? - 0; - - if (syncingHeight > cachedHeight) { - await DB.instance.put( - boxName: walletId, key: "storedSyncingHeight", value: syncingHeight); - return syncingHeight; - } else { - return cachedHeight; - } - } - - Future updateStoredChainHeight({required int newHeight}) async { - await DB.instance.put( - boxName: walletId, key: "storedChainHeight", value: newHeight); - } - - int get storedChainHeight { - return DB.instance.get(boxName: walletId, key: "storedChainHeight") - as int? ?? - 0; - } - - /// Increases the index for either the internal or external chain, depending on [chain]. - /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! - Future _incrementAddressIndexForChain(int chain) async { - // Here we assume chain == 1 if it isn't 0 - String indexKey = chain == 0 ? "receivingIndex" : "changeIndex"; - - final newIndex = - (DB.instance.get(boxName: walletId, key: indexKey)) + 1; - await DB.instance - .put(boxName: walletId, key: indexKey, value: newIndex); - } - - Future _checkCurrentReceivingAddressesForTransactions() async { - try { - await _checkReceivingAddressForTransactions(); - } catch (e, s) { - Logging.instance.log( - "Exception rethrown from _checkCurrentReceivingAddressesForTransactions(): $e\n$s", - level: LogLevel.Error); - rethrow; - } - } - - Future _checkReceivingAddressForTransactions() async { - try { - int highestIndex = -1; - for (var element - in walletBase!.transactionHistory!.transactions!.entries) { - if (element.value.direction == TransactionDirection.incoming) { - int curAddressIndex = - element.value.additionalInfo!['addressIndex'] as int; - if (curAddressIndex > highestIndex) { - highestIndex = curAddressIndex; - } - } - } - - // Check the new receiving index - String indexKey = "receivingIndex"; - final curIndex = - DB.instance.get(boxName: walletId, key: indexKey) as int; - if (highestIndex >= curIndex) { - // First increment the receiving index - await _incrementAddressIndexForChain(0); - final newReceivingIndex = - DB.instance.get(boxName: walletId, key: indexKey) as int; - - // Use new index to derive a new receiving address - final newReceivingAddress = - await _generateAddressForChain(0, newReceivingIndex); - - // Add that new receiving address to the array of receiving addresses - await _addToAddressesArrayForChain(newReceivingAddress, 0); - - // Set the new receiving address that the service - - _currentReceivingAddress = Future(() => newReceivingAddress); - } - } on SocketException catch (se, s) { - Logging.instance.log( - "SocketException caught in _checkReceivingAddressForTransactions(): $se\n$s", - level: LogLevel.Error); - return; - } catch (e, s) { - Logging.instance.log( - "Exception rethrown from _checkReceivingAddressForTransactions(): $e\n$s", - level: LogLevel.Error); - rethrow; - } - } - - @override - bool get isRefreshing => refreshMutex; - - bool refreshMutex = false; - - Timer? syncPercentTimer; - - Mutex syncHeightMutex = Mutex(); - Future stopSyncPercentTimer() async { - syncPercentTimer?.cancel(); - syncPercentTimer = null; - } - - Future startSyncPercentTimer() async { - if (syncPercentTimer != null) { - return; - } - syncPercentTimer?.cancel(); - GlobalEventBus.instance - .fire(RefreshPercentChangedEvent(highestPercentCached, walletId)); - syncPercentTimer = Timer.periodic(const Duration(seconds: 30), (_) async { - if (syncHeightMutex.isLocked) { - return; - } - await syncHeightMutex.protect(() async { - // int restoreheight = walletBase!.walletInfo.restoreHeight ?? 0; - int _height = await currentSyncingHeight; - int _currentHeight = await currentNodeHeight; - double progress = 0; - try { - progress = walletBase!.syncStatus!.progress(); - } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Warning); - } - - final int blocksRemaining = _currentHeight - _height; - - GlobalEventBus.instance - .fire(BlocksRemainingEvent(blocksRemaining, walletId)); - - if (progress == 1 && _currentHeight > 0 && _height > 0) { - await stopSyncPercentTimer(); - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.synced, - walletId, - coin, - ), - ); - return; - } - - // for some reason this can be 0 which screws up the percent calculation - // int64MaxValue is NOT the best value to use here - if (_currentHeight < 1) { - _currentHeight = int64MaxValue; - } - - if (_height < 1) { - _height = 1; - } - - double restorePercent = progress; - double highestPercent = highestPercentCached; - - Logging.instance.log( - "currentSyncingHeight: $_height, nodeHeight: $_currentHeight, restorePercent: $restorePercent, highestPercentCached: $highestPercentCached", - level: LogLevel.Info); - - if (restorePercent > 0 && restorePercent <= 1) { - // if (restorePercent > highestPercent) { - highestPercent = restorePercent; - highestPercentCached = restorePercent; - // } - } - - GlobalEventBus.instance - .fire(RefreshPercentChangedEvent(highestPercent, walletId)); - }); - }); - } - - double get highestPercentCached => - DB.instance.get(boxName: walletId, key: "highestPercentCached") - as double? ?? - 0; - set highestPercentCached(double value) => DB.instance.put( - boxName: walletId, - key: "highestPercentCached", - value: value, - ); - - /// Refreshes display data for the wallet - @override - Future refresh() async { - if (refreshMutex) { - Logging.instance.log("$walletId $walletName refreshMutex denied", - level: LogLevel.Info); - return; - } else { - refreshMutex = true; - } - - if (walletBase == null) { - throw Exception("Tried to call refresh() in monero without walletBase!"); - } - - try { - await startSyncPercentTimer(); - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.syncing, - walletId, - coin, - ), - ); - - final int _currentSyncingHeight = await currentSyncingHeight; - final int storedHeight = storedChainHeight; - int _currentNodeHeight = await currentNodeHeight; - - double progress = 0; - try { - progress = (walletBase!.syncStatus!).progress(); - } catch (e, s) { - // Logging.instance.log("$e $s", level: LogLevel.Warning); - } - await _fetchTransactionData(); - - bool stillSyncing = false; - Logging.instance.log( - "storedHeight: $storedHeight, _currentSyncingHeight: $_currentSyncingHeight, _currentNodeHeight: $_currentNodeHeight, progress: $progress, issynced: ${await walletBase!.isConnected()}", - level: LogLevel.Info); - - if (progress < 1.0) { - stillSyncing = true; - } - - if (_currentSyncingHeight > storedHeight) { - // 0 is returned from monero as I assume an error????? - if (_currentSyncingHeight > 0) { - // 0 failed to fetch current height??? - await updateStoredChainHeight(newHeight: _currentSyncingHeight); - } - } - - await _checkCurrentReceivingAddressesForTransactions(); - String indexKey = "receivingIndex"; - final curIndex = - DB.instance.get(boxName: walletId, key: indexKey) as int; - // Use new index to derive a new receiving address - try { - final newReceivingAddress = await _generateAddressForChain(0, curIndex); - _currentReceivingAddress = Future(() => newReceivingAddress); - } catch (e, s) { - Logging.instance.log( - "Failed to call _generateAddressForChain(0, $curIndex): $e\n$s", - level: LogLevel.Error); - } - final newTxData = await _fetchTransactionData(); - _transactionData = Future(() => newTxData); - - if (isActive || shouldAutoSync) { - timer ??= Timer.periodic(const Duration(seconds: 60), (timer) async { - //todo: check if print needed - // debugPrint("run timer"); - //TODO: check for new data and refresh if needed. if monero even needs this - // chain height check currently broken - // if ((await chainHeight) != (await storedChainHeight)) { - // if (await refreshIfThereIsNewData()) { - await refresh(); - GlobalEventBus.instance.fire(UpdatedInBackgroundEvent( - "New data found in $walletId $walletName in background!", - walletId)); - // } - // } - }); - moneroAutosaveTimer ??= - Timer.periodic(const Duration(seconds: 93), (timer) async { - //todo: check if print needed - // debugPrint("run monero timer"); - if (isActive) { - await walletBase?.save(); - GlobalEventBus.instance.fire(UpdatedInBackgroundEvent( - "New data found in $walletId $walletName in background!", - walletId)); - } - }); - } - - if (stillSyncing) { - debugPrint("still syncing"); - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.syncing, - walletId, - coin, - ), - ); - refreshMutex = false; - return; - } - await stopSyncPercentTimer(); - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.synced, - walletId, - coin, - ), - ); - refreshMutex = false; - } catch (error, strace) { - refreshMutex = false; - await stopSyncPercentTimer(); - GlobalEventBus.instance.fire( - NodeConnectionStatusChangedEvent( - NodeConnectionStatus.disconnected, - walletId, - coin, - ), - ); - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.unableToSync, - walletId, - coin, - ), - ); - Logging.instance.log( - "Caught exception in refreshWalletData(): $error\n$strace", - level: LogLevel.Error); - } - } - - @override - // TODO: implement allOwnAddresses - Future> get allOwnAddresses { - return Future(() => []); - } - - @override - Future get balanceMinusMaxFee async => - (await availableBalance) - - (Decimal.fromInt((await maxFee)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(); - - @override - Future get currentReceivingAddress => - _currentReceivingAddress ??= _getCurrentAddressForChain(0); - - @override - Future exit() async { - _hasCalledExit = true; - stopNetworkAlivePinging(); - moneroAutosaveTimer?.cancel(); - moneroAutosaveTimer = null; - timer?.cancel(); - timer = null; - await stopSyncPercentTimer(); - await walletBase?.save(prioritySave: true); - walletBase?.close(); - isActive = false; - } - - bool _hasCalledExit = false; - - @override - bool get hasCalledExit => _hasCalledExit; - - Future? _currentReceivingAddress; - - Future _getFees() async { - // TODO: not use random hard coded values here - return FeeObject( - numberOfBlocksFast: 10, - numberOfBlocksAverage: 15, - numberOfBlocksSlow: 20, - fast: MoneroTransactionPriority.fast.raw!, - medium: MoneroTransactionPriority.regular.raw!, - slow: MoneroTransactionPriority.slow.raw!, - ); - } - - @override - Future get fees => _feeObject ??= _getFees(); - Future? _feeObject; - - @override - // TODO: implement fullRescan - Future fullRescan( - int maxUnusedAddressGap, - int maxNumberOfIndexesToCheck, - ) async { - var restoreHeight = walletBase?.walletInfo.restoreHeight; - await walletBase?.rescan(height: restoreHeight); - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.syncing, - walletId, - coin, - ), - ); - return; - } - - Future _generateAddressForChain(int chain, int index) async { - // - String address = walletBase!.getTransactionAddress(chain, index); - - return address; - } - - /// Adds [address] to the relevant chain's address array, which is determined by [chain]. - /// [address] - Expects a standard native segwit address - /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! - Future _addToAddressesArrayForChain(String address, int chain) async { - String chainArray = ''; - if (chain == 0) { - chainArray = 'receivingAddresses'; - } else { - chainArray = 'changeAddresses'; - } - - final addressArray = - DB.instance.get(boxName: walletId, key: chainArray); - if (addressArray == null) { - Logging.instance.log( - 'Attempting to add the following to $chainArray array for chain $chain:${[ - address - ]}', - level: LogLevel.Info); - await DB.instance - .put(boxName: walletId, key: chainArray, value: [address]); - } else { - // Make a deep copy of the existing list - final List newArray = []; - addressArray - .forEach((dynamic _address) => newArray.add(_address as String)); - newArray.add(address); // Add the address passed into the method - await DB.instance - .put(boxName: walletId, key: chainArray, value: newArray); - } - } - - /// Returns the latest receiving/change (external/internal) address for the wallet depending on [chain] - /// and - /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! - Future _getCurrentAddressForChain(int chain) async { - // Here, we assume that chain == 1 if it isn't 0 - String arrayKey = chain == 0 ? "receivingAddresses" : "changeAddresses"; - final internalChainArray = (DB.instance - .get(boxName: walletId, key: arrayKey)) as List; - return internalChainArray.last as String; - } - - //TODO: take in the default language when creating wallet. - Future _generateNewWallet() async { - Logging.instance - .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); - // TODO: ping monero server and make sure the genesis hash matches - // if (!integrationTestFlag) { - // final features = await electrumXClient.getServerFeatures(); - // Logging.instance.log("features: $features"); - // if (_networkType == BasicNetworkType.main) { - // if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { - // throw Exception("genesis hash does not match main net!"); - // } - // } else if (_networkType == BasicNetworkType.test) { - // if (features['genesis_hash'] != GENESIS_HASH_TESTNET) { - // throw Exception("genesis hash does not match test net!"); - // } - // } - // } - - // this should never fail - if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) { - throw Exception( - "Attempted to overwrite mnemonic on generate new wallet!"); - } - - walletService = - monero.createMoneroWalletService(DB.instance.moneroWalletInfoBox); - keysStorage = KeyService(_secureStore); - WalletInfo walletInfo; - WalletCredentials credentials; - try { - String name = _walletId; - final dirPath = - await pathForWalletDir(name: name, type: WalletType.monero); - final path = await pathForWallet(name: name, type: WalletType.monero); - credentials = monero.createMoneroNewWalletCredentials( - name: name, - language: "English", - ); - - // subtract a couple days to ensure we have a buffer for SWB - final bufferedCreateHeight = monero.getHeigthByDate( - date: DateTime.now().subtract(const Duration(days: 2))); - - await DB.instance.put( - boxName: walletId, key: "restoreHeight", value: bufferedCreateHeight); - - walletInfo = WalletInfo.external( - id: WalletBase.idFor(name, WalletType.monero), - name: name, - type: WalletType.monero, - isRecovery: false, - restoreHeight: bufferedCreateHeight, - date: DateTime.now(), - path: path, - dirPath: dirPath, - // TODO: find out what to put for address - address: ''); - credentials.walletInfo = walletInfo; - - _walletCreationService = WalletCreationService( - secureStorage: _secureStore, - walletService: walletService, - keyService: keysStorage, - ); - _walletCreationService?.changeWalletType(); - // To restore from a seed - final wallet = await _walletCreationService?.create(credentials); - - await _secureStore.write( - key: '${_walletId}_mnemonic', value: wallet?.seed.trim()); - walletInfo.address = wallet?.walletAddresses.address; - await DB.instance - .add(boxName: WalletInfo.boxName, value: walletInfo); - walletBase?.close(); - walletBase = wallet as MoneroWalletBase; - } catch (e, s) { - //todo: come back to this - debugPrint(e.toString()); - debugPrint(s.toString()); - } - final node = await getCurrentNode(); - final host = Uri.parse(node.host).host; - await walletBase?.connectToNode( - node: Node(uri: "$host:${node.port}", type: WalletType.monero)); - await walletBase?.startSync(); - await DB.instance - .put(boxName: walletId, key: "id", value: _walletId); - - // Set relevant indexes - await DB.instance - .put(boxName: walletId, key: "receivingIndex", value: 0); - await DB.instance - .put(boxName: walletId, key: "changeIndex", value: 0); - await DB.instance.put( - boxName: walletId, - key: 'blocked_tx_hashes', - value: ["0xdefault"], - ); // A list of transaction hashes to represent frozen utxos in wallet - // initialize address book entries - await DB.instance.put( - boxName: walletId, - key: 'addressBookEntries', - value: {}); - await DB.instance - .put(boxName: walletId, key: "isFavorite", value: false); - - // Generate and add addresses to relevant arrays - final initialReceivingAddress = await _generateAddressForChain(0, 0); - // final initialChangeAddress = await _generateAddressForChain(1, 0); - - await _addToAddressesArrayForChain(initialReceivingAddress, 0); - // await _addToAddressesArrayForChain(initialChangeAddress, 1); - - await DB.instance.put( - boxName: walletId, - key: 'receivingAddresses', - value: [initialReceivingAddress]); - await DB.instance - .put(boxName: walletId, key: "receivingIndex", value: 0); - - _currentReceivingAddress = Future(() => initialReceivingAddress); - - Logging.instance.log("_generateNewWalletFinished", level: LogLevel.Info); - } - - @override - // TODO: implement initializeWallet - Future initializeNew() async { - await _prefs.init(); - // TODO: ping actual monero network - // try { - // final hasNetwork = await _electrumXClient.ping(); - // if (!hasNetwork) { - // return false; - // } - // } catch (e, s) { - // Logging.instance.log("Caught in initializeWallet(): $e\n$s"); - // return false; - // } - - walletService = - monero.createMoneroWalletService(DB.instance.moneroWalletInfoBox); - keysStorage = KeyService(_secureStore); - - await _generateNewWallet(); - // var password; - // try { - // password = - // await keysStorage?.getWalletPassword(walletName: this._walletId); - // } catch (e, s) { - // Logging.instance.log("$e $s"); - // Logging.instance.log("Generating new ${coin.ticker} wallet."); - // // Triggers for new users automatically. Generates new wallet - // await _generateNewWallet(wallet); - // await wallet.put("id", this._walletId); - // return true; - // } - // walletBase = (await walletService?.openWallet(this._walletId, password)) - // as MoneroWalletBase; - // Logging.instance.log("Opening existing ${coin.ticker} wallet."); - // // Wallet already exists, triggers for a returning user - // final currentAddress = awaicurrentHeightt _getCurrentAddressForChain(0); - // this._currentReceivingAddress = Future(() => currentAddress); - // - // await walletBase?.connectToNode( - // node: Node( - // uri: "xmr-node.cakewallet.com:18081", type: WalletType.monero)); - // walletBase?.startSync(); - - return true; - } - - @override - Future initializeExisting() async { - Logging.instance.log( - "Opening existing ${coin.prettyName} wallet $walletName...", - level: LogLevel.Info); - - if ((DB.instance.get(boxName: walletId, key: "id")) == null) { - //todo: check if print is needed - // debugPrint("Exception was thrown"); - throw Exception( - "Attempted to initialize an existing wallet using an unknown wallet ID!"); - } - - walletService = - monero.createMoneroWalletService(DB.instance.moneroWalletInfoBox); - keysStorage = KeyService(_secureStore); - - await _prefs.init(); - final data = - DB.instance.get(boxName: walletId, key: "latest_tx_model") - as TransactionData?; - if (data != null) { - _transactionData = Future(() => data); - } - - String? password; - try { - password = await keysStorage?.getWalletPassword(walletName: _walletId); - } catch (e, s) { - //todo: check if print needed - // debugPrint("Exception was thrown $e $s"); - throw Exception("Password not found $e, $s"); - } - walletBase = (await walletService?.openWallet(_walletId, password!)) - as MoneroWalletBase; - debugPrint("walletBase $walletBase"); - Logging.instance.log( - "Opened existing ${coin.prettyName} wallet $walletName", - level: LogLevel.Info); - // Wallet already exists, triggers for a returning user - - String indexKey = "receivingIndex"; - final curIndex = - await DB.instance.get(boxName: walletId, key: indexKey) as int; - // Use new index to derive a new receiving address - final newReceivingAddress = await _generateAddressForChain(0, curIndex); - Logging.instance.log("xmr address in init existing: $newReceivingAddress", - level: LogLevel.Info); - _currentReceivingAddress = Future(() => newReceivingAddress); - } - - @override - Future get maxFee async { - var bal = await availableBalance; - var fee = walletBase!.calculateEstimatedFee( - monero.getDefaultTransactionPriority(), - Format.decimalAmountToSatoshis(bal, coin), - ); - - return fee; - } - - @override - // TODO: implement pendingBalance - Future get pendingBalance => throw UnimplementedError(); - - bool longMutex = false; - - // TODO: are these needed? - - WalletService? walletService; - KeyService? keysStorage; - MoneroWalletBase? walletBase; - WalletCreationService? _walletCreationService; - - String toStringForinfo(WalletInfo info) { - return "id: ${info.id} name: ${info.name} type: ${info.type} recovery: ${info.isRecovery}" - " restoreheight: ${info.restoreHeight} timestamp: ${info.timestamp} dirPath: ${info.dirPath} " - "path: ${info.path} address: ${info.address} addresses: ${info.addresses}"; - } - - Future pathForWalletDir({ - required String name, - required WalletType type, - }) async { - Directory root = await StackFileSystem.applicationRootDirectory(); - - final prefix = walletTypeToString(type).toLowerCase(); - final walletsDir = Directory('${root.path}/wallets'); - final walletDire = Directory('${walletsDir.path}/$prefix/$name'); - - if (!walletDire.existsSync()) { - walletDire.createSync(recursive: true); - } - - return walletDire.path; - } - - Future pathForWallet({ - required String name, - required WalletType type, - }) async => - await pathForWalletDir(name: name, type: type) - .then((path) => '$path/$name'); - - // TODO: take in a dynamic height - @override - Future recoverFromMnemonic({ - required String mnemonic, - required int maxUnusedAddressGap, - required int maxNumberOfIndexesToCheck, - required int height, - }) async { - await _prefs.init(); - longMutex = true; - final start = DateTime.now(); - try { - // Logging.instance.log("IS_INTEGRATION_TEST: $integrationTestFlag"); - // if (!integrationTestFlag) { - // final features = await electrumXClient.getServerFeatures(); - // Logging.instance.log("features: $features"); - // if (_networkType == BasicNetworkType.main) { - // if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { - // throw Exception("genesis hash does not match main net!"); - // } - // } else if (_networkType == BasicNetworkType.test) { - // if (features['genesis_hash'] != GENESIS_HASH_TESTNET) { - // throw Exception("genesis hash does not match test net!"); - // } - // } + // xmr wallets cannot be open at the same time + // leave following commented out for now + + // if (!shouldAutoSync) { + // timer?.cancel(); + // moneroAutosaveTimer?.cancel(); + // timer = null; + // moneroAutosaveTimer = null; + // stopNetworkAlivePinging(); + // } else { + // startNetworkAlivePinging(); + // // Walletbase needs to be open for this to work + // refresh(); // } - // check to make sure we aren't overwriting a mnemonic - // this should never fail - if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) { - longMutex = false; - throw Exception("Attempted to overwrite mnemonic on restore!"); - } - await _secureStore.write( - key: '${_walletId}_mnemonic', value: mnemonic.trim()); - - await DB.instance - .put(boxName: walletId, key: "restoreHeight", value: height); - - walletService = - monero.createMoneroWalletService(DB.instance.moneroWalletInfoBox); - keysStorage = KeyService(_secureStore); - WalletInfo walletInfo; - WalletCredentials credentials; - String name = _walletId; - final dirPath = - await pathForWalletDir(name: name, type: WalletType.monero); - final path = await pathForWallet(name: name, type: WalletType.monero); - credentials = monero.createMoneroRestoreWalletFromSeedCredentials( - name: name, - height: height, - mnemonic: mnemonic.trim(), - ); - try { - walletInfo = WalletInfo.external( - id: WalletBase.idFor(name, WalletType.monero), - name: name, - type: WalletType.monero, - isRecovery: false, - restoreHeight: credentials.height ?? 0, - date: DateTime.now(), - path: path, - dirPath: dirPath, - // TODO: find out what to put for address - address: ''); - credentials.walletInfo = walletInfo; - - _walletCreationService = WalletCreationService( - secureStorage: _secureStore, - walletService: walletService, - keyService: keysStorage, - ); - _walletCreationService!.changeWalletType(); - // To restore from a seed - final wallet = - await _walletCreationService!.restoreFromSeed(credentials); - walletInfo.address = wallet.walletAddresses.address; - await DB.instance - .add(boxName: WalletInfo.boxName, value: walletInfo); - walletBase?.close(); - walletBase = wallet as MoneroWalletBase; - await DB.instance.put( - boxName: walletId, - key: 'receivingAddresses', - value: [walletInfo.address!]); - await DB.instance - .put(boxName: walletId, key: "receivingIndex", value: 0); - await DB.instance - .put(boxName: walletId, key: "id", value: _walletId); - await DB.instance - .put(boxName: walletId, key: "changeIndex", value: 0); - await DB.instance.put( - boxName: walletId, - key: 'blocked_tx_hashes', - value: ["0xdefault"], - ); // A list of transaction hashes to represent frozen utxos in wallet - // initialize address book entries - await DB.instance.put( - boxName: walletId, - key: 'addressBookEntries', - value: {}); - await DB.instance - .put(boxName: walletId, key: "isFavorite", value: false); - } catch (e, s) { - debugPrint(e.toString()); - debugPrint(s.toString()); - } - final node = await getCurrentNode(); - final host = Uri.parse(node.host).host; - await walletBase?.connectToNode( - node: Node(uri: "$host:${node.port}", type: WalletType.monero)); - await walletBase?.rescan(height: credentials.height); - } catch (e, s) { - Logging.instance.log( - "Exception rethrown from recoverFromMnemonic(): $e\n$s", - level: LogLevel.Error); - longMutex = false; - rethrow; - } - longMutex = false; - - final end = DateTime.now(); - Logging.instance.log( - "$walletName Recovery time: ${end.difference(start).inMilliseconds} millis", - level: LogLevel.Info); - } - - @override - Future send({ - required String toAddress, - required int amount, - Map args = const {}, - }) async { - try { - final txData = await prepareSend( - address: toAddress, satoshiAmount: amount, args: args); - final txHash = await confirmSend(txData: txData); - return txHash; - } catch (e, s) { - Logging.instance - .log("Exception rethrown from send(): $e\n$s", level: LogLevel.Error); - rethrow; } } - @override - Future testNetworkConnection() async { - return await walletBase?.isConnected() ?? false; - } - - Timer? _networkAliveTimer; - - void startNetworkAlivePinging() { - // call once on start right away - _periodicPingCheck(); - - // then periodically check - _networkAliveTimer = Timer.periodic( - Constants.networkAliveTimerDuration, - (_) async { - _periodicPingCheck(); - }, - ); - } - - void _periodicPingCheck() async { - bool hasNetwork = await testNetworkConnection(); - _isConnected = hasNetwork; - if (_isConnected != hasNetwork) { - NodeConnectionStatus status = hasNetwork - ? NodeConnectionStatus.connected - : NodeConnectionStatus.disconnected; - GlobalEventBus.instance - .fire(NodeConnectionStatusChangedEvent(status, walletId, coin)); - } - } - - void stopNetworkAlivePinging() { - _networkAliveTimer?.cancel(); - _networkAliveTimer = null; - } - - bool _isConnected = false; - - @override - bool get isConnected => _isConnected; - - @override - Future get totalBalance async { - var transactions = walletBase?.transactionHistory!.transactions; - int transactionBalance = 0; - for (var tx in transactions!.entries) { - if (tx.value.direction == TransactionDirection.incoming) { - transactionBalance += tx.value.amount!; - } else { - transactionBalance += -tx.value.amount! - tx.value.fee!; - } - } - - // TODO: grab total balance - var bal = 0; - for (var element in walletBase!.balance!.entries) { - bal = bal + element.value.fullBalance; - } - //todo: check if print needed - // debugPrint("balances: $transactionBalance $bal"); - if (isActive) { - String am = moneroAmountToString(amount: bal); - - return Decimal.parse(am); - } else { - String am = moneroAmountToString(amount: transactionBalance); - - return Decimal.parse(am); - } - } - - @override - // TODO: implement onIsActiveWalletChanged - void Function(bool)? get onIsActiveWalletChanged => (isActive) async { - await walletBase?.save(); - walletBase?.close(); - moneroAutosaveTimer?.cancel(); - moneroAutosaveTimer = null; - timer?.cancel(); - timer = null; - await stopSyncPercentTimer(); - if (isActive) { - String? password; - try { - password = - await keysStorage?.getWalletPassword(walletName: _walletId); - } catch (e, s) { - //todo: check if print needed - // debugPrint("Exception was thrown $e $s"); - throw Exception("Password not found $e, $s"); - } - walletBase = (await walletService?.openWallet(_walletId, password!)) - as MoneroWalletBase?; - if (!(await walletBase!.isConnected())) { - final node = await getCurrentNode(); - final host = Uri.parse(node.host).host; - await walletBase?.connectToNode( - node: Node(uri: "$host:${node.port}", type: WalletType.monero)); - await walletBase?.startSync(); - } - await refresh(); - } - this.isActive = isActive; - }; - - bool isActive = false; - - @override - Future get transactionData => - _transactionData ??= _fetchTransactionData(); - Future? _transactionData; - - // not used in monero - TransactionData? cachedTxData; - - @override - Future updateSentCachedTxData(Map txData) async { - // not used in monero - } - - Future _fetchTransactionData() async { - final transactions = walletBase?.transactionHistory!.transactions; - - final cachedTransactions = - DB.instance.get(boxName: walletId, key: 'latest_tx_model') - as TransactionData?; - int latestTxnBlockHeight = - DB.instance.get(boxName: walletId, key: "storedTxnDataHeight") - as int? ?? - 0; - - final txidsList = DB.instance - .get(boxName: walletId, key: "cachedTxids") as List? ?? - []; - - final Set cachedTxids = Set.from(txidsList); - - // TODO: filter to skip cached + confirmed txn processing in next step - // final unconfirmedCachedTransactions = - // cachedTransactions?.getAllTransactions() ?? {}; - // unconfirmedCachedTransactions - // .removeWhere((key, value) => value.confirmedStatus); - // - // if (cachedTransactions != null) { - // for (final tx in allTxHashes.toList(growable: false)) { - // final txHeight = tx["height"] as int; - // if (txHeight > 0 && - // txHeight < latestTxnBlockHeight - MINIMUM_CONFIRMATIONS) { - // if (unconfirmedCachedTransactions[tx["tx_hash"] as String] == null) { - // allTxHashes.remove(tx); - // } - // } - // } - // } - - // sort thing stuff - // change to get Monero price - final priceData = - await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final List> midSortedArray = []; - - if (transactions != null) { - for (var tx in transactions.entries) { - cachedTxids.add(tx.value.id); - Logging.instance.log( - "${tx.value.accountIndex} ${tx.value.addressIndex} ${tx.value.amount} ${tx.value.date} " - "${tx.value.direction} ${tx.value.fee} ${tx.value.height} ${tx.value.id} ${tx.value.isPending} ${tx.value.key} " - "${tx.value.recipientAddress}, ${tx.value.additionalInfo} con:${tx.value.confirmations}" - " ${tx.value.keyIndex}", - level: LogLevel.Info); - String am = moneroAmountToString(amount: tx.value.amount!); - final worthNow = (currentPrice * Decimal.parse(am)).toStringAsFixed(2); - Map midSortedTx = {}; - // // create final tx map - midSortedTx["txid"] = tx.value.id; - midSortedTx["confirmed_status"] = !tx.value.isPending && - tx.value.confirmations! >= MINIMUM_CONFIRMATIONS; - midSortedTx["confirmations"] = tx.value.confirmations ?? 0; - midSortedTx["timestamp"] = - (tx.value.date.millisecondsSinceEpoch ~/ 1000); - midSortedTx["txType"] = - tx.value.direction == TransactionDirection.incoming - ? "Received" - : "Sent"; - midSortedTx["amount"] = tx.value.amount; - midSortedTx["worthNow"] = worthNow; - midSortedTx["worthAtBlockTimestamp"] = worthNow; - midSortedTx["fees"] = tx.value.fee; - // TODO: shouldn't monero have an address I can grab - if (tx.value.direction == TransactionDirection.incoming) { - final addressInfo = tx.value.additionalInfo; - - midSortedTx["address"] = walletBase?.getTransactionAddress( - addressInfo!['accountIndex'] as int, - addressInfo['addressIndex'] as int, - ); - } else { - midSortedTx["address"] = ""; - } - - final int txHeight = tx.value.height ?? 0; - midSortedTx["height"] = txHeight; - if (txHeight >= latestTxnBlockHeight) { - latestTxnBlockHeight = txHeight; - } - - midSortedTx["aliens"] = []; - midSortedTx["inputSize"] = 0; - midSortedTx["outputSize"] = 0; - midSortedTx["inputs"] = []; - midSortedTx["outputs"] = []; - midSortedArray.add(midSortedTx); - } - } - - // sort by date ---- - midSortedArray - .sort((a, b) => (b["timestamp"] as int) - (a["timestamp"] as int)); - Logging.instance.log(midSortedArray, level: LogLevel.Info); - - // buildDateTimeChunks - final Map result = {"dateTimeChunks": []}; - final dateArray = []; - - for (int i = 0; i < midSortedArray.length; i++) { - final txObject = midSortedArray[i]; - final date = extractDateFromTimestamp(txObject["timestamp"] as int); - final txTimeArray = [txObject["timestamp"], date]; - - if (dateArray.contains(txTimeArray[1])) { - result["dateTimeChunks"].forEach((dynamic chunk) { - if (extractDateFromTimestamp(chunk["timestamp"] as int) == - txTimeArray[1]) { - if (chunk["transactions"] == null) { - chunk["transactions"] = >[]; - } - chunk["transactions"].add(txObject); - } - }); - } else { - dateArray.add(txTimeArray[1]); - final chunk = { - "timestamp": txTimeArray[0], - "transactions": [txObject], - }; - result["dateTimeChunks"].add(chunk); - } - } - - final transactionsMap = cachedTransactions?.getAllTransactions() ?? {}; - transactionsMap - .addAll(TransactionData.fromJson(result).getAllTransactions()); - - final txModel = TransactionData.fromMap(transactionsMap); - - await DB.instance.put( - boxName: walletId, - key: 'storedTxnDataHeight', - value: latestTxnBlockHeight); - await DB.instance.put( - boxName: walletId, key: 'latest_tx_model', value: txModel); - await DB.instance.put( - boxName: walletId, - key: 'cachedTxids', - value: cachedTxids.toList(growable: false)); - - return txModel; - } - - @override - // TODO: implement unspentOutputs - Future> get unspentOutputs => throw UnimplementedError(); - - @override - bool validateAddress(String address) { - bool valid = walletBase!.validateAddress(address); - return valid; - } - - @override - String get walletId => _walletId; - late String _walletId; - @override String get walletName => _walletName; - late String _walletName; // setter for updating on rename @override set walletName(String newName) => _walletName = newName; - @override - set isFavorite(bool markFavorite) { - DB.instance.put( - boxName: walletId, key: "isFavorite", value: markFavorite); - } - - @override - bool get isFavorite { - try { - return DB.instance.get(boxName: walletId, key: "isFavorite") - as bool; - } catch (e, s) { - Logging.instance.log( - "isFavorite fetch failed (returning false by default): $e\n$s", - level: LogLevel.Error); - return false; - } - } - - @override - Future get availableBalance async { - var bal = 0; - for (var element in walletBase!.balance!.entries) { - bal = bal + element.value.unlockedBalance; - } - String am = moneroAmountToString(amount: bal); - - return Decimal.parse(am); - } - @override Coin get coin => _coin; @@ -1421,13 +163,276 @@ class MoneroWallet extends CoinServiceAPI { } } - // TODO: fix the double free memory crash error. @override - Future> prepareSend( - {required String address, - required int satoshiAmount, - Map? args}) async { - int amount = satoshiAmount; + Future get currentReceivingAddress async => + (await _currentReceivingAddress)?.value ?? + (await _generateAddressForChain(0, 0)).value; + + @override + Future estimateFeeFor(Amount amount, int feeRate) async { + MoneroTransactionPriority priority; + + switch (feeRate) { + case 1: + priority = MoneroTransactionPriority.regular; + break; + case 2: + priority = MoneroTransactionPriority.medium; + break; + case 3: + priority = MoneroTransactionPriority.fast; + break; + case 4: + priority = MoneroTransactionPriority.fastest; + break; + case 0: + default: + priority = MoneroTransactionPriority.slow; + break; + } + + final fee = walletBase!.calculateEstimatedFee(priority, amount.raw.toInt()); + + return Amount(rawValue: BigInt.from(fee), fractionDigits: coin.decimals); + } + + @override + Future exit() async { + if (!_hasCalledExit) { + walletBase?.onNewBlock = null; + walletBase?.onNewTransaction = null; + walletBase?.syncStatusChanged = null; + _hasCalledExit = true; + _autoSaveTimer?.cancel(); + await walletBase?.save(prioritySave: true); + walletBase?.close(); + } + } + + @override + Future get fees => _feeObject ??= _getFees(); + + @override + Future fullRescan( + int maxUnusedAddressGap, + int maxNumberOfIndexesToCheck, + ) async { + // clear blockchain info + await db.deleteWalletBlockchainData(walletId); + + var restoreHeight = walletBase?.walletInfo.restoreHeight; + highestPercentCached = 0; + await walletBase?.rescan(height: restoreHeight); + await refresh(); + } + + @override + Future generateNewAddress() async { + try { + final currentReceiving = await _currentReceivingAddress; + + final newReceivingIndex = currentReceiving!.derivationIndex + 1; + + // Use new index to derive a new receiving address + final newReceivingAddress = await _generateAddressForChain( + 0, + newReceivingIndex, + ); + + // Add that new receiving address + await db.putAddress(newReceivingAddress); + + return true; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from generateNewAddress(): $e\n$s", + level: LogLevel.Error); + return false; + } + } + + @override + bool get hasCalledExit => _hasCalledExit; + + @override + Future initializeExisting() async { + Logging.instance.log( + "initializeExisting() ${coin.prettyName} wallet $walletName...", + level: LogLevel.Info, + ); + + if (getCachedId() == null) { + throw Exception( + "Attempted to initialize an existing wallet using an unknown wallet ID!"); + } + + walletService = + monero.createMoneroWalletService(DB.instance.moneroWalletInfoBox); + keysStorage = KeyService(_secureStorage); + + await _prefs.init(); + + // final data = + // DB.instance.get(boxName: walletId, key: "latest_tx_model") + // as TransactionData?; + // if (data != null) { + // _transactionData = Future(() => data); + // } + + String password; + try { + password = await keysStorage!.getWalletPassword(walletName: _walletId); + } catch (_) { + throw Exception("Monero password not found for $walletName"); + } + walletBase = (await walletService!.openWallet(_walletId, password)) + as MoneroWalletBase; + + // await _checkCurrentReceivingAddressesForTransactions(); + + Logging.instance.log( + "Opened existing ${coin.prettyName} wallet $walletName", + level: LogLevel.Info, + ); + } + + @override + Future initializeNew() async { + await _prefs.init(); + + // this should never fail + if ((await mnemonicString) != null || (await mnemonicPassphrase) != null) { + throw Exception( + "Attempted to overwrite mnemonic on generate new wallet!"); + } + + walletService = + monero.createMoneroWalletService(DB.instance.moneroWalletInfoBox); + keysStorage = KeyService(_secureStorage); + WalletInfo walletInfo; + WalletCredentials credentials; + try { + String name = _walletId; + final dirPath = + await _pathForWalletDir(name: name, type: WalletType.monero); + final path = await _pathForWallet(name: name, type: WalletType.monero); + credentials = monero.createMoneroNewWalletCredentials( + name: name, + language: "English", + ); + + // subtract a couple days to ensure we have a buffer for SWB + final bufferedCreateHeight = monero.getHeigthByDate( + date: DateTime.now().subtract(const Duration(days: 2))); + + await DB.instance.put( + boxName: walletId, key: "restoreHeight", value: bufferedCreateHeight); + + walletInfo = WalletInfo.external( + id: WalletBase.idFor(name, WalletType.monero), + name: name, + type: WalletType.monero, + isRecovery: false, + restoreHeight: bufferedCreateHeight, + date: DateTime.now(), + path: path, + dirPath: dirPath, + // TODO: find out what to put for address + address: ''); + credentials.walletInfo = walletInfo; + + _walletCreationService = WalletCreationService( + secureStorage: _secureStorage, + walletService: walletService, + keyService: keysStorage, + ); + _walletCreationService?.changeWalletType(); + // To restore from a seed + final wallet = await _walletCreationService?.create(credentials); + + await _secureStorage.write( + key: '${_walletId}_mnemonic', value: wallet?.seed.trim()); + await _secureStorage.write( + key: '${_walletId}_mnemonicPassphrase', + value: "", + ); + walletInfo.address = wallet?.walletAddresses.address; + await DB.instance + .add(boxName: WalletInfo.boxName, value: walletInfo); + walletBase?.close(); + walletBase = wallet as MoneroWalletBase; + // walletBase!.onNewBlock = onNewBlock; + // walletBase!.onNewTransaction = onNewTransaction; + // walletBase!.syncStatusChanged = syncStatusChanged; + } catch (e, s) { + //todo: come back to this + debugPrint("some nice searchable string thing"); + debugPrint(e.toString()); + debugPrint(s.toString()); + walletBase?.close(); + } + final node = await _getCurrentNode(); + final host = Uri.parse(node.host).host; + await walletBase!.connectToNode( + node: Node( + uri: "$host:${node.port}", + type: WalletType.monero, + trusted: node.trusted ?? false, + ), + ); + await walletBase!.startSync(); + + await Future.wait([ + updateCachedId(walletId), + updateCachedIsFavorite(false), + ]); + + // Generate and add addresses to relevant arrays + final initialReceivingAddress = await _generateAddressForChain(0, 0); + // final initialChangeAddress = await _generateAddressForChain(1, 0); + + await db.putAddress(initialReceivingAddress); + + walletBase?.close(); + Logging.instance + .log("initializeNew for $walletName $walletId", level: LogLevel.Info); + } + + @override + bool get isConnected => _isConnected; + + @override + bool get isRefreshing => refreshMutex; + + @override + // not used in xmr + Future get maxFee => throw UnimplementedError(); + + @override + Future> get mnemonic async { + final _mnemonicString = await mnemonicString; + if (_mnemonicString == null) { + return []; + } + final List data = _mnemonicString.split(' '); + return data; + } + + @override + Future get mnemonicString => + _secureStorage.read(key: '${_walletId}_mnemonic'); + + @override + Future get mnemonicPassphrase => _secureStorage.read( + key: '${_walletId}_mnemonicPassphrase', + ); + + @override + Future> prepareSend({ + required String address, + required Amount amount, + Map? args, + }) async { String toAddress = address; try { final feeRate = args?["feeRate"]; @@ -1449,16 +454,13 @@ class MoneroWallet extends CoinServiceAPI { try { // check for send all bool isSendAll = false; - final balance = await availableBalance; - final satInDecimal = ((Decimal.fromInt(satoshiAmount) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal()); - if (satInDecimal == balance) { + final balance = await _availableBalance; + if (amount == balance) { isSendAll = true; } Logging.instance .log("$toAddress $amount $args", level: LogLevel.Info); - String amountToSend = moneroAmountToString(amount: amount); + String amountToSend = amount.decimal.toString(); Logging.instance.log("$amount $amountToSend", level: LogLevel.Info); monero_output.Output output = monero_output.Output(walletBase!); @@ -1481,14 +483,16 @@ class MoneroWallet extends CoinServiceAPI { PendingMoneroTransaction pendingMoneroTransaction = await (awaitPendingTransaction!) as PendingMoneroTransaction; - int realfee = Format.decimalAmountToSatoshis( - Decimal.parse(pendingMoneroTransaction.feeFormatted), coin); - debugPrint("fee? $realfee"); + final int realFee = Amount.fromDecimal( + Decimal.parse(pendingMoneroTransaction.feeFormatted), + fractionDigits: coin.decimals, + ).raw.toInt(); + Map txData = { "pendingMoneroTransaction": pendingMoneroTransaction, - "fee": realfee, + "fee": realFee, "addresss": toAddress, - "recipientAmt": satoshiAmount, + "recipientAmt": amount, }; Logging.instance.log("prepare send: $txData", level: LogLevel.Info); @@ -1510,89 +514,742 @@ class MoneroWallet extends CoinServiceAPI { } } - Mutex prepareSendMutex = Mutex(); - Mutex estimateFeeMutex = Mutex(); - @override - Future estimateFeeFor(int satoshiAmount, int feeRate) async { - MoneroTransactionPriority priority; - FeeRateType feeRateType; - - switch (feeRate) { - case 1: - priority = MoneroTransactionPriority.regular; - feeRateType = FeeRateType.average; - break; - case 2: - priority = MoneroTransactionPriority.medium; - feeRateType = FeeRateType.average; - break; - case 3: - priority = MoneroTransactionPriority.fast; - feeRateType = FeeRateType.fast; - break; - case 4: - priority = MoneroTransactionPriority.fastest; - feeRateType = FeeRateType.fast; - break; - case 0: - default: - priority = MoneroTransactionPriority.slow; - feeRateType = FeeRateType.slow; - break; - } - // int? aprox; - - // corrupted size vs. prev_size occurs but not sure if related to fees or just generating monero transactions in general - - // await estimateFeeMutex.protect(() async { - // { - // try { - // aprox = (await prepareSend( - // // This address is only used for getting an approximate fee, never for sending - // address: - // "8347huhmj6Ggzr1BpZPJAD5oa96ob5Fe8GtQdGZDYVVYVsCgtUNH3pEEzExDuaAVZdC16D4FkAb24J6wUfsKkcZtC8EPXB7", - // satoshiAmount: satoshiAmount, - // args: {"feeRate": feeRateType}))['fee'] as int?; - // await Future.delayed(const Duration(milliseconds: 1000)); - // } catch (e, s) { - // Logging.instance.log("$feeRateType $e $s", level: LogLevel.Error); - final aprox = walletBase!.calculateEstimatedFee(priority, satoshiAmount); - // } - // } - // }); - - print("this is the aprox fee $aprox for $satoshiAmount"); - final fee = aprox; - return fee; - } - - @override - Future generateNewAddress() async { + Future recoverFromMnemonic({ + required String mnemonic, + String? mnemonicPassphrase, // not used at the moment + required int maxUnusedAddressGap, + required int maxNumberOfIndexesToCheck, + required int height, + }) async { + await _prefs.init(); + longMutex = true; + final start = DateTime.now(); try { - const String indexKey = "receivingIndex"; - // First increment the receiving index - await _incrementAddressIndexForChain(0); - final newReceivingIndex = - DB.instance.get(boxName: walletId, key: indexKey) as int; + // Logging.instance.log("IS_INTEGRATION_TEST: $integrationTestFlag"); + // if (!integrationTestFlag) { + // final features = await electrumXClient.getServerFeatures(); + // Logging.instance.log("features: $features"); + // if (_networkType == BasicNetworkType.main) { + // if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { + // throw Exception("genesis hash does not match main net!"); + // } + // } else if (_networkType == BasicNetworkType.test) { + // if (features['genesis_hash'] != GENESIS_HASH_TESTNET) { + // throw Exception("genesis hash does not match test net!"); + // } + // } + // } + // check to make sure we aren't overwriting a mnemonic + // this should never fail + if ((await mnemonicString) != null || + (await this.mnemonicPassphrase) != null) { + longMutex = false; + throw Exception("Attempted to overwrite mnemonic on restore!"); + } + await _secureStorage.write( + key: '${_walletId}_mnemonic', value: mnemonic.trim()); + await _secureStorage.write( + key: '${_walletId}_mnemonicPassphrase', + value: mnemonicPassphrase ?? "", + ); - // Use new index to derive a new receiving address - final newReceivingAddress = - await _generateAddressForChain(0, newReceivingIndex); + await DB.instance + .put(boxName: walletId, key: "restoreHeight", value: height); - // Add that new receiving address to the array of receiving addresses - await _addToAddressesArrayForChain(newReceivingAddress, 0); + walletService = + monero.createMoneroWalletService(DB.instance.moneroWalletInfoBox); + keysStorage = KeyService(_secureStorage); + WalletInfo walletInfo; + WalletCredentials credentials; + String name = _walletId; + final dirPath = + await _pathForWalletDir(name: name, type: WalletType.monero); + final path = await _pathForWallet(name: name, type: WalletType.monero); + credentials = monero.createMoneroRestoreWalletFromSeedCredentials( + name: name, + height: height, + mnemonic: mnemonic.trim(), + ); + try { + walletInfo = WalletInfo.external( + id: WalletBase.idFor(name, WalletType.monero), + name: name, + type: WalletType.monero, + isRecovery: false, + restoreHeight: credentials.height ?? 0, + date: DateTime.now(), + path: path, + dirPath: dirPath, + // TODO: find out what to put for address + address: ''); + credentials.walletInfo = walletInfo; - // Set the new receiving address that the service + _walletCreationService = WalletCreationService( + secureStorage: _secureStorage, + walletService: walletService, + keyService: keysStorage, + ); + _walletCreationService!.changeWalletType(); + // To restore from a seed + final wallet = + await _walletCreationService!.restoreFromSeed(credentials); + walletInfo.address = wallet.walletAddresses.address; + await DB.instance + .add(boxName: WalletInfo.boxName, value: walletInfo); + walletBase?.close(); + walletBase = wallet as MoneroWalletBase; + // walletBase!.onNewBlock = onNewBlock; + // walletBase!.onNewTransaction = onNewTransaction; + // walletBase!.syncStatusChanged = syncStatusChanged; - _currentReceivingAddress = Future(() => newReceivingAddress); - - return true; + await Future.wait([ + updateCachedId(walletId), + updateCachedIsFavorite(false), + ]); + } catch (e, s) { + debugPrint(e.toString()); + debugPrint(s.toString()); + } + final node = await _getCurrentNode(); + final host = Uri.parse(node.host).host; + await walletBase!.connectToNode( + node: Node( + uri: "$host:${node.port}", + type: WalletType.monero, + trusted: node.trusted ?? false, + ), + ); + await walletBase!.rescan(height: credentials.height); + walletBase!.close(); } catch (e, s) { Logging.instance.log( - "Exception rethrown from generateNewAddress(): $e\n$s", + "Exception rethrown from recoverFromMnemonic(): $e\n$s", level: LogLevel.Error); - return false; + longMutex = false; + rethrow; + } + longMutex = false; + + final end = DateTime.now(); + Logging.instance.log( + "$walletName Recovery time: ${end.difference(start).inMilliseconds} millis", + level: LogLevel.Info); + } + + @override + Future refresh() async { + if (refreshMutex) { + Logging.instance.log("$walletId $walletName refreshMutex denied", + level: LogLevel.Info); + return; + } else { + refreshMutex = true; + } + + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.syncing, + walletId, + coin, + ), + ); + + await _refreshTransactions(); + await _updateBalance(); + + await _checkCurrentReceivingAddressesForTransactions(); + + if (walletBase?.syncStatus is SyncedSyncStatus) { + refreshMutex = false; + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.synced, + walletId, + coin, + ), + ); } } + + @override + Future testNetworkConnection() async { + return await walletBase?.isConnected() ?? false; + } + + bool _isActive = false; + + @override + void Function(bool)? get onIsActiveWalletChanged => (isActive) async { + if (_isActive == isActive) { + return; + } + _isActive = isActive; + + if (isActive) { + _hasCalledExit = false; + String? password; + try { + password = + await keysStorage?.getWalletPassword(walletName: _walletId); + } catch (e, s) { + throw Exception("Password not found $e, $s"); + } + walletBase = (await walletService?.openWallet(_walletId, password!)) + as MoneroWalletBase?; + + walletBase!.onNewBlock = onNewBlock; + walletBase!.onNewTransaction = onNewTransaction; + walletBase!.syncStatusChanged = syncStatusChanged; + + if (!(await walletBase!.isConnected())) { + final node = await _getCurrentNode(); + final host = Uri.parse(node.host).host; + await walletBase?.connectToNode( + node: Node( + uri: "$host:${node.port}", + type: WalletType.monero, + trusted: node.trusted ?? false, + ), + ); + } + await walletBase?.startSync(); + await refresh(); + _autoSaveTimer?.cancel(); + _autoSaveTimer = Timer.periodic( + const Duration(seconds: 193), + (_) async => await walletBase?.save(), + ); + } else { + await exit(); + // _autoSaveTimer?.cancel(); + // await walletBase?.save(prioritySave: true); + // walletBase?.close(); + } + }; + + Future _updateCachedBalance(int sats) async { + await DB.instance.put( + boxName: walletId, + key: "cachedMoneroBalanceSats", + value: sats, + ); + } + + int _getCachedBalance() => + DB.instance.get( + boxName: walletId, + key: "cachedMoneroBalanceSats", + ) as int? ?? + 0; + + Future _updateBalance() async { + final total = await _totalBalance; + final available = await _availableBalance; + _balance = Balance( + total: total, + spendable: available, + blockedTotal: Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ), + pendingSpendable: total - available, + ); + await updateCachedBalance(_balance!); + } + + Future get _availableBalance async { + try { + int runningBalance = 0; + for (final entry in walletBase!.balance!.entries) { + runningBalance += entry.value.unlockedBalance; + } + return Amount( + rawValue: BigInt.from(runningBalance), + fractionDigits: coin.decimals, + ); + } catch (_) { + return Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); + } + } + + Future get _totalBalance async { + try { + final balanceEntries = walletBase?.balance?.entries; + if (balanceEntries != null) { + int bal = 0; + for (var element in balanceEntries) { + bal = bal + element.value.fullBalance; + } + await _updateCachedBalance(bal); + return Amount( + rawValue: BigInt.from(bal), + fractionDigits: coin.decimals, + ); + } else { + final transactions = walletBase!.transactionHistory!.transactions; + int transactionBalance = 0; + for (var tx in transactions!.entries) { + if (tx.value.direction == TransactionDirection.incoming) { + transactionBalance += tx.value.amount!; + } else { + transactionBalance += -tx.value.amount! - tx.value.fee!; + } + } + + await _updateCachedBalance(transactionBalance); + return Amount( + rawValue: BigInt.from(transactionBalance), + fractionDigits: coin.decimals, + ); + } + } catch (_) { + return Amount( + rawValue: BigInt.from(_getCachedBalance()), + fractionDigits: coin.decimals, + ); + } + } + + @override + Future updateNode(bool shouldRefresh) async { + final node = await _getCurrentNode(); + + final host = Uri.parse(node.host).host; + await walletBase?.connectToNode( + node: Node( + uri: "$host:${node.port}", + type: WalletType.monero, + trusted: node.trusted ?? false, + ), + ); + + // TODO: is this sync call needed? Do we need to notify ui here? + await walletBase?.startSync(); + + if (shouldRefresh) { + await refresh(); + } + } + + @override + Future updateSentCachedTxData(Map txData) async { + // not used for xmr + return; + } + + @override + bool validateAddress(String address) => walletBase!.validateAddress(address); + + @override + String get walletId => _walletId; + + Future _generateAddressForChain( + int chain, + int index, + ) async { + // + String address = walletBase!.getTransactionAddress(chain, index); + + return isar_models.Address( + walletId: walletId, + derivationIndex: index, + derivationPath: null, + value: address, + publicKey: [], + type: isar_models.AddressType.cryptonote, + subType: chain == 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change, + ); + } + + Future _getFees() async { + // TODO: not use random hard coded values here + return FeeObject( + numberOfBlocksFast: 10, + numberOfBlocksAverage: 15, + numberOfBlocksSlow: 20, + fast: MoneroTransactionPriority.fast.raw!, + medium: MoneroTransactionPriority.regular.raw!, + slow: MoneroTransactionPriority.slow.raw!, + ); + } + + Future _refreshTransactions() async { + await walletBase!.updateTransactions(); + final transactions = walletBase?.transactionHistory!.transactions; + + // final cachedTransactions = + // DB.instance.get(boxName: walletId, key: 'latest_tx_model') + // as TransactionData?; + // int latestTxnBlockHeight = + // DB.instance.get(boxName: walletId, key: "storedTxnDataHeight") + // as int? ?? + // 0; + + // final txidsList = DB.instance + // .get(boxName: walletId, key: "cachedTxids") as List? ?? + // []; + // + // final Set cachedTxids = Set.from(txidsList); + + final List> txnsData = + []; + + if (transactions != null) { + for (var tx in transactions.entries) { + // cachedTxids.add(tx.value.id); + // Logging.instance.log( + // "${tx.value.accountIndex} ${tx.value.addressIndex} ${tx.value.amount} ${tx.value.date} " + // "${tx.value.direction} ${tx.value.fee} ${tx.value.height} ${tx.value.id} ${tx.value.isPending} ${tx.value.key} " + // "${tx.value.recipientAddress}, ${tx.value.additionalInfo} con:${tx.value.confirmations}" + // " ${tx.value.keyIndex}", + // level: LogLevel.Info); + + isar_models.Address? address; + isar_models.TransactionType type; + if (tx.value.direction == TransactionDirection.incoming) { + final addressInfo = tx.value.additionalInfo; + + final addressString = walletBase?.getTransactionAddress( + addressInfo!['accountIndex'] as int, + addressInfo['addressIndex'] as int, + ); + + if (addressString != null) { + address = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(addressString) + .findFirst(); + } + + type = isar_models.TransactionType.incoming; + } else { + // txn.address = ""; + type = isar_models.TransactionType.outgoing; + } + + final txn = isar_models.Transaction( + walletId: walletId, + txid: tx.value.id, + timestamp: (tx.value.date.millisecondsSinceEpoch ~/ 1000), + type: type, + subType: isar_models.TransactionSubType.none, + amount: tx.value.amount ?? 0, + amountString: Amount( + rawValue: BigInt.from(tx.value.amount ?? 0), + fractionDigits: coin.decimals, + ).toJsonString(), + fee: tx.value.fee ?? 0, + height: tx.value.height, + isCancelled: false, + isLelantus: false, + slateId: null, + otherData: null, + nonce: null, + inputs: [], + outputs: [], + ); + + txnsData.add(Tuple2(txn, address)); + } + } + + await db.addNewTransactionData(txnsData, walletId); + + // quick hack to notify manager to call notifyListeners if + // transactions changed + if (txnsData.isNotEmpty) { + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "Transactions updated/added for: $walletId $walletName ", + walletId, + ), + ); + } + } + + Future _pathForWalletDir({ + required String name, + required WalletType type, + }) async { + Directory root = await StackFileSystem.applicationRootDirectory(); + + final prefix = walletTypeToString(type).toLowerCase(); + final walletsDir = Directory('${root.path}/wallets'); + final walletDire = Directory('${walletsDir.path}/$prefix/$name'); + + if (!walletDire.existsSync()) { + walletDire.createSync(recursive: true); + } + + return walletDire.path; + } + + Future _pathForWallet({ + required String name, + required WalletType type, + }) async => + await _pathForWalletDir(name: name, type: type) + .then((path) => '$path/$name'); + + Future _getCurrentNode() async { + return NodeService(secureStorageInterface: _secureStorage) + .getPrimaryNodeFor(coin: coin) ?? + DefaultNodes.getNodeFor(coin); + } + + void onNewBlock({required int height, required int blocksLeft}) { + // + print("============================="); + print("New Block! :: $walletName"); + print("============================="); + updateCachedChainHeight(height); + _refreshTxDataHelper(); + } + + void onNewTransaction() { + // + print("============================="); + print("New Transaction! :: $walletName"); + print("============================="); + + // call this here? + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "New data found in $walletId $walletName in background!", + walletId, + ), + ); + } + + bool _txRefreshLock = false; + int _lastCheckedHeight = -1; + int _txCount = 0; + + Future _refreshTxDataHelper() async { + if (_txRefreshLock) return; + _txRefreshLock = true; + + final syncStatus = walletBase?.syncStatus; + + if (syncStatus != null && syncStatus is SyncingSyncStatus) { + final int blocksLeft = syncStatus.blocksLeft; + final tenKChange = blocksLeft ~/ 10000; + + // only refresh transactions periodically during a sync + if (_lastCheckedHeight == -1 || tenKChange < _lastCheckedHeight) { + _lastCheckedHeight = tenKChange; + await _refreshTxData(); + } + } else { + await _refreshTxData(); + } + + _txRefreshLock = false; + } + + Future _refreshTxData() async { + await _refreshTransactions(); + final count = await db.getTransactions(walletId).count(); + + if (count > _txCount) { + _txCount = count; + await _updateBalance(); + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "New transaction data found in $walletId $walletName!", + walletId, + ), + ); + } + } + + void syncStatusChanged() async { + final syncStatus = walletBase?.syncStatus; + if (syncStatus != null) { + if (syncStatus.progress() == 1) { + refreshMutex = false; + } + + WalletSyncStatus? status; + _isConnected = true; + + if (syncStatus is SyncingSyncStatus) { + final int blocksLeft = syncStatus.blocksLeft; + + // ensure at least 1 to prevent math errors + final int height = max(1, syncStatus.height); + + final nodeHeight = height + blocksLeft; + + final percent = height / nodeHeight; + + final highest = max(highestPercentCached, percent); + + // update cached + if (highestPercentCached < percent) { + highestPercentCached = percent; + } + await updateCachedChainHeight(height); + + GlobalEventBus.instance.fire( + RefreshPercentChangedEvent( + highest, + walletId, + ), + ); + GlobalEventBus.instance.fire( + BlocksRemainingEvent( + blocksLeft, + walletId, + ), + ); + } else if (syncStatus is SyncedSyncStatus) { + status = WalletSyncStatus.synced; + } else if (syncStatus is NotConnectedSyncStatus) { + status = WalletSyncStatus.unableToSync; + _isConnected = false; + } else if (syncStatus is StartingSyncStatus) { + status = WalletSyncStatus.syncing; + GlobalEventBus.instance.fire( + RefreshPercentChangedEvent( + highestPercentCached, + walletId, + ), + ); + } else if (syncStatus is FailedSyncStatus) { + status = WalletSyncStatus.unableToSync; + _isConnected = false; + } else if (syncStatus is ConnectingSyncStatus) { + status = WalletSyncStatus.syncing; + GlobalEventBus.instance.fire( + RefreshPercentChangedEvent( + highestPercentCached, + walletId, + ), + ); + } else if (syncStatus is ConnectedSyncStatus) { + status = WalletSyncStatus.syncing; + GlobalEventBus.instance.fire( + RefreshPercentChangedEvent( + highestPercentCached, + walletId, + ), + ); + } else if (syncStatus is LostConnectionSyncStatus) { + status = WalletSyncStatus.unableToSync; + _isConnected = false; + } + + if (status != null) { + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + status, + walletId, + coin, + ), + ); + } + } + } + + Future _checkCurrentReceivingAddressesForTransactions() async { + try { + await _checkReceivingAddressForTransactions(); + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _checkCurrentReceivingAddressesForTransactions(): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + Future _checkReceivingAddressForTransactions() async { + try { + int highestIndex = -1; + for (var element + in walletBase!.transactionHistory!.transactions!.entries) { + if (element.value.direction == TransactionDirection.incoming) { + int curAddressIndex = + element.value.additionalInfo!['addressIndex'] as int; + if (curAddressIndex > highestIndex) { + highestIndex = curAddressIndex; + } + } + } + + // Check the new receiving index + final currentReceiving = await _currentReceivingAddress; + final curIndex = currentReceiving?.derivationIndex ?? -1; + + if (highestIndex >= curIndex) { + // First increment the receiving index + final newReceivingIndex = curIndex + 1; + + // Use new index to derive a new receiving address + final newReceivingAddress = + await _generateAddressForChain(0, newReceivingIndex); + + final existing = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(newReceivingAddress.value) + .findFirst(); + if (existing == null) { + // Add that new change address + await db.putAddress(newReceivingAddress); + } else { + // we need to update the address + await db.updateAddress(existing, newReceivingAddress); + + // since we updated an existing address there is a chance it has + // some tx history. To prevent address reuse we will call check again + // recursively + await _checkReceivingAddressForTransactions(); + } + } + } on SocketException catch (se, s) { + Logging.instance.log( + "SocketException caught in _checkReceivingAddressForTransactions(): $se\n$s", + level: LogLevel.Error); + return; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _checkReceivingAddressForTransactions(): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + double get highestPercentCached => + DB.instance.get(boxName: walletId, key: "highestPercentCached") + as double? ?? + 0; + + set highestPercentCached(double value) => DB.instance.put( + boxName: walletId, + key: "highestPercentCached", + value: value, + ); + + @override + int get storedChainHeight => getCachedChainHeight(); + + @override + Balance get balance => _balance ??= getCachedBalance(); + Balance? _balance; + + @override + Future> get transactions => + db.getTransactions(walletId).sortByTimestampDesc().findAll(); + + @override + // TODO: implement utxos + Future> get utxos => throw UnimplementedError(); } diff --git a/lib/services/coins/namecoin/namecoin_wallet.dart b/lib/services/coins/namecoin/namecoin_wallet.dart index 85fea61ad..19c5a823b 100644 --- a/lib/services/coins/namecoin/namecoin_wallet.dart +++ b/lib/services/coins/namecoin/namecoin_wallet.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'dart:typed_data'; import 'package:bech32/bech32.dart'; import 'package:bip32/bip32.dart' as bip32; @@ -10,140 +9,131 @@ import 'package:bitcoindart/bitcoindart.dart'; import 'package:bs58check/bs58check.dart' as bs58check; import 'package:crypto/crypto.dart'; import 'package:decimal/decimal.dart'; -import 'package:devicelocale/devicelocale.dart'; import 'package:flutter/foundation.dart'; -import 'package:http/http.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; -import 'package:stackwallet/hive/db.dart'; -import 'package:stackwallet/models/models.dart' as models; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; import 'package:stackwallet/models/paymint/fee_object_model.dart'; -import 'package:stackwallet/models/paymint/transactions_model.dart'; -import 'package:stackwallet/models/paymint/utxo_model.dart'; +import 'package:stackwallet/models/signing_data.dart'; import 'package:stackwallet/services/coins/coin_service.dart'; import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +import 'package:stackwallet/services/mixins/coin_control_interface.dart'; +import 'package:stackwallet/services/mixins/electrum_x_parsing.dart'; +import 'package:stackwallet/services/mixins/wallet_cache.dart'; +import 'package:stackwallet/services/mixins/wallet_db.dart'; +import 'package:stackwallet/services/mixins/xpubable.dart'; import 'package:stackwallet/services/node_service.dart'; -import 'package:stackwallet/services/notifications_api.dart'; -import 'package:stackwallet/services/price.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; -import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/bip32_utils.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/default_nodes.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart'; import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; +import 'package:stackwallet/widgets/crypto_notifications.dart'; import 'package:tuple/tuple.dart'; import 'package:uuid/uuid.dart'; const int MINIMUM_CONFIRMATIONS = 2; // Find real dust limit -const int DUST_LIMIT = 546; +final Amount DUST_LIMIT = Amount( + rawValue: BigInt.from(546), + fractionDigits: Coin.particl.decimals, +); const String GENESIS_HASH_MAINNET = "000000000062b72c5e2ceb45fbc8587e807c155b0da735e6483dfba2f0a9c770"; const String GENESIS_HASH_TESTNET = "00000007199508e34a9ff81e6ec0c477a4cccff2a4767a8eee39c11db367b008"; -enum DerivePathType { bip44, bip49, bip84 } - -bip32.BIP32 getBip32Node( - int chain, - int index, - String mnemonic, - NetworkType network, - DerivePathType derivePathType, -) { - final root = getBip32Root(mnemonic, network); - - final node = getBip32NodeFromRoot(chain, index, root, derivePathType); - return node; -} - -/// wrapper for compute() -bip32.BIP32 getBip32NodeWrapper( - Tuple5 args, -) { - return getBip32Node( - args.item1, - args.item2, - args.item3, - args.item4, - args.item5, - ); -} - -bip32.BIP32 getBip32NodeFromRoot( - int chain, - int index, - bip32.BIP32 root, - DerivePathType derivePathType, -) { +String constructDerivePath({ + required DerivePathType derivePathType, + required int networkWIF, + int account = 0, + required int chain, + required int index, +}) { String coinType; - switch (root.network.wif) { + switch (networkWIF) { case 0xb4: // nmc mainnet wif coinType = "7"; // nmc mainnet break; default: - throw Exception("Invalid Namecoin network type used!"); + throw Exception("Invalid Namecoin network wif used!"); } + + int purpose; switch (derivePathType) { case DerivePathType.bip44: - return root.derivePath("m/44'/$coinType'/0'/$chain/$index"); + purpose = 44; + break; case DerivePathType.bip49: - return root.derivePath("m/49'/$coinType'/0'/$chain/$index"); + purpose = 49; + break; case DerivePathType.bip84: - return root.derivePath("m/84'/$coinType'/0'/$chain/$index"); + purpose = 84; + break; default: - throw Exception("DerivePathType must not be null."); + throw Exception("DerivePathType $derivePathType not supported"); } + + return "m/$purpose'/$coinType'/$account'/$chain/$index"; } -/// wrapper for compute() -bip32.BIP32 getBip32NodeFromRootWrapper( - Tuple4 args, -) { - return getBip32NodeFromRoot( - args.item1, - args.item2, - args.item3, - args.item4, - ); -} +class NamecoinWallet extends CoinServiceAPI + with WalletCache, WalletDB, ElectrumXParsing, CoinControlInterface + implements XPubAble { + NamecoinWallet({ + required String walletId, + required String walletName, + required Coin coin, + required ElectrumX client, + required CachedElectrumX cachedClient, + required TransactionNotificationTracker tracker, + required SecureStorageInterface secureStore, + MainDB? mockableOverride, + }) { + txTracker = tracker; + _walletId = walletId; + _walletName = walletName; + _coin = coin; + _electrumXClient = client; + _cachedElectrumXClient = cachedClient; + _secureStore = secureStore; + initCache(walletId, coin); + initWalletDB(mockableOverride: mockableOverride); + initCoinControlInterface( + walletId: walletId, + walletName: walletName, + coin: coin, + db: db, + getChainHeight: () => chainHeight, + refreshedBalanceCallback: (balance) async { + _balance = balance; + await updateCachedBalance(_balance!); + }, + ); + } -bip32.BIP32 getBip32Root(String mnemonic, NetworkType network) { - final seed = bip39.mnemonicToSeed(mnemonic); - final networkType = bip32.NetworkType( - wif: network.wif, - bip32: bip32.Bip32Type( - public: network.bip32.public, - private: network.bip32.private, - ), - ); - - final root = bip32.BIP32.fromSeed(seed, networkType); - return root; -} - -/// wrapper for compute() -bip32.BIP32 getBip32RootWrapper(Tuple2 args) { - return getBip32Root(args.item1, args.item2); -} - -class NamecoinWallet extends CoinServiceAPI { static const integrationTestFlag = bool.fromEnvironment("IS_INTEGRATION_TEST"); final _prefs = Prefs.instance; Timer? timer; - late Coin _coin; + late final Coin _coin; late final TransactionNotificationTracker txTracker; @@ -156,93 +146,53 @@ class NamecoinWallet extends CoinServiceAPI { } } - List outputsList = []; - @override set isFavorite(bool markFavorite) { - DB.instance.put( - boxName: walletId, key: "isFavorite", value: markFavorite); + _isFavorite = markFavorite; + updateCachedIsFavorite(markFavorite); } @override - bool get isFavorite { - try { - return DB.instance.get(boxName: walletId, key: "isFavorite") - as bool; - } catch (e, s) { - Logging.instance.log( - "isFavorite fetch failed (returning false by default): $e\n$s", - level: LogLevel.Error); - return false; - } - } + bool get isFavorite => _isFavorite ??= getCachedIsFavorite(); + + bool? _isFavorite; @override Coin get coin => _coin; @override - Future> get allOwnAddresses => - _allOwnAddresses ??= _fetchAllOwnAddresses(); - Future>? _allOwnAddresses; - - Future? _utxoData; - Future get utxoData => _utxoData ??= _fetchUtxoData(); + Future> get utxos => db.getUTXOs(walletId).findAll(); @override - Future> get unspentOutputs async => - (await utxoData).unspentOutputArray; + Future> get transactions => + db.getTransactions(walletId).sortByTimestampDesc().findAll(); @override - Future get availableBalance async { - final data = await utxoData; - return Format.satoshisToAmount( - data.satoshiBalance - data.satoshiBalanceUnconfirmed, - coin: coin); - } + Future get currentReceivingAddress async => + (await _currentReceivingAddress).value; - @override - Future get pendingBalance async { - final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalanceUnconfirmed, coin: coin); - } + Future get _currentReceivingAddress async => + (await db + .getAddresses(walletId) + .filter() + .typeEqualTo(isar_models.AddressType.p2wpkh) + .subTypeEqualTo(isar_models.AddressSubType.receiving) + .sortByDerivationIndexDesc() + .findFirst()) ?? + await _generateAddressForChain(0, 0, DerivePathTypeExt.primaryFor(coin)); - @override - Future get balanceMinusMaxFee async => - (await availableBalance) - - (Decimal.fromInt((await maxFee)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(); + Future get currentChangeAddress async => + (await _currentChangeAddress).value; - @override - Future get totalBalance async { - if (!isActive) { - final totalBalance = DB.instance - .get(boxName: walletId, key: 'totalBalance') as int?; - if (totalBalance == null) { - final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalance, coin: coin); - } else { - return Format.satoshisToAmount(totalBalance, coin: coin); - } - } - final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalance, coin: coin); - } - - @override - Future get currentReceivingAddress => _currentReceivingAddress ??= - _getCurrentAddressForChain(0, DerivePathType.bip84); - Future? _currentReceivingAddress; - - Future get currentLegacyReceivingAddress => - _currentReceivingAddressP2PKH ??= - _getCurrentAddressForChain(0, DerivePathType.bip44); - Future? _currentReceivingAddressP2PKH; - - Future get currentReceivingAddressP2SH => - _currentReceivingAddressP2SH ??= - _getCurrentAddressForChain(0, DerivePathType.bip49); - Future? _currentReceivingAddressP2SH; + Future get _currentChangeAddress async => + (await db + .getAddresses(walletId) + .filter() + .typeEqualTo(isar_models.AddressType.p2wpkh) + .subTypeEqualTo(isar_models.AddressSubType.change) + .sortByDerivationIndexDesc() + .findFirst()) ?? + await _generateAddressForChain(1, 0, DerivePathTypeExt.primaryFor(coin)); @override Future exit() async { @@ -272,27 +222,38 @@ class NamecoinWallet extends CoinServiceAPI { @override Future> get mnemonic => _getMnemonicList(); + @override + Future get mnemonicString => + _secureStore.read(key: '${_walletId}_mnemonic'); + + @override + Future get mnemonicPassphrase => _secureStore.read( + key: '${_walletId}_mnemonicPassphrase', + ); + Future get chainHeight async { try { final result = await _electrumXClient.getBlockHeadTip(); - return result["height"] as int; + final height = result["height"] as int; + await updateCachedChainHeight(height); + if (height > storedChainHeight) { + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "Updated current chain height in $walletId $walletName!", + walletId, + ), + ); + } + return height; } catch (e, s) { Logging.instance.log("Exception caught in chainHeight: $e\n$s", level: LogLevel.Error); - return -1; + return storedChainHeight; } } - int get storedChainHeight { - final storedHeight = DB.instance - .get(boxName: walletId, key: "storedChainHeight") as int?; - return storedHeight ?? 0; - } - - Future updateStoredChainHeight({required int newHeight}) async { - await DB.instance.put( - boxName: walletId, key: "storedChainHeight", value: newHeight); - } + @override + int get storedChainHeight => getCachedChainHeight(); DerivePathType addressType({required String address}) { Uint8List? decodeBase58; @@ -334,6 +295,7 @@ class NamecoinWallet extends CoinServiceAPI { @override Future recoverFromMnemonic({ required String mnemonic, + String? mnemonicPassphrase, required int maxUnusedAddressGap, required int maxNumberOfIndexesToCheck, required int height, @@ -365,14 +327,21 @@ class NamecoinWallet extends CoinServiceAPI { } // check to make sure we aren't overwriting a mnemonic // this should never fail - if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) { + if ((await mnemonicString) != null || + (await this.mnemonicPassphrase) != null) { longMutex = false; throw Exception("Attempted to overwrite mnemonic on restore!"); } await _secureStore.write( key: '${_walletId}_mnemonic', value: mnemonic.trim()); + await _secureStore.write( + key: '${_walletId}_mnemonicPassphrase', + value: mnemonicPassphrase ?? "", + ); + await _recoverWalletFromBIP32SeedPhrase( mnemonic: mnemonic.trim(), + mnemonicPassphrase: mnemonicPassphrase ?? "", maxUnusedAddressGap: maxUnusedAddressGap, maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, ); @@ -397,8 +366,8 @@ class NamecoinWallet extends CoinServiceAPI { int txCountBatchSize, bip32.BIP32 root, DerivePathType type, - int account) async { - List addressArray = []; + int chain) async { + List addressArray = []; int returningIndex = -1; Map> derivations = {}; int gapCounter = 0; @@ -407,7 +376,7 @@ class NamecoinWallet extends CoinServiceAPI { index += txCountBatchSize) { List iterationsAddressArray = []; Logging.instance.log( - "index: $index, \t GapCounter $account ${type.name}: $gapCounter", + "index: $index, \t GapCounter $chain ${type.name}: $gapCounter", level: LogLevel.Info); final _id = "k_$index"; @@ -415,26 +384,27 @@ class NamecoinWallet extends CoinServiceAPI { final Map receivingNodes = {}; for (int j = 0; j < txCountBatchSize; j++) { - final node = await compute( - getBip32NodeFromRootWrapper, - Tuple4( - account, - index + j, - root, - type, - ), + final derivePath = constructDerivePath( + derivePathType: type, + networkWIF: root.network.wif, + chain: chain, + index: index + j, ); - String? address; + final node = await Bip32Utils.getBip32NodeFromRoot(root, derivePath); + + String addressString; + isar_models.AddressType addrType; switch (type) { case DerivePathType.bip44: - address = P2PKH( + addressString = P2PKH( data: PaymentData(pubkey: node.publicKey), network: _network) .data .address!; + addrType = isar_models.AddressType.p2pkh; break; case DerivePathType.bip49: - address = P2SH( + addressString = P2SH( data: PaymentData( redeem: P2WPKH( data: PaymentData(pubkey: node.publicKey), @@ -444,18 +414,33 @@ class NamecoinWallet extends CoinServiceAPI { network: _network) .data .address!; + addrType = isar_models.AddressType.p2sh; break; case DerivePathType.bip84: - address = P2WPKH( + addressString = P2WPKH( network: _network, data: PaymentData(pubkey: node.publicKey), overridePrefix: namecoin.bech32!) .data .address!; + addrType = isar_models.AddressType.p2wpkh; break; default: - throw Exception("No Path type $type exists"); + throw Exception("DerivePathType $type not supported"); } + + final address = isar_models.Address( + walletId: walletId, + subType: chain == 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change, + type: addrType, + publicKey: node.publicKey, + value: addressString, + derivationIndex: index + j, + derivationPath: isar_models.DerivationPath()..value = derivePath, + ); + receivingNodes.addAll({ "${_id}_$j": { "node": node, @@ -463,7 +448,7 @@ class NamecoinWallet extends CoinServiceAPI { } }); txCountCallArgs.addAll({ - "${_id}_$j": address, + "${_id}_$j": addressString, }); } @@ -475,15 +460,16 @@ class NamecoinWallet extends CoinServiceAPI { int count = counts["${_id}_$k"]!; if (count > 0) { final node = receivingNodes["${_id}_$k"]; + final address = node["address"] as isar_models.Address; // add address to array - addressArray.add(node["address"] as String); - iterationsAddressArray.add(node["address"] as String); + addressArray.add(address); + iterationsAddressArray.add(address.value); // set current index returningIndex = index + k; // reset counter gapCounter = 0; // add info to derivations - derivations[node["address"] as String] = { + derivations[address.value] = { "pubKey": Format.uint8listToString( (node["node"] as bip32.BIP32).publicKey), "wif": (node["node"] as bip32.BIP32).toWIF(), @@ -527,8 +513,10 @@ class NamecoinWallet extends CoinServiceAPI { Future _recoverWalletFromBIP32SeedPhrase({ required String mnemonic, + required String mnemonicPassphrase, int maxUnusedAddressGap = 20, int maxNumberOfIndexesToCheck = 1000, + bool isRescan = false, }) async { longMutex = true; @@ -539,18 +527,22 @@ class NamecoinWallet extends CoinServiceAPI { Map> p2shChangeDerivations = {}; Map> p2wpkhChangeDerivations = {}; - final root = await compute(getBip32RootWrapper, Tuple2(mnemonic, _network)); + final root = await Bip32Utils.getBip32Root( + mnemonic, + mnemonicPassphrase, + _network, + ); - List p2pkhReceiveAddressArray = []; - List p2shReceiveAddressArray = []; - List p2wpkhReceiveAddressArray = []; + List p2pkhReceiveAddressArray = []; + List p2shReceiveAddressArray = []; + List p2wpkhReceiveAddressArray = []; int p2pkhReceiveIndex = -1; int p2shReceiveIndex = -1; int p2wpkhReceiveIndex = -1; - List p2pkhChangeAddressArray = []; - List p2shChangeAddressArray = []; - List p2wpkhChangeAddressArray = []; + List p2pkhChangeAddressArray = []; + List p2shChangeAddressArray = []; + List p2wpkhChangeAddressArray = []; int p2pkhChangeIndex = -1; int p2shChangeIndex = -1; int p2wpkhChangeIndex = -1; @@ -593,37 +585,37 @@ class NamecoinWallet extends CoinServiceAPI { ]); p2pkhReceiveAddressArray = - (await resultReceive44)['addressArray'] as List; + (await resultReceive44)['addressArray'] as List; p2pkhReceiveIndex = (await resultReceive44)['index'] as int; p2pkhReceiveDerivations = (await resultReceive44)['derivations'] as Map>; p2shReceiveAddressArray = - (await resultReceive49)['addressArray'] as List; + (await resultReceive49)['addressArray'] as List; p2shReceiveIndex = (await resultReceive49)['index'] as int; p2shReceiveDerivations = (await resultReceive49)['derivations'] as Map>; p2wpkhReceiveAddressArray = - (await resultReceive84)['addressArray'] as List; + (await resultReceive84)['addressArray'] as List; p2wpkhReceiveIndex = (await resultReceive84)['index'] as int; p2wpkhReceiveDerivations = (await resultReceive84)['derivations'] as Map>; p2pkhChangeAddressArray = - (await resultChange44)['addressArray'] as List; + (await resultChange44)['addressArray'] as List; p2pkhChangeIndex = (await resultChange44)['index'] as int; p2pkhChangeDerivations = (await resultChange44)['derivations'] as Map>; p2shChangeAddressArray = - (await resultChange49)['addressArray'] as List; + (await resultChange49)['addressArray'] as List; p2shChangeIndex = (await resultChange49)['index'] as int; p2shChangeDerivations = (await resultChange49)['derivations'] as Map>; p2wpkhChangeAddressArray = - (await resultChange84)['addressArray'] as List; + (await resultChange84)['addressArray'] as List; p2wpkhChangeIndex = (await resultChange84)['index'] as int; p2wpkhChangeDerivations = (await resultChange84)['derivations'] as Map>; @@ -672,19 +664,16 @@ class NamecoinWallet extends CoinServiceAPI { final address = await _generateAddressForChain(0, 0, DerivePathType.bip44); p2pkhReceiveAddressArray.add(address); - p2pkhReceiveIndex = 0; } if (p2shReceiveIndex == -1) { final address = await _generateAddressForChain(0, 0, DerivePathType.bip49); p2shReceiveAddressArray.add(address); - p2shReceiveIndex = 0; } if (p2wpkhReceiveIndex == -1) { final address = await _generateAddressForChain(0, 0, DerivePathType.bip84); p2wpkhReceiveAddressArray.add(address); - p2wpkhReceiveIndex = 0; } // If restoring a wallet that never sent any funds with change, then set changeArray @@ -693,69 +682,44 @@ class NamecoinWallet extends CoinServiceAPI { final address = await _generateAddressForChain(1, 0, DerivePathType.bip44); p2pkhChangeAddressArray.add(address); - p2pkhChangeIndex = 0; } if (p2shChangeIndex == -1) { final address = await _generateAddressForChain(1, 0, DerivePathType.bip49); p2shChangeAddressArray.add(address); - p2shChangeIndex = 0; } if (p2wpkhChangeIndex == -1) { final address = await _generateAddressForChain(1, 0, DerivePathType.bip84); p2wpkhChangeAddressArray.add(address); - p2wpkhChangeIndex = 0; } - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2WPKH', - value: p2wpkhReceiveAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2WPKH', - value: p2wpkhChangeAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2PKH', - value: p2pkhReceiveAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2PKH', - value: p2pkhChangeAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2SH', - value: p2shReceiveAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2SH', - value: p2shChangeAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2WPKH', - value: p2wpkhReceiveIndex); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2WPKH', - value: p2wpkhChangeIndex); - await DB.instance.put( - boxName: walletId, key: 'changeIndexP2PKH', value: p2pkhChangeIndex); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2PKH', - value: p2pkhReceiveIndex); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2SH', - value: p2shReceiveIndex); - await DB.instance.put( - boxName: walletId, key: 'changeIndexP2SH', value: p2shChangeIndex); - await DB.instance - .put(boxName: walletId, key: "id", value: _walletId); - await DB.instance - .put(boxName: walletId, key: "isFavorite", value: false); + if (isRescan) { + await db.updateOrPutAddresses([ + ...p2wpkhReceiveAddressArray, + ...p2wpkhChangeAddressArray, + ...p2pkhReceiveAddressArray, + ...p2pkhChangeAddressArray, + ...p2shReceiveAddressArray, + ...p2shChangeAddressArray, + ]); + } else { + await db.putAddresses([ + ...p2wpkhReceiveAddressArray, + ...p2wpkhChangeAddressArray, + ...p2pkhReceiveAddressArray, + ...p2pkhChangeAddressArray, + ...p2shReceiveAddressArray, + ...p2shChangeAddressArray, + ]); + } + + await _updateUTXOs(); + + await Future.wait([ + updateCachedId(walletId), + updateCachedIsFavorite(false), + ]); longMutex = false; } catch (e, s) { @@ -795,11 +759,15 @@ class NamecoinWallet extends CoinServiceAPI { } if (!needsRefresh) { var allOwnAddresses = await _fetchAllOwnAddresses(); - List> allTxs = - await _fetchHistory(allOwnAddresses); - final txData = await transactionData; + List> allTxs = await _fetchHistory( + allOwnAddresses.map((e) => e.value).toList(growable: false)); for (Map transaction in allTxs) { - if (txData.findTransaction(transaction['tx_hash'] as String) == + final txid = transaction['tx_hash'] as String; + if ((await db + .getTransactions(walletId) + .filter() + .txidMatches(txid) + .findFirst()) == null) { Logging.instance.log( " txid not found in address history already ${transaction['tx_hash']}", @@ -818,16 +786,25 @@ class NamecoinWallet extends CoinServiceAPI { } } - Future getAllTxsToWatch( - TransactionData txData, - ) async { + Future getAllTxsToWatch() async { if (_hasCalledExit) return; - List unconfirmedTxnsToNotifyPending = []; - List unconfirmedTxnsToNotifyConfirmed = []; + List unconfirmedTxnsToNotifyPending = []; + List unconfirmedTxnsToNotifyConfirmed = []; - for (final chunk in txData.txChunks) { - for (final tx in chunk.transactions) { - if (tx.confirmedStatus) { + final currentChainHeight = await chainHeight; + + final txCount = await db.getTransactions(walletId).count(); + + const paginateLimit = 50; + + for (int i = 0; i < txCount; i += paginateLimit) { + final transactions = await db + .getTransactions(walletId) + .offset(i) + .limit(paginateLimit) + .findAll(); + for (final tx in transactions) { + if (tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { // get all transactions that were notified as pending but not as confirmed if (txTracker.wasNotifiedPending(tx.txid) && !txTracker.wasNotifiedConfirmed(tx.txid)) { @@ -844,60 +821,74 @@ class NamecoinWallet extends CoinServiceAPI { // notify on unconfirmed transactions for (final tx in unconfirmedTxnsToNotifyPending) { - if (tx.txType == "Received") { - unawaited(NotificationApi.showNotification( - title: "Incoming transaction", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS, - coinName: coin.name, - txid: tx.txid, - confirmations: tx.confirmations, - requiredConfirmations: MINIMUM_CONFIRMATIONS, - )); + final confirmations = tx.getConfirmations(currentChainHeight); + + if (tx.type == isar_models.TransactionType.incoming) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Incoming transaction", + walletId: walletId, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, + txid: tx.txid, + confirmations: confirmations, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); + await txTracker.addNotifiedPending(tx.txid); - } else if (tx.txType == "Sent") { - unawaited(NotificationApi.showNotification( - title: "Sending transaction", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS, - coinName: coin.name, - txid: tx.txid, - confirmations: tx.confirmations, - requiredConfirmations: MINIMUM_CONFIRMATIONS, - )); + } else if (tx.type == isar_models.TransactionType.outgoing) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Sending transaction", + walletId: walletId, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, + txid: tx.txid, + confirmations: confirmations, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); + await txTracker.addNotifiedPending(tx.txid); } } // notify on confirmed for (final tx in unconfirmedTxnsToNotifyConfirmed) { - if (tx.txType == "Received") { - unawaited(NotificationApi.showNotification( - title: "Incoming transaction confirmed", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: false, - coinName: coin.name, - )); + if (tx.type == isar_models.TransactionType.incoming) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Incoming transaction confirmed", + walletId: walletId, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: false, + txid: tx.txid, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); + await txTracker.addNotifiedConfirmed(tx.txid); - } else if (tx.txType == "Sent") { - unawaited(NotificationApi.showNotification( - title: "Outgoing transaction confirmed", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: false, - coinName: coin.name, - )); + } else if (tx.type == isar_models.TransactionType.outgoing) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Outgoing transaction confirmed", + walletId: walletId, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: false, + txid: tx.txid, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); + await txTracker.addNotifiedConfirmed(tx.txid); } } @@ -962,46 +953,31 @@ class NamecoinWallet extends CoinServiceAPI { .log("cached height: $storedHeight", level: LogLevel.Info); if (currentHeight != storedHeight) { - if (currentHeight != -1) { - // -1 failed to fetch current height - unawaited(updateStoredChainHeight(newHeight: currentHeight)); - } - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId)); - final changeAddressForTransactions = - _checkChangeAddressForTransactions(DerivePathType.bip84); + await _checkChangeAddressForTransactions(); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId)); - final currentReceivingAddressesForTransactions = - _checkCurrentReceivingAddressesForTransactions(); + await _checkCurrentReceivingAddressesForTransactions(); - final newTxData = _fetchTransactionData(); + final fetchFuture = _refreshTransactions(); + final utxosRefreshFuture = _updateUTXOs(); GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.50, walletId)); - final newUtxoData = _fetchUtxoData(); final feeObj = _getFees(); GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.60, walletId)); - _transactionData = Future(() => newTxData); - GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.70, walletId)); _feeObject = Future(() => feeObj); - _utxoData = Future(() => newUtxoData); + + await utxosRefreshFuture; GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.80, walletId)); - final allTxsToWatch = getAllTxsToWatch(await newTxData); - await Future.wait([ - newTxData, - changeAddressForTransactions, - currentReceivingAddressesForTransactions, - newUtxoData, - feeObj, - allTxsToWatch, - ]); + await fetchFuture; + await getAllTxsToWatch(); GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.90, walletId)); } @@ -1057,12 +1033,13 @@ class NamecoinWallet extends CoinServiceAPI { @override Future> prepareSend({ required String address, - required int satoshiAmount, + required Amount amount, Map? args, }) async { try { final feeRateType = args?["feeRate"]; final feeRateAmount = args?["feeRateAmount"]; + final utxos = args?["UTXOs"] as Set?; if (feeRateType is FeeRateType || feeRateAmount is int) { late final int rate; if (feeRateType is FeeRateType) { @@ -1086,14 +1063,20 @@ class NamecoinWallet extends CoinServiceAPI { // check for send all bool isSendAll = false; - final balance = - Format.decimalAmountToSatoshis(await availableBalance, coin); - if (satoshiAmount == balance) { + if (amount == balance.spendable) { isSendAll = true; } - final txData = - await coinSelection(satoshiAmount, rate, address, isSendAll); + final bool coinControl = utxos != null; + + final txData = await coinSelection( + satoshiAmountToSend: amount.raw.toInt(), + selectedTxFeeRate: rate, + recipientAddress: address, + isSendAll: isSendAll, + utxos: utxos?.toList(), + coinControl: coinControl, + ); Logging.instance.log("prepare send: $txData", level: LogLevel.Info); try { @@ -1156,6 +1139,11 @@ class NamecoinWallet extends CoinServiceAPI { final txHash = await _electrumXClient.broadcastTransaction(rawTx: hex); Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info); + final utxos = txData["usedUTXOs"] as List; + + // mark utxos as used + await db.putUTXOs(utxos.map((e) => e.copyWith(used: true)).toList()); + return txHash; } catch (e, s) { Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s", @@ -1164,24 +1152,6 @@ class NamecoinWallet extends CoinServiceAPI { } } - @override - Future send({ - required String toAddress, - required int amount, - Map args = const {}, - }) async { - try { - final txData = await prepareSend( - address: toAddress, satoshiAmount: amount, args: args); - final txHash = await confirmSend(txData: txData); - return txHash; - } catch (e, s) { - Logging.instance - .log("Exception rethrown from send(): $e\n$s", level: LogLevel.Error); - rethrow; - } - } - @override Future testNetworkConnection() async { try { @@ -1234,7 +1204,7 @@ class NamecoinWallet extends CoinServiceAPI { Logging.instance .log("Generating new ${coin.prettyName} wallet.", level: LogLevel.Info); - if ((DB.instance.get(boxName: walletId, key: "id")) != null) { + if (getCachedId() != null) { throw Exception( "Attempted to initialize a new wallet using an existing wallet ID!"); } @@ -1248,81 +1218,60 @@ class NamecoinWallet extends CoinServiceAPI { rethrow; } await Future.wait([ - DB.instance.put(boxName: walletId, key: "id", value: walletId), - DB.instance - .put(boxName: walletId, key: "isFavorite", value: false), + updateCachedId(walletId), + updateCachedIsFavorite(false), ]); } @override Future initializeExisting() async { - Logging.instance.log("Opening existing ${coin.prettyName} wallet.", + Logging.instance.log("initializeExisting() ${coin.prettyName} wallet.", level: LogLevel.Info); - if ((DB.instance.get(boxName: walletId, key: "id")) == null) { + if (getCachedId() == null) { throw Exception( "Attempted to initialize an existing wallet using an unknown wallet ID!"); } await _prefs.init(); - final data = - DB.instance.get(boxName: walletId, key: "latest_tx_model") - as TransactionData?; - if (data != null) { - _transactionData = Future(() => data); - } + // await _checkCurrentChangeAddressesForTransactions(); + // await _checkCurrentReceivingAddressesForTransactions(); } - @override - Future get transactionData => - _transactionData ??= _fetchTransactionData(); - Future? _transactionData; - - TransactionData? cachedTxData; - // hack to add tx to txData before refresh completes // required based on current app architecture where we don't properly store // transactions locally in a good way @override Future updateSentCachedTxData(Map txData) async { - final priceData = - await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final locale = Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; - final String worthNow = Format.localizedStringAsFixed( - value: - ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2), - decimalPlaces: 2, - locale: locale!); - - final tx = models.Transaction( + final transaction = isar_models.Transaction( + walletId: walletId, txid: txData["txid"] as String, - confirmedStatus: false, timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, - txType: "Sent", - amount: txData["recipientAmt"] as int, - worthNow: worthNow, - worthAtBlockTimestamp: worthNow, - fees: txData["fee"] as int, - inputSize: 0, - outputSize: 0, + type: isar_models.TransactionType.outgoing, + subType: isar_models.TransactionSubType.none, + // precision may be lost here hence the following amountString + amount: (txData["recipientAmt"] as Amount).raw.toInt(), + amountString: (txData["recipientAmt"] as Amount).toJsonString(), + fee: txData["fee"] as int, + height: null, + isCancelled: false, + isLelantus: false, + otherData: null, + slateId: null, + nonce: null, inputs: [], outputs: [], - address: txData["address"] as String, - height: -1, - confirmations: 0, ); - if (cachedTxData == null) { - final data = await _fetchTransactionData(); - _transactionData = Future(() => data); - } + final address = txData["address"] is String + ? await db.getAddress(walletId, txData["address"] as String) + : null; - final transactions = cachedTxData!.getAllTransactions(); - transactions[tx.txid] = tx; - cachedTxData = models.TransactionData.fromMap(transactions); - _transactionData = Future(() => cachedTxData!); + await db.addNewTransactionData( + [ + Tuple2(transaction, address), + ], + walletId, + ); } @override @@ -1332,7 +1281,7 @@ class NamecoinWallet extends CoinServiceAPI { @override String get walletId => _walletId; - late String _walletId; + late final String _walletId; @override String get walletName => _walletName; @@ -1352,29 +1301,6 @@ class NamecoinWallet extends CoinServiceAPI { late SecureStorageInterface _secureStore; - late PriceAPI _priceAPI; - - NamecoinWallet({ - required String walletId, - required String walletName, - required Coin coin, - required ElectrumX client, - required CachedElectrumX cachedClient, - required TransactionNotificationTracker tracker, - PriceAPI? priceAPI, - required SecureStorageInterface secureStore, - }) { - txTracker = tracker; - _walletId = walletId; - _walletName = walletName; - _coin = coin; - _electrumXClient = client; - _cachedElectrumXClient = cachedClient; - - _priceAPI = priceAPI ?? PriceAPI(Client()); - _secureStore = secureStore; - } - @override Future updateNode(bool shouldRefresh) async { final failovers = NodeService(secureStorageInterface: _secureStore) @@ -1405,12 +1331,11 @@ class NamecoinWallet extends CoinServiceAPI { } Future> _getMnemonicList() async { - final mnemonicString = - await _secureStore.read(key: '${_walletId}_mnemonic'); - if (mnemonicString == null) { + final _mnemonicString = await mnemonicString; + if (_mnemonicString == null) { return []; } - final List data = mnemonicString.split(' '); + final List data = _mnemonicString.split(' '); return data; } @@ -1428,53 +1353,64 @@ class NamecoinWallet extends CoinServiceAPI { ); } - Future> _fetchAllOwnAddresses() async { - final List allAddresses = []; - final receivingAddresses = DB.instance.get( - boxName: walletId, key: 'receivingAddressesP2WPKH') as List; - final changeAddresses = DB.instance.get( - boxName: walletId, key: 'changeAddressesP2WPKH') as List; - final receivingAddressesP2PKH = DB.instance.get( - boxName: walletId, key: 'receivingAddressesP2PKH') as List; - final changeAddressesP2PKH = - DB.instance.get(boxName: walletId, key: 'changeAddressesP2PKH') - as List; - final receivingAddressesP2SH = DB.instance.get( - boxName: walletId, key: 'receivingAddressesP2SH') as List; - final changeAddressesP2SH = - DB.instance.get(boxName: walletId, key: 'changeAddressesP2SH') - as List; - - for (var i = 0; i < receivingAddresses.length; i++) { - if (!allAddresses.contains(receivingAddresses[i])) { - allAddresses.add(receivingAddresses[i] as String); - } - } - for (var i = 0; i < changeAddresses.length; i++) { - if (!allAddresses.contains(changeAddresses[i])) { - allAddresses.add(changeAddresses[i] as String); - } - } - for (var i = 0; i < receivingAddressesP2PKH.length; i++) { - if (!allAddresses.contains(receivingAddressesP2PKH[i])) { - allAddresses.add(receivingAddressesP2PKH[i] as String); - } - } - for (var i = 0; i < changeAddressesP2PKH.length; i++) { - if (!allAddresses.contains(changeAddressesP2PKH[i])) { - allAddresses.add(changeAddressesP2PKH[i] as String); - } - } - for (var i = 0; i < receivingAddressesP2SH.length; i++) { - if (!allAddresses.contains(receivingAddressesP2SH[i])) { - allAddresses.add(receivingAddressesP2SH[i] as String); - } - } - for (var i = 0; i < changeAddressesP2SH.length; i++) { - if (!allAddresses.contains(changeAddressesP2SH[i])) { - allAddresses.add(changeAddressesP2SH[i] as String); - } - } + Future> _fetchAllOwnAddresses() async { + final allAddresses = await db + .getAddresses(walletId) + .filter() + .not() + .typeEqualTo(isar_models.AddressType.nonWallet) + .and() + .group((q) => q + .subTypeEqualTo(isar_models.AddressSubType.receiving) + .or() + .subTypeEqualTo(isar_models.AddressSubType.change)) + .findAll(); + // final List allAddresses = []; + // final receivingAddresses = DB.instance.get( + // boxName: walletId, key: 'receivingAddressesP2WPKH') as List; + // final changeAddresses = DB.instance.get( + // boxName: walletId, key: 'changeAddressesP2WPKH') as List; + // final receivingAddressesP2PKH = DB.instance.get( + // boxName: walletId, key: 'receivingAddressesP2PKH') as List; + // final changeAddressesP2PKH = + // DB.instance.get(boxName: walletId, key: 'changeAddressesP2PKH') + // as List; + // final receivingAddressesP2SH = DB.instance.get( + // boxName: walletId, key: 'receivingAddressesP2SH') as List; + // final changeAddressesP2SH = + // DB.instance.get(boxName: walletId, key: 'changeAddressesP2SH') + // as List; + // + // for (var i = 0; i < receivingAddresses.length; i++) { + // if (!allAddresses.contains(receivingAddresses[i])) { + // allAddresses.add(receivingAddresses[i] as String); + // } + // } + // for (var i = 0; i < changeAddresses.length; i++) { + // if (!allAddresses.contains(changeAddresses[i])) { + // allAddresses.add(changeAddresses[i] as String); + // } + // } + // for (var i = 0; i < receivingAddressesP2PKH.length; i++) { + // if (!allAddresses.contains(receivingAddressesP2PKH[i])) { + // allAddresses.add(receivingAddressesP2PKH[i] as String); + // } + // } + // for (var i = 0; i < changeAddressesP2PKH.length; i++) { + // if (!allAddresses.contains(changeAddressesP2PKH[i])) { + // allAddresses.add(changeAddressesP2PKH[i] as String); + // } + // } + // for (var i = 0; i < receivingAddressesP2SH.length; i++) { + // if (!allAddresses.contains(receivingAddressesP2SH[i])) { + // allAddresses.add(receivingAddressesP2SH[i] as String); + // } + // } + // for (var i = 0; i < changeAddressesP2SH.length; i++) { + // if (!allAddresses.contains(changeAddressesP2SH[i])) { + // allAddresses.add(changeAddressesP2SH[i] as String); + // } + // } return allAddresses; } @@ -1491,9 +1427,18 @@ class NamecoinWallet extends CoinServiceAPI { numberOfBlocksFast: f, numberOfBlocksAverage: m, numberOfBlocksSlow: s, - fast: Format.decimalAmountToSatoshis(fast, coin), - medium: Format.decimalAmountToSatoshis(medium, coin), - slow: Format.decimalAmountToSatoshis(slow, coin), + fast: Amount.fromDecimal( + fast, + fractionDigits: coin.decimals, + ).raw.toInt(), + medium: Amount.fromDecimal( + medium, + fractionDigits: coin.decimals, + ).raw.toInt(), + slow: Amount.fromDecimal( + slow, + fractionDigits: coin.decimals, + ).raw.toInt(), ); Logging.instance.log("fetched fees: $feeObject", level: LogLevel.Info); @@ -1509,138 +1454,53 @@ class NamecoinWallet extends CoinServiceAPI { Logging.instance .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); if (!integrationTestFlag) { - final features = await electrumXClient.getServerFeatures(); - Logging.instance.log("features: $features", level: LogLevel.Info); - switch (coin) { - case Coin.namecoin: - if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { - throw Exception("genesis hash does not match main net!"); - } - break; - default: - throw Exception( - "Attempted to generate a NamecoinWallet using a non namecoin coin type: ${coin.name}"); + try { + final features = await electrumXClient.getServerFeatures(); + Logging.instance.log("features: $features", level: LogLevel.Info); + switch (coin) { + case Coin.namecoin: + if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { + throw Exception("genesis hash does not match main net!"); + } + break; + default: + throw Exception( + "Attempted to generate a NamecoinWallet using a non namecoin coin type: ${coin.name}"); + } + } catch (e, s) { + Logging.instance.log("$e/n$s", level: LogLevel.Info); } } // this should never fail - if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) { + if ((await mnemonicString) != null || (await mnemonicPassphrase) != null) { throw Exception( "Attempted to overwrite mnemonic on generate new wallet!"); } await _secureStore.write( key: '${_walletId}_mnemonic', value: bip39.generateMnemonic(strength: 256)); - - // Set relevant indexes - await DB.instance - .put(boxName: walletId, key: "receivingIndexP2WPKH", value: 0); - await DB.instance - .put(boxName: walletId, key: "changeIndexP2WPKH", value: 0); - await DB.instance - .put(boxName: walletId, key: "receivingIndexP2PKH", value: 0); - await DB.instance - .put(boxName: walletId, key: "changeIndexP2PKH", value: 0); - await DB.instance - .put(boxName: walletId, key: "receivingIndexP2SH", value: 0); - await DB.instance - .put(boxName: walletId, key: "changeIndexP2SH", value: 0); - await DB.instance.put( - boxName: walletId, - key: 'blocked_tx_hashes', - value: ["0xdefault"], - ); // A list of transaction hashes to represent frozen utxos in wallet - // initialize address book entries - await DB.instance.put( - boxName: walletId, - key: 'addressBookEntries', - value: {}); + await _secureStore.write( + key: '${_walletId}_mnemonicPassphrase', + value: "", + ); // Generate and add addresses to relevant arrays - await Future.wait([ + final initialAddresses = await Future.wait([ // P2WPKH - _generateAddressForChain(0, 0, DerivePathType.bip84).then( - (initialReceivingAddressP2WPKH) { - _addToAddressesArrayForChain( - initialReceivingAddressP2WPKH, 0, DerivePathType.bip84); - _currentReceivingAddress = - Future(() => initialReceivingAddressP2WPKH); - }, - ), - _generateAddressForChain(1, 0, DerivePathType.bip84).then( - (initialChangeAddressP2WPKH) => _addToAddressesArrayForChain( - initialChangeAddressP2WPKH, - 1, - DerivePathType.bip84, - ), - ), + _generateAddressForChain(0, 0, DerivePathType.bip84), + _generateAddressForChain(1, 0, DerivePathType.bip84), // P2PKH - _generateAddressForChain(0, 0, DerivePathType.bip44).then( - (initialReceivingAddressP2PKH) { - _addToAddressesArrayForChain( - initialReceivingAddressP2PKH, 0, DerivePathType.bip44); - _currentReceivingAddressP2PKH = - Future(() => initialReceivingAddressP2PKH); - }, - ), - _generateAddressForChain(1, 0, DerivePathType.bip44).then( - (initialChangeAddressP2PKH) => _addToAddressesArrayForChain( - initialChangeAddressP2PKH, - 1, - DerivePathType.bip44, - ), - ), + _generateAddressForChain(0, 0, DerivePathType.bip44), + _generateAddressForChain(1, 0, DerivePathType.bip44), // P2SH - _generateAddressForChain(0, 0, DerivePathType.bip49).then( - (initialReceivingAddressP2SH) { - _addToAddressesArrayForChain( - initialReceivingAddressP2SH, 0, DerivePathType.bip49); - _currentReceivingAddressP2SH = - Future(() => initialReceivingAddressP2SH); - }, - ), - _generateAddressForChain(1, 0, DerivePathType.bip49).then( - (initialChangeAddressP2SH) => _addToAddressesArrayForChain( - initialChangeAddressP2SH, - 1, - DerivePathType.bip49, - ), - ), + _generateAddressForChain(0, 0, DerivePathType.bip49), + _generateAddressForChain(1, 0, DerivePathType.bip49), ]); - // // P2PKH - // _generateAddressForChain(0, 0, DerivePathType.bip44).then( - // (initialReceivingAddressP2PKH) { - // _addToAddressesArrayForChain( - // initialReceivingAddressP2PKH, 0, DerivePathType.bip44); - // this._currentReceivingAddressP2PKH = - // Future(() => initialReceivingAddressP2PKH); - // }, - // ); - // _generateAddressForChain(1, 0, DerivePathType.bip44) - // .then((initialChangeAddressP2PKH) => _addToAddressesArrayForChain( - // initialChangeAddressP2PKH, - // 1, - // DerivePathType.bip44, - // )); - // - // // P2SH - // _generateAddressForChain(0, 0, DerivePathType.bip49).then( - // (initialReceivingAddressP2SH) { - // _addToAddressesArrayForChain( - // initialReceivingAddressP2SH, 0, DerivePathType.bip49); - // this._currentReceivingAddressP2SH = - // Future(() => initialReceivingAddressP2SH); - // }, - // ); - // _generateAddressForChain(1, 0, DerivePathType.bip49) - // .then((initialChangeAddressP2SH) => _addToAddressesArrayForChain( - // initialChangeAddressP2SH, - // 1, - // DerivePathType.bip49, - // )); + await db.putAddresses(initialAddresses); Logging.instance.log("_generateNewWalletFinished", level: LogLevel.Info); } @@ -1648,28 +1508,40 @@ class NamecoinWallet extends CoinServiceAPI { /// Generates a new internal or external chain address for the wallet using a BIP84, BIP44, or BIP49 derivation path. /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! /// [index] - This can be any integer >= 0 - Future _generateAddressForChain( + Future _generateAddressForChain( int chain, int index, DerivePathType derivePathType, ) async { - final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic'); - final node = await compute( - getBip32NodeWrapper, - Tuple5( - chain, - index, - mnemonic!, - _network, - derivePathType, - ), + final _mnemonic = await mnemonicString; + final _mnemonicPassphrase = await mnemonicPassphrase; + if (_mnemonicPassphrase == null) { + Logging.instance.log( + "Exception in _generateAddressForChain: mnemonic passphrase null, possible migration issue; if using internal builds, delete wallet and restore from seed, if using a release build, please file bug report", + level: LogLevel.Error); + } + + final derivePath = constructDerivePath( + derivePathType: derivePathType, + networkWIF: _network.wif, + chain: chain, + index: index, ); + final node = await Bip32Utils.getBip32Node( + _mnemonic!, + _mnemonicPassphrase!, + _network, + derivePath, + ); + final data = PaymentData(pubkey: node.publicKey); String address; + isar_models.AddressType addrType; switch (derivePathType) { case DerivePathType.bip44: address = P2PKH(data: data, network: _network).data.address!; + addrType = isar_models.AddressType.p2pkh; break; case DerivePathType.bip49: address = P2SH( @@ -1682,13 +1554,17 @@ class NamecoinWallet extends CoinServiceAPI { network: _network) .data .address!; + addrType = isar_models.AddressType.p2sh; break; case DerivePathType.bip84: address = P2WPKH( network: _network, data: data, overridePrefix: namecoin.bech32!) .data .address!; + addrType = isar_models.AddressType.p2wpkh; break; + default: + throw Exception("DerivePathType must not be null."); } // add generated address & info to derivations @@ -1700,98 +1576,54 @@ class NamecoinWallet extends CoinServiceAPI { derivePathType: derivePathType, ); - return address; - } - - /// Increases the index for either the internal or external chain, depending on [chain]. - /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! - Future _incrementAddressIndexForChain( - int chain, DerivePathType derivePathType) async { - // Here we assume chain == 1 if it isn't 0 - String indexKey = chain == 0 ? "receivingIndex" : "changeIndex"; - switch (derivePathType) { - case DerivePathType.bip44: - indexKey += "P2PKH"; - break; - case DerivePathType.bip49: - indexKey += "P2SH"; - break; - case DerivePathType.bip84: - indexKey += "P2WPKH"; - break; - } - - final newIndex = - (DB.instance.get(boxName: walletId, key: indexKey)) + 1; - await DB.instance - .put(boxName: walletId, key: indexKey, value: newIndex); - } - - /// Adds [address] to the relevant chain's address array, which is determined by [chain]. - /// [address] - Expects a standard native segwit address - /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! - Future _addToAddressesArrayForChain( - String address, int chain, DerivePathType derivePathType) async { - String chainArray = ''; - if (chain == 0) { - chainArray = 'receivingAddresses'; - } else { - chainArray = 'changeAddresses'; - } - switch (derivePathType) { - case DerivePathType.bip44: - chainArray += "P2PKH"; - break; - case DerivePathType.bip49: - chainArray += "P2SH"; - break; - case DerivePathType.bip84: - chainArray += "P2WPKH"; - break; - } - - final addressArray = - DB.instance.get(boxName: walletId, key: chainArray); - if (addressArray == null) { - Logging.instance.log( - 'Attempting to add the following to $chainArray array for chain $chain:${[ - address - ]}', - level: LogLevel.Info); - await DB.instance - .put(boxName: walletId, key: chainArray, value: [address]); - } else { - // Make a deep copy of the existing list - final List newArray = []; - addressArray - .forEach((dynamic _address) => newArray.add(_address as String)); - newArray.add(address); // Add the address passed into the method - await DB.instance - .put(boxName: walletId, key: chainArray, value: newArray); - } + return isar_models.Address( + walletId: walletId, + derivationIndex: index, + derivationPath: isar_models.DerivationPath()..value = derivePath, + value: address, + publicKey: node.publicKey, + type: addrType, + subType: chain == 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change, + ); } /// Returns the latest receiving/change (external/internal) address for the wallet depending on [chain] /// and /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! Future _getCurrentAddressForChain( - int chain, DerivePathType derivePathType) async { - // Here, we assume that chain == 1 if it isn't 0 - String arrayKey = chain == 0 ? "receivingAddresses" : "changeAddresses"; + int chain, + DerivePathType derivePathType, + ) async { + final subType = chain == 0 // Here, we assume that chain == 1 if it isn't 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change; + + isar_models.AddressType type; + isar_models.Address? address; switch (derivePathType) { case DerivePathType.bip44: - arrayKey += "P2PKH"; + type = isar_models.AddressType.p2pkh; break; case DerivePathType.bip49: - arrayKey += "P2SH"; + type = isar_models.AddressType.p2sh; break; case DerivePathType.bip84: - arrayKey += "P2WPKH"; + type = isar_models.AddressType.p2wpkh; break; + default: + throw Exception( + "DerivePathType null or unsupported (${DerivePathType.bip44})"); } - final internalChainArray = - DB.instance.get(boxName: walletId, key: arrayKey); - return internalChainArray.last as String; + address = await db + .getAddresses(walletId) + .filter() + .typeEqualTo(type) + .subTypeEqualTo(subType) + .sortByDerivationIndexDesc() + .findFirst(); + return address!.value; } String _buildDerivationStorageKey({ @@ -1810,6 +1642,8 @@ class NamecoinWallet extends CoinServiceAPI { case DerivePathType.bip84: key = "${walletId}_${chainId}DerivationsP2WPKH"; break; + default: + throw Exception("DerivePathType $derivePathType not supported"); } return key; } @@ -1896,8 +1730,8 @@ class NamecoinWallet extends CoinServiceAPI { await _secureStore.write(key: key, value: newReceiveDerivationsString); } - Future _fetchUtxoData() async { - final List allAddresses = await _fetchAllOwnAddresses(); + Future _updateUTXOs() async { + final allAddresses = await _fetchAllOwnAddresses(); try { final fetchedUtxoList = >>[]; @@ -1909,9 +1743,10 @@ class NamecoinWallet extends CoinServiceAPI { if (batches[batchNumber] == null) { batches[batchNumber] = {}; } - final scripthash = _convertToScriptHash(allAddresses[i], _network); + final scripthash = + _convertToScriptHash(allAddresses[i].value, _network); - print("SCRIPT_HASH_FOR_ADDRESS ${allAddresses[i]} IS $scripthash"); + // print("SCRIPT_HASH_FOR_ADDRESS ${allAddresses[i]} IS $scripthash"); batches[batchNumber]!.addAll({ scripthash: [scripthash] }); @@ -1929,143 +1764,112 @@ class NamecoinWallet extends CoinServiceAPI { } } } - final priceData = - await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final List> outputArray = []; - int satoshiBalance = 0; - int satoshiBalancePending = 0; + + final List outputArray = []; for (int i = 0; i < fetchedUtxoList.length; i++) { for (int j = 0; j < fetchedUtxoList[i].length; j++) { - int value = fetchedUtxoList[i][j]["value"] as int; - satoshiBalance += value; + final jsonUTXO = fetchedUtxoList[i][j]; final txn = await cachedElectrumXClient.getTransaction( - txHash: fetchedUtxoList[i][j]["tx_hash"] as String, + txHash: jsonUTXO["tx_hash"] as String, verbose: true, coin: coin, ); - final Map utxo = {}; - final int confirmations = txn["confirmations"] as int? ?? 0; - final bool confirmed = confirmations >= MINIMUM_CONFIRMATIONS; - if (!confirmed) { - satoshiBalancePending += value; + final vout = jsonUTXO["tx_pos"] as int; + + final outputs = txn["vout"] as List; + + String? utxoOwnerAddress; + // get UTXO owner address + for (final output in outputs) { + if (output["n"] == vout) { + utxoOwnerAddress = + output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]?["address"] as String?; + } } - utxo["txid"] = txn["txid"]; - utxo["vout"] = fetchedUtxoList[i][j]["tx_pos"]; - utxo["value"] = value; + final utxo = isar_models.UTXO( + walletId: walletId, + txid: txn["txid"] as String, + vout: vout, + value: jsonUTXO["value"] as int, + name: "", + isBlocked: false, + blockedReason: null, + isCoinbase: txn["is_coinbase"] as bool? ?? false, + blockHash: txn["blockhash"] as String?, + blockHeight: jsonUTXO["height"] as int?, + blockTime: txn["blocktime"] as int?, + address: utxoOwnerAddress, + ); - utxo["status"] = {}; - utxo["status"]["confirmed"] = confirmed; - utxo["status"]["confirmations"] = confirmations; - utxo["status"]["block_height"] = fetchedUtxoList[i][j]["height"]; - utxo["status"]["block_hash"] = txn["blockhash"]; - utxo["status"]["block_time"] = txn["blocktime"]; - - final fiatValue = ((Decimal.fromInt(value) * currentPrice) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2); - utxo["rawWorth"] = fiatValue; - utxo["fiatWorth"] = fiatValue.toString(); outputArray.add(utxo); } } - Decimal currencyBalanceRaw = - ((Decimal.fromInt(satoshiBalance) * currentPrice) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2); - - final Map result = { - "total_user_currency": currencyBalanceRaw.toString(), - "total_sats": satoshiBalance, - "total_btc": (Decimal.fromInt(satoshiBalance) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal( - scaleOnInfinitePrecision: Constants.decimalPlacesForCoin(coin)) - .toString(), - "outputArray": outputArray, - "unconfirmed": satoshiBalancePending, - }; - - final dataModel = UtxoData.fromJson(result); - - final List allOutputs = dataModel.unspentOutputArray; Logging.instance - .log('Outputs fetched: $allOutputs', level: LogLevel.Info); - await _sortOutputs(allOutputs); - await DB.instance.put( - boxName: walletId, key: 'latest_utxo_model', value: dataModel); - await DB.instance.put( - boxName: walletId, - key: 'totalBalance', - value: dataModel.satoshiBalance); - return dataModel; + .log('Outputs fetched: $outputArray', level: LogLevel.Info); + + await db.updateUTXOs(walletId, outputArray); + + // finally update balance + await _updateBalance(); } catch (e, s) { Logging.instance .log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error); - final latestTxModel = - DB.instance.get(boxName: walletId, key: 'latest_utxo_model') - as models.UtxoData?; - - if (latestTxModel == null) { - final emptyModel = { - "total_user_currency": "0.00", - "total_sats": 0, - "total_btc": "0", - "outputArray": [] - }; - return UtxoData.fromJson(emptyModel); - } else { - Logging.instance - .log("Old output model located", level: LogLevel.Warning); - return latestTxModel; - } } } - /// Takes in a list of UtxoObjects and adds a name (dependent on object index within list) - /// and checks for the txid associated with the utxo being blocked and marks it accordingly. - /// Now also checks for output labeling. - Future _sortOutputs(List utxos) async { - final blockedHashArray = - DB.instance.get(boxName: walletId, key: 'blocked_tx_hashes') - as List?; - final List lst = []; - if (blockedHashArray != null) { - for (var hash in blockedHashArray) { - lst.add(hash as String); - } - } - final labels = - DB.instance.get(boxName: walletId, key: 'labels') as Map? ?? - {}; - - outputsList = []; - - for (var i = 0; i < utxos.length; i++) { - if (labels[utxos[i].txid] != null) { - utxos[i].txName = labels[utxos[i].txid] as String? ?? ""; - } else { - utxos[i].txName = 'Output #$i'; - } - - if (utxos[i].status.confirmed == false) { - outputsList.add(utxos[i]); - } else { - if (lst.contains(utxos[i].txid)) { - utxos[i].blocked = true; - outputsList.add(utxos[i]); - } else if (!lst.contains(utxos[i].txid)) { - outputsList.add(utxos[i]); - } - } - } + Future _updateBalance() async { + await refreshBalance(); } + @override + Balance get balance => _balance ??= getCachedBalance(); + Balance? _balance; + + // /// Takes in a list of UtxoObjects and adds a name (dependent on object index within list) + // /// and checks for the txid associated with the utxo being blocked and marks it accordingly. + // /// Now also checks for output labeling. + // Future _sortOutputs(List utxos) async { + // final blockedHashArray = + // DB.instance.get(boxName: walletId, key: 'blocked_tx_hashes') + // as List?; + // final List lst = []; + // if (blockedHashArray != null) { + // for (var hash in blockedHashArray) { + // lst.add(hash as String); + // } + // } + // final labels = + // DB.instance.get(boxName: walletId, key: 'labels') as Map? ?? + // {}; + // + // outputsList = []; + // + // for (var i = 0; i < utxos.length; i++) { + // if (labels[utxos[i].txid] != null) { + // utxos[i].txName = labels[utxos[i].txid] as String? ?? ""; + // } else { + // utxos[i].txName = 'Output #$i'; + // } + // + // if (utxos[i].status.confirmed == false) { + // outputsList.add(utxos[i]); + // } else { + // if (lst.contains(utxos[i].txid)) { + // utxos[i].blocked = true; + // outputsList.add(utxos[i]); + // } else if (!lst.contains(utxos[i].txid)) { + // outputsList.add(utxos[i]); + // } + // } + // } + // } + Future getTxCount({required String address}) async { String? scripthash; try { @@ -2086,13 +1890,13 @@ class NamecoinWallet extends CoinServiceAPI { }) async { try { final Map> args = {}; - print("Address $addresses"); + // print("Address $addresses"); for (final entry in addresses.entries) { args[entry.key] = [_convertToScriptHash(entry.value, _network)]; } - print("Args ${jsonEncode(args)}"); + // print("Args ${jsonEncode(args)}"); final response = await electrumXClient.getBatchHistory(args: args); - print("Response ${jsonEncode(response)}"); + // print("Response ${jsonEncode(response)}"); final Map result = {}; for (final entry in response.entries) { result[entry.key] = entry.value.length; @@ -2107,111 +1911,86 @@ class NamecoinWallet extends CoinServiceAPI { } } - Future _checkReceivingAddressForTransactions( - DerivePathType derivePathType) async { + Future _checkReceivingAddressForTransactions() async { try { - final String currentExternalAddr = - await _getCurrentAddressForChain(0, derivePathType); - final int txCount = await getTxCount(address: currentExternalAddr); + final currentReceiving = await _currentReceivingAddress; + + final int txCount = await getTxCount(address: currentReceiving.value); Logging.instance.log( - 'Number of txs for current receiving address $currentExternalAddr: $txCount', + 'Number of txs for current receiving address $currentReceiving: $txCount', level: LogLevel.Info); - if (txCount >= 1) { + if (txCount >= 1 || currentReceiving.derivationIndex < 0) { // First increment the receiving index - await _incrementAddressIndexForChain(0, derivePathType); - - // Check the new receiving index - String indexKey = "receivingIndex"; - switch (derivePathType) { - case DerivePathType.bip44: - indexKey += "P2PKH"; - break; - case DerivePathType.bip49: - indexKey += "P2SH"; - break; - case DerivePathType.bip84: - indexKey += "P2WPKH"; - break; - } - final newReceivingIndex = - DB.instance.get(boxName: walletId, key: indexKey) as int; + final newReceivingIndex = currentReceiving.derivationIndex + 1; // Use new index to derive a new receiving address final newReceivingAddress = await _generateAddressForChain( - 0, newReceivingIndex, derivePathType); + 0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin)); - // Add that new receiving address to the array of receiving addresses - await _addToAddressesArrayForChain( - newReceivingAddress, 0, derivePathType); - - // Set the new receiving address that the service - - switch (derivePathType) { - case DerivePathType.bip44: - _currentReceivingAddressP2PKH = Future(() => newReceivingAddress); - break; - case DerivePathType.bip49: - _currentReceivingAddressP2SH = Future(() => newReceivingAddress); - break; - case DerivePathType.bip84: - _currentReceivingAddress = Future(() => newReceivingAddress); - break; + final existing = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(newReceivingAddress.value) + .findFirst(); + if (existing == null) { + // Add that new change address + await db.putAddress(newReceivingAddress); + } else { + // we need to update the address + await db.updateAddress(existing, newReceivingAddress); } + // keep checking until address with no tx history is set as current + await _checkReceivingAddressForTransactions(); } } catch (e, s) { Logging.instance.log( - "Exception rethrown from _checkReceivingAddressForTransactions($derivePathType): $e\n$s", + "Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s", level: LogLevel.Error); rethrow; } } - Future _checkChangeAddressForTransactions( - DerivePathType derivePathType) async { + Future _checkChangeAddressForTransactions() async { try { - final String currentExternalAddr = - await _getCurrentAddressForChain(1, derivePathType); - final int txCount = await getTxCount(address: currentExternalAddr); + final currentChange = await _currentChangeAddress; + final int txCount = await getTxCount(address: currentChange.value); Logging.instance.log( - 'Number of txs for current change address $currentExternalAddr: $txCount', + 'Number of txs for current change address $currentChange: $txCount', level: LogLevel.Info); - if (txCount >= 1) { + if (txCount >= 1 || currentChange.derivationIndex < 0) { // First increment the change index - await _incrementAddressIndexForChain(1, derivePathType); - - // Check the new change index - String indexKey = "changeIndex"; - switch (derivePathType) { - case DerivePathType.bip44: - indexKey += "P2PKH"; - break; - case DerivePathType.bip49: - indexKey += "P2SH"; - break; - case DerivePathType.bip84: - indexKey += "P2WPKH"; - break; - } - final newChangeIndex = - DB.instance.get(boxName: walletId, key: indexKey) as int; + final newChangeIndex = currentChange.derivationIndex + 1; // Use new index to derive a new change address - final newChangeAddress = - await _generateAddressForChain(1, newChangeIndex, derivePathType); + final newChangeAddress = await _generateAddressForChain( + 1, newChangeIndex, DerivePathTypeExt.primaryFor(coin)); - // Add that new receiving address to the array of change addresses - await _addToAddressesArrayForChain(newChangeAddress, 1, derivePathType); + final existing = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(newChangeAddress.value) + .findFirst(); + if (existing == null) { + // Add that new change address + await db.putAddress(newChangeAddress); + } else { + // we need to update the address + await db.updateAddress(existing, newChangeAddress); + } + + // keep checking until address with no tx history is set as current + await _checkChangeAddressForTransactions(); } } on SocketException catch (se, s) { Logging.instance.log( - "SocketException caught in _checkReceivingAddressForTransactions($derivePathType): $se\n$s", + "SocketException caught in _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $se\n$s", level: LogLevel.Error); return; } catch (e, s) { Logging.instance.log( - "Exception rethrown from _checkReceivingAddressForTransactions($derivePathType): $e\n$s", + "Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s", level: LogLevel.Error); rethrow; } @@ -2219,9 +1998,9 @@ class NamecoinWallet extends CoinServiceAPI { Future _checkCurrentReceivingAddressesForTransactions() async { try { - for (final type in DerivePathType.values) { - await _checkReceivingAddressForTransactions(type); - } + // for (final type in DerivePathType.values) { + await _checkReceivingAddressForTransactions(); + // } } catch (e, s) { Logging.instance.log( "Exception rethrown from _checkCurrentReceivingAddressesForTransactions(): $e\n$s", @@ -2243,9 +2022,9 @@ class NamecoinWallet extends CoinServiceAPI { Future _checkCurrentChangeAddressesForTransactions() async { try { - for (final type in DerivePathType.values) { - await _checkChangeAddressForTransactions(type); - } + // for (final type in DerivePathType.values) { + await _checkChangeAddressForTransactions(); + // } } catch (e, s) { Logging.instance.log( "Exception rethrown from _checkCurrentChangeAddressesForTransactions(): $e\n$s", @@ -2379,52 +2158,12 @@ class NamecoinWallet extends CoinServiceAPI { return allTransactions; } - Future _fetchTransactionData() async { - final List allAddresses = await _fetchAllOwnAddresses(); - - final changeAddresses = DB.instance.get( - boxName: walletId, key: 'changeAddressesP2WPKH') as List; - final changeAddressesP2PKH = - DB.instance.get(boxName: walletId, key: 'changeAddressesP2PKH') - as List; - final changeAddressesP2SH = - DB.instance.get(boxName: walletId, key: 'changeAddressesP2SH') - as List; - - for (var i = 0; i < changeAddressesP2PKH.length; i++) { - changeAddresses.add(changeAddressesP2PKH[i] as String); - } - for (var i = 0; i < changeAddressesP2SH.length; i++) { - changeAddresses.add(changeAddressesP2SH[i] as String); - } + Future _refreshTransactions() async { + final List allAddresses = + await _fetchAllOwnAddresses(); final List> allTxHashes = - await _fetchHistory(allAddresses); - - final cachedTransactions = - DB.instance.get(boxName: walletId, key: 'latest_tx_model') - as TransactionData?; - int latestTxnBlockHeight = - DB.instance.get(boxName: walletId, key: "storedTxnDataHeight") - as int? ?? - 0; - - final unconfirmedCachedTransactions = - cachedTransactions?.getAllTransactions() ?? {}; - unconfirmedCachedTransactions - .removeWhere((key, value) => value.confirmedStatus); - - if (cachedTransactions != null) { - for (final tx in allTxHashes.toList(growable: false)) { - final txHeight = tx["height"] as int; - if (txHeight > 0 && - txHeight < latestTxnBlockHeight - MINIMUM_CONFIRMATIONS) { - if (unconfirmedCachedTransactions[tx["tx_hash"] as String] == null) { - allTxHashes.remove(tx); - } - } - } - } + await _fetchHistory(allAddresses.map((e) => e.value).toList()); Set hashes = {}; for (var element in allTxHashes) { @@ -2432,33 +2171,41 @@ class NamecoinWallet extends CoinServiceAPI { } await fastFetch(hashes.toList()); List> allTransactions = []; + final currentHeight = await chainHeight; for (final txHash in allTxHashes) { - final tx = await cachedElectrumXClient.getTransaction( - txHash: txHash["tx_hash"] as String, - verbose: true, - coin: coin, - ); + final storedTx = await db + .getTransactions(walletId) + .filter() + .txidEqualTo(txHash["tx_hash"] as String) + .findFirst(); - // Logging.instance.log("TRANSACTION: ${jsonEncode(tx)}"); - // TODO fix this for sent to self transactions? - if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { - tx["address"] = txHash["address"]; - tx["height"] = txHash["height"]; - allTransactions.add(tx); + if (storedTx == null || + !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)) { + final tx = await cachedElectrumXClient.getTransaction( + txHash: txHash["tx_hash"] as String, + verbose: true, + coin: coin, + ); + + // Logging.instance.log("TRANSACTION: ${jsonEncode(tx)}"); + if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { + tx["address"] = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(txHash["address"] as String) + .findFirst(); + tx["height"] = txHash["height"]; + allTransactions.add(tx); + } } } - Logging.instance.log("addAddresses: $allAddresses", level: LogLevel.Info); - Logging.instance.log("allTxHashes: $allTxHashes", level: LogLevel.Info); - - Logging.instance.log("allTransactions length: ${allTransactions.length}", - level: LogLevel.Info); - - final priceData = - await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final List> midSortedArray = []; + // Logging.instance.log("addAddresses: $allAddresses", level: LogLevel.Info); + // Logging.instance.log("allTxHashes: $allTxHashes", level: LogLevel.Info); + // + // Logging.instance.log("allTransactions length: ${allTransactions.length}", + // level: LogLevel.Info); Set vHashes = {}; for (final txObject in allTransactions) { @@ -2470,261 +2217,33 @@ class NamecoinWallet extends CoinServiceAPI { } await fastFetch(vHashes.toList()); + final List> txnsData = + []; + for (final txObject in allTransactions) { - List sendersArray = []; - List recipientsArray = []; + final data = await parseTransaction( + txObject, + cachedElectrumXClient, + allAddresses, + coin, + MINIMUM_CONFIRMATIONS, + walletId, + ); - // Usually only has value when txType = 'Send' - int inputAmtSentFromWallet = 0; - // Usually has value regardless of txType due to change addresses - int outputAmtAddressedToWallet = 0; - int fee = 0; - - Map midSortedTx = {}; - - for (int i = 0; i < (txObject["vin"] as List).length; i++) { - final input = txObject["vin"]![i] as Map; - final prevTxid = input["txid"] as String; - final prevOut = input["vout"] as int; - - final tx = await _cachedElectrumXClient.getTransaction( - txHash: prevTxid, - coin: coin, - ); - - for (final out in tx["vout"] as List) { - if (prevOut == out["n"]) { - String? address = out["scriptPubKey"]["address"] as String?; - if (address == null && out["scriptPubKey"]["address"] != null) { - address = out["scriptPubKey"]["address"] as String?; - } - - if (address != null) { - sendersArray.add(address); - } - } - } - } - - Logging.instance.log("sendersArray: $sendersArray", level: LogLevel.Info); - - for (final output in txObject["vout"] as List) { - String? address = output["scriptPubKey"]["address"] as String?; - if (address == null && output["scriptPubKey"]["address"] != null) { - address = output["scriptPubKey"]["address"] as String?; - } - if (address != null) { - recipientsArray.add(address); - } - } - - Logging.instance - .log("recipientsArray: $recipientsArray", level: LogLevel.Info); - - final foundInSenders = - allAddresses.any((element) => sendersArray.contains(element)); - Logging.instance - .log("foundInSenders: $foundInSenders", level: LogLevel.Info); - - // If txType = Sent, then calculate inputAmtSentFromWallet - if (foundInSenders) { - int totalInput = 0; - for (int i = 0; i < (txObject["vin"] as List).length; i++) { - final input = txObject["vin"]![i] as Map; - final prevTxid = input["txid"] as String; - final prevOut = input["vout"] as int; - final tx = await _cachedElectrumXClient.getTransaction( - txHash: prevTxid, - coin: coin, - ); - - for (final out in tx["vout"] as List) { - if (prevOut == out["n"]) { - inputAmtSentFromWallet += - (Decimal.parse(out["value"]!.toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - } - } - } - totalInput = inputAmtSentFromWallet; - int totalOutput = 0; - - for (final output in txObject["vout"] as List) { - Logging.instance.log(output, level: LogLevel.Info); - final address = output["scriptPubKey"]["address"]; - final value = output["value"]; - final _value = (Decimal.parse(value.toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - totalOutput += _value; - if (changeAddresses.contains(address)) { - inputAmtSentFromWallet -= _value; - } else { - // change address from 'sent from' to the 'sent to' address - txObject["address"] = address; - } - } - // calculate transaction fee - fee = totalInput - totalOutput; - // subtract fee from sent to calculate correct value of sent tx - inputAmtSentFromWallet -= fee; - } else { - // counters for fee calculation - int totalOut = 0; - int totalIn = 0; - - // add up received tx value - for (final output in txObject["vout"] as List) { - String? address = output["scriptPubKey"]["address"] as String?; - if (address == null && output["scriptPubKey"]["address"] != null) { - address = output["scriptPubKey"]["address"] as String?; - } - if (address != null) { - final value = (Decimal.parse(output["value"].toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - totalOut += value; - if (allAddresses.contains(address)) { - outputAmtAddressedToWallet += value; - } - } - } - - // calculate fee for received tx - for (int i = 0; i < (txObject["vin"] as List).length; i++) { - final input = txObject["vin"][i] as Map; - final prevTxid = input["txid"] as String; - final prevOut = input["vout"] as int; - final tx = await _cachedElectrumXClient.getTransaction( - txHash: prevTxid, - coin: coin, - ); - - for (final out in tx["vout"] as List) { - if (prevOut == out["n"]) { - totalIn += (Decimal.parse(out["value"].toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - } - } - } - fee = totalIn - totalOut; - } - - // create final tx map - midSortedTx["txid"] = txObject["txid"]; - midSortedTx["confirmed_status"] = (txObject["confirmations"] != null) && - (txObject["confirmations"] as int >= MINIMUM_CONFIRMATIONS); - midSortedTx["confirmations"] = txObject["confirmations"] ?? 0; - midSortedTx["timestamp"] = txObject["blocktime"] ?? - (DateTime.now().millisecondsSinceEpoch ~/ 1000); - - if (foundInSenders) { - midSortedTx["txType"] = "Sent"; - midSortedTx["amount"] = inputAmtSentFromWallet; - final String worthNow = - ((currentPrice * Decimal.fromInt(inputAmtSentFromWallet)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2) - .toStringAsFixed(2); - midSortedTx["worthNow"] = worthNow; - midSortedTx["worthAtBlockTimestamp"] = worthNow; - } else { - midSortedTx["txType"] = "Received"; - midSortedTx["amount"] = outputAmtAddressedToWallet; - final worthNow = - ((currentPrice * Decimal.fromInt(outputAmtAddressedToWallet)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2) - .toStringAsFixed(2); - midSortedTx["worthNow"] = worthNow; - } - midSortedTx["aliens"] = []; - midSortedTx["fees"] = fee; - midSortedTx["address"] = txObject["address"]; - midSortedTx["inputSize"] = txObject["vin"].length; - midSortedTx["outputSize"] = txObject["vout"].length; - midSortedTx["inputs"] = txObject["vin"]; - midSortedTx["outputs"] = txObject["vout"]; - - final int height = txObject["height"] as int; - midSortedTx["height"] = height; - - if (height >= latestTxnBlockHeight) { - latestTxnBlockHeight = height; - } - - midSortedArray.add(midSortedTx); + txnsData.add(data); } + await db.addNewTransactionData(txnsData, walletId); - // sort by date ---- //TODO not sure if needed - // shouldn't be any issues with a null timestamp but I got one at some point? - midSortedArray - .sort((a, b) => (b["timestamp"] as int) - (a["timestamp"] as int)); - // { - // final aT = a["timestamp"]; - // final bT = b["timestamp"]; - // - // if (aT == null && bT == null) { - // return 0; - // } else if (aT == null) { - // return -1; - // } else if (bT == null) { - // return 1; - // } else { - // return bT - aT; - // } - // }); - - // buildDateTimeChunks - final Map result = {"dateTimeChunks": []}; - final dateArray = []; - - for (int i = 0; i < midSortedArray.length; i++) { - final txObject = midSortedArray[i]; - final date = extractDateFromTimestamp(txObject["timestamp"] as int); - final txTimeArray = [txObject["timestamp"], date]; - - if (dateArray.contains(txTimeArray[1])) { - result["dateTimeChunks"].forEach((dynamic chunk) { - if (extractDateFromTimestamp(chunk["timestamp"] as int) == - txTimeArray[1]) { - if (chunk["transactions"] == null) { - chunk["transactions"] = >[]; - } - chunk["transactions"].add(txObject); - } - }); - } else { - dateArray.add(txTimeArray[1]); - final chunk = { - "timestamp": txTimeArray[0], - "transactions": [txObject], - }; - result["dateTimeChunks"].add(chunk); - } + // quick hack to notify manager to call notifyListeners if + // transactions changed + if (txnsData.isNotEmpty) { + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "Transactions updated/added for: $walletId $walletName ", + walletId, + ), + ); } - - final transactionsMap = cachedTransactions?.getAllTransactions() ?? {}; - transactionsMap - .addAll(TransactionData.fromJson(result).getAllTransactions()); - - final txModel = TransactionData.fromMap(transactionsMap); - - await DB.instance.put( - boxName: walletId, - key: 'storedTxnDataHeight', - value: latestTxnBlockHeight); - await DB.instance.put( - boxName: walletId, key: 'latest_tx_model', value: txModel); - - cachedTxData = txModel; - return txModel; } int estimateTxFee({required int vSize, required int feeRatePerKB}) { @@ -2735,32 +2254,43 @@ class NamecoinWallet extends CoinServiceAPI { /// with [satoshiAmountToSend] and [selectedTxFeeRate]. If so, it will call buildTrasaction() and return /// a map containing the tx hex along with other important information. If not, then it will return /// an integer (1 or 2) - dynamic coinSelection( - int satoshiAmountToSend, - int selectedTxFeeRate, - String _recipientAddress, - bool isSendAll, { + dynamic coinSelection({ + required int satoshiAmountToSend, + required int selectedTxFeeRate, + required String recipientAddress, + required bool coinControl, + required bool isSendAll, int additionalOutputs = 0, - List? utxos, + List? utxos, }) async { Logging.instance .log("Starting coinSelection ----------", level: LogLevel.Info); - final List availableOutputs = utxos ?? outputsList; - final List spendableOutputs = []; + final List availableOutputs = utxos ?? await this.utxos; + final currentChainHeight = await chainHeight; + final List spendableOutputs = []; int spendableSatoshiValue = 0; // Build list of spendable outputs and totaling their satoshi amount - for (var i = 0; i < availableOutputs.length; i++) { - if (availableOutputs[i].blocked == false && - availableOutputs[i].status.confirmed == true) { - spendableOutputs.add(availableOutputs[i]); - spendableSatoshiValue += availableOutputs[i].value; + for (final utxo in availableOutputs) { + if (utxo.isBlocked == false && + utxo.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) && + utxo.used != true) { + spendableOutputs.add(utxo); + spendableSatoshiValue += utxo.value; } } - // sort spendable by age (oldest first) - spendableOutputs.sort( - (a, b) => b.status.confirmations.compareTo(a.status.confirmations)); + if (coinControl) { + if (spendableOutputs.length < availableOutputs.length) { + throw ArgumentError("Attempted to use an unavailable utxo"); + } + } + + // don't care about sorting if using all utxos + if (!coinControl) { + // sort spendable by age (oldest first) + spendableOutputs.sort((a, b) => b.blockTime!.compareTo(a.blockTime!)); + } Logging.instance.log("spendableOutputs.length: ${spendableOutputs.length}", level: LogLevel.Info); @@ -2787,21 +2317,29 @@ class NamecoinWallet extends CoinServiceAPI { // Possible situation right here int satoshisBeingUsed = 0; int inputsBeingConsumed = 0; - List utxoObjectsToUse = []; + List utxoObjectsToUse = []; - for (var i = 0; - satoshisBeingUsed < satoshiAmountToSend && i < spendableOutputs.length; - i++) { - utxoObjectsToUse.add(spendableOutputs[i]); - satoshisBeingUsed += spendableOutputs[i].value; - inputsBeingConsumed += 1; - } - for (int i = 0; - i < additionalOutputs && inputsBeingConsumed < spendableOutputs.length; - i++) { - utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]); - satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value; - inputsBeingConsumed += 1; + if (!coinControl) { + for (var i = 0; + satoshisBeingUsed < satoshiAmountToSend && + i < spendableOutputs.length; + i++) { + utxoObjectsToUse.add(spendableOutputs[i]); + satoshisBeingUsed += spendableOutputs[i].value; + inputsBeingConsumed += 1; + } + for (int i = 0; + i < additionalOutputs && + inputsBeingConsumed < spendableOutputs.length; + i++) { + utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]); + satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value; + inputsBeingConsumed += 1; + } + } else { + satoshisBeingUsed = spendableSatoshiValue; + utxoObjectsToUse = spendableOutputs; + inputsBeingConsumed = spendableOutputs.length; } Logging.instance @@ -2812,7 +2350,7 @@ class NamecoinWallet extends CoinServiceAPI { .log('utxoObjectsToUse: $utxoObjectsToUse', level: LogLevel.Info); // numberOfOutputs' length must always be equal to that of recipientsArray and recipientsAmtArray - List recipientsArray = [_recipientAddress]; + List recipientsArray = [recipientAddress]; List recipientsAmtArray = [satoshiAmountToSend]; // gather required signing data @@ -2823,9 +2361,8 @@ class NamecoinWallet extends CoinServiceAPI { .log("Attempting to send all $coin", level: LogLevel.Info); final int vSizeForOneOutput = (await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, - recipients: [_recipientAddress], + recipients: [recipientAddress], satoshiAmounts: [satoshisBeingUsed - 1], ))["vSize"] as int; int feeForOneOutput = estimateTxFee( @@ -2833,15 +2370,17 @@ class NamecoinWallet extends CoinServiceAPI { feeRatePerKB: selectedTxFeeRate, ); - final int roughEstimate = - roughFeeEstimate(spendableOutputs.length, 1, selectedTxFeeRate); + final int roughEstimate = roughFeeEstimate( + spendableOutputs.length, + 1, + selectedTxFeeRate, + ).raw.toInt(); if (feeForOneOutput < roughEstimate) { feeForOneOutput = roughEstimate; } final int amount = satoshiAmountToSend - feeForOneOutput; dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: [amount], @@ -2849,25 +2388,27 @@ class NamecoinWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": amount, + "recipientAmt": Amount( + rawValue: BigInt.from(amount), + fractionDigits: coin.decimals, + ), "fee": feeForOneOutput, "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), }; return transactionObject; } final int vSizeForOneOutput = (await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, - recipients: [_recipientAddress], + recipients: [recipientAddress], satoshiAmounts: [satoshisBeingUsed - 1], ))["vSize"] as int; final int vSizeForTwoOutPuts = (await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: [ - _recipientAddress, - await _getCurrentAddressForChain(1, DerivePathType.bip84), + recipientAddress, + await _getCurrentAddressForChain(1, DerivePathTypeExt.primaryFor(coin)), ], satoshiAmounts: [ satoshiAmountToSend, @@ -2893,7 +2434,7 @@ class NamecoinWallet extends CoinServiceAPI { if (satoshisBeingUsed - satoshiAmountToSend > feeForOneOutput) { if (satoshisBeingUsed - satoshiAmountToSend > - feeForOneOutput + DUST_LIMIT) { + feeForOneOutput + DUST_LIMIT.raw.toInt()) { // Here, we know that theoretically, we may be able to include another output(change) but we first need to // factor in the value of this output in satoshis. int changeOutputSize = @@ -2901,13 +2442,13 @@ class NamecoinWallet extends CoinServiceAPI { // We check to see if the user can pay for the new transaction with 2 outputs instead of one. If they can and // the second output's size > DUST_LIMIT satoshis, we perform the mechanics required to properly generate and use a new // change address. - if (changeOutputSize > DUST_LIMIT && + if (changeOutputSize > DUST_LIMIT.raw.toInt() && satoshisBeingUsed - satoshiAmountToSend - changeOutputSize == feeForTwoOutputs) { // generate new change address if current change address has been used - await _checkChangeAddressForTransactions(DerivePathType.bip84); - final String newChangeAddress = - await _getCurrentAddressForChain(1, DerivePathType.bip84); + await _checkChangeAddressForTransactions(); + final String newChangeAddress = await _getCurrentAddressForChain( + 1, DerivePathTypeExt.primaryFor(coin)); int feeBeingPaid = satoshisBeingUsed - satoshiAmountToSend - changeOutputSize; @@ -2929,7 +2470,6 @@ class NamecoinWallet extends CoinServiceAPI { Logging.instance .log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info); dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -2957,7 +2497,6 @@ class NamecoinWallet extends CoinServiceAPI { Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs', level: LogLevel.Info); txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -2967,9 +2506,13 @@ class NamecoinWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": feeBeingPaid, "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), }; return transactionObject; } else { @@ -2986,7 +2529,6 @@ class NamecoinWallet extends CoinServiceAPI { Logging.instance .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -2994,9 +2536,13 @@ class NamecoinWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": satoshisBeingUsed - satoshiAmountToSend, "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), }; return transactionObject; } @@ -3015,7 +2561,6 @@ class NamecoinWallet extends CoinServiceAPI { Logging.instance .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -3023,9 +2568,13 @@ class NamecoinWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": satoshisBeingUsed - satoshiAmountToSend, "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), }; return transactionObject; } @@ -3044,7 +2593,6 @@ class NamecoinWallet extends CoinServiceAPI { Logging.instance .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -3052,9 +2600,13 @@ class NamecoinWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": feeForOneOutput, "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), }; return transactionObject; } else { @@ -3066,254 +2618,158 @@ class NamecoinWallet extends CoinServiceAPI { level: LogLevel.Warning); // try adding more outputs if (spendableOutputs.length > inputsBeingConsumed) { - return coinSelection(satoshiAmountToSend, selectedTxFeeRate, - _recipientAddress, isSendAll, - additionalOutputs: additionalOutputs + 1, utxos: utxos); + return coinSelection( + satoshiAmountToSend: satoshiAmountToSend, + selectedTxFeeRate: selectedTxFeeRate, + recipientAddress: recipientAddress, + isSendAll: isSendAll, + additionalOutputs: additionalOutputs + 1, + utxos: utxos, + coinControl: coinControl, + ); } return 2; } } - Future> fetchBuildTxData( - List utxosToUse, + Future> fetchBuildTxData( + List utxosToUse, ) async { // return data - Map results = {}; - Map> addressTxid = {}; - - // addresses to check - List addressesP2PKH = []; - List addressesP2SH = []; - List addressesP2WPKH = []; - Logging.instance.log("utxos: $utxosToUse", level: LogLevel.Info); + List signingData = []; try { // Populating the addresses to check for (var i = 0; i < utxosToUse.length; i++) { - final txid = utxosToUse[i].txid; - final tx = await _cachedElectrumXClient.getTransaction( - txHash: txid, - coin: coin, - ); - Logging.instance.log("tx: ${json.encode(tx)}", - level: LogLevel.Info, printFullLength: true); - - for (final output in tx["vout"] as List) { - final n = output["n"]; - if (n != null && n == utxosToUse[i].vout) { - final address = output["scriptPubKey"]["address"] as String; - if (!addressTxid.containsKey(address)) { - addressTxid[address] = []; - } - (addressTxid[address] as List).add(txid); - switch (addressType(address: address)) { - case DerivePathType.bip44: - addressesP2PKH.add(address); - break; - case DerivePathType.bip49: - addressesP2SH.add(address); - break; - case DerivePathType.bip84: - addressesP2WPKH.add(address); - break; + if (utxosToUse[i].address == null) { + final txid = utxosToUse[i].txid; + final tx = await _cachedElectrumXClient.getTransaction( + txHash: txid, + coin: coin, + ); + for (final output in tx["vout"] as List) { + final n = output["n"]; + if (n != null && n == utxosToUse[i].vout) { + utxosToUse[i] = utxosToUse[i].copyWith( + address: output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]["address"] as String, + ); } } } + + final derivePathType = addressType(address: utxosToUse[i].address!); + + signingData.add( + SigningData( + derivePathType: derivePathType, + utxo: utxosToUse[i], + ), + ); } - // p2pkh / bip44 - final p2pkhLength = addressesP2PKH.length; - if (p2pkhLength > 0) { - final receiveDerivations = await _fetchDerivations( - chain: 0, - derivePathType: DerivePathType.bip44, - ); - final changeDerivations = await _fetchDerivations( - chain: 1, - derivePathType: DerivePathType.bip44, - ); - for (int i = 0; i < p2pkhLength; i++) { - // receives - final receiveDerivation = receiveDerivations[addressesP2PKH[i]]; - // if a match exists it will not be null - if (receiveDerivation != null) { - final data = P2PKH( - data: PaymentData( - pubkey: Format.stringToUint8List( - receiveDerivation["pubKey"] as String)), - network: _network, - ).data; + Map> receiveDerivations = {}; + Map> changeDerivations = {}; - for (String tx in addressTxid[addressesP2PKH[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - receiveDerivation["wif"] as String, - network: _network, - ), - }; - } - } else { - // if its not a receive, check change - final changeDerivation = changeDerivations[addressesP2PKH[i]]; - // if a match exists it will not be null - if (changeDerivation != null) { - final data = P2PKH( + for (final sd in signingData) { + String? pubKey; + String? wif; + + // fetch receiving derivations if null + receiveDerivations[sd.derivePathType] ??= await _fetchDerivations( + chain: 0, + derivePathType: sd.derivePathType, + ); + final receiveDerivation = + receiveDerivations[sd.derivePathType]![sd.utxo.address!]; + + if (receiveDerivation != null) { + pubKey = receiveDerivation["pubKey"] as String; + wif = receiveDerivation["wif"] as String; + } else { + // fetch change derivations if null + changeDerivations[sd.derivePathType] ??= await _fetchDerivations( + chain: 1, + derivePathType: sd.derivePathType, + ); + final changeDerivation = + changeDerivations[sd.derivePathType]![sd.utxo.address!]; + if (changeDerivation != null) { + pubKey = changeDerivation["pubKey"] as String; + wif = changeDerivation["wif"] as String; + } + } + + if (wif == null || pubKey == null) { + final address = await db.getAddress(walletId, sd.utxo.address!); + if (address?.derivationPath != null) { + final node = await Bip32Utils.getBip32Node( + (await mnemonicString)!, + (await mnemonicPassphrase)!, + _network, + address!.derivationPath!.value, + ); + + wif = node.toWIF(); + pubKey = Format.uint8listToString(node.publicKey); + } + } + + if (wif != null && pubKey != null) { + final PaymentData data; + final Uint8List? redeemScript; + + switch (sd.derivePathType) { + case DerivePathType.bip44: + data = P2PKH( data: PaymentData( - pubkey: Format.stringToUint8List( - changeDerivation["pubKey"] as String)), + pubkey: Format.stringToUint8List(pubKey), + ), network: _network, ).data; + redeemScript = null; + break; - for (String tx in addressTxid[addressesP2PKH[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - changeDerivation["wif"] as String, - network: _network, - ), - }; - } - } - } - } - } - - // p2sh / bip49 - final p2shLength = addressesP2SH.length; - if (p2shLength > 0) { - final receiveDerivations = await _fetchDerivations( - chain: 0, - derivePathType: DerivePathType.bip49, - ); - final changeDerivations = await _fetchDerivations( - chain: 1, - derivePathType: DerivePathType.bip49, - ); - for (int i = 0; i < p2shLength; i++) { - // receives - final receiveDerivation = receiveDerivations[addressesP2SH[i]]; - // if a match exists it will not be null - if (receiveDerivation != null) { - final p2wpkh = P2WPKH( - data: PaymentData( - pubkey: Format.stringToUint8List( - receiveDerivation["pubKey"] as String)), - network: _network, - overridePrefix: namecoin.bech32!) - .data; - - final redeemScript = p2wpkh.output; - - final data = - P2SH(data: PaymentData(redeem: p2wpkh), network: _network).data; - - for (String tx in addressTxid[addressesP2SH[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - receiveDerivation["wif"] as String, - network: _network, - ), - "redeemScript": redeemScript, - }; - } - } else { - // if its not a receive, check change - final changeDerivation = changeDerivations[addressesP2SH[i]]; - // if a match exists it will not be null - if (changeDerivation != null) { + case DerivePathType.bip49: final p2wpkh = P2WPKH( - data: PaymentData( - pubkey: Format.stringToUint8List( - changeDerivation["pubKey"] as String)), - network: _network, - overridePrefix: namecoin.bech32!) - .data; - - final redeemScript = p2wpkh.output; - - final data = - P2SH(data: PaymentData(redeem: p2wpkh), network: _network) - .data; - - for (String tx in addressTxid[addressesP2SH[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - changeDerivation["wif"] as String, - network: _network, - ), - "redeemScript": redeemScript, - }; - } - } - } - } - } - - // p2wpkh / bip84 - final p2wpkhLength = addressesP2WPKH.length; - if (p2wpkhLength > 0) { - final receiveDerivations = await _fetchDerivations( - chain: 0, - derivePathType: DerivePathType.bip84, - ); - final changeDerivations = await _fetchDerivations( - chain: 1, - derivePathType: DerivePathType.bip84, - ); - - for (int i = 0; i < p2wpkhLength; i++) { - // receives - final receiveDerivation = receiveDerivations[addressesP2WPKH[i]]; - // if a match exists it will not be null - if (receiveDerivation != null) { - final data = P2WPKH( - data: PaymentData( - pubkey: Format.stringToUint8List( - receiveDerivation["pubKey"] as String)), - network: _network, - overridePrefix: namecoin.bech32!) - .data; - - for (String tx in addressTxid[addressesP2WPKH[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - receiveDerivation["wif"] as String, - network: _network, + data: PaymentData( + pubkey: Format.stringToUint8List(pubKey), ), - }; - } - } else { - // if its not a receive, check change - final changeDerivation = changeDerivations[addressesP2WPKH[i]]; - // if a match exists it will not be null - if (changeDerivation != null) { - final data = P2WPKH( - data: PaymentData( - pubkey: Format.stringToUint8List( - changeDerivation["pubKey"] as String)), - network: _network, - overridePrefix: namecoin.bech32!) + network: _network, + overridePrefix: _network.bech32!, + ).data; + redeemScript = p2wpkh.output; + data = P2SH(data: PaymentData(redeem: p2wpkh), network: _network) .data; + break; - for (String tx in addressTxid[addressesP2WPKH[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - changeDerivation["wif"] as String, - network: _network, - ), - }; - } - } + case DerivePathType.bip84: + data = P2WPKH( + data: PaymentData( + pubkey: Format.stringToUint8List(pubKey), + ), + network: _network, + overridePrefix: _network.bech32!, + ).data; + redeemScript = null; + break; + + default: + throw Exception("DerivePathType unsupported"); } + + final keyPair = ECPair.fromWIF( + wif, + network: _network, + ); + + sd.redeemScript = redeemScript; + sd.output = data.output; + sd.keyPair = keyPair; } } - return results; + return signingData; } catch (e, s) { Logging.instance .log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error); @@ -3323,8 +2779,7 @@ class NamecoinWallet extends CoinServiceAPI { /// Builds and signs a transaction Future> buildTransaction({ - required List utxosToUse, - required Map utxoSigningData, + required List utxoSigningData, required List recipients, required List satoshiAmounts, }) async { @@ -3335,10 +2790,15 @@ class NamecoinWallet extends CoinServiceAPI { txb.setVersion(2); // Add transaction inputs - for (var i = 0; i < utxosToUse.length; i++) { - final txid = utxosToUse[i].txid; - txb.addInput(txid, utxosToUse[i].vout, null, - utxoSigningData[txid]["output"] as Uint8List, namecoin.bech32!); + for (var i = 0; i < utxoSigningData.length; i++) { + final txid = utxoSigningData[i].utxo.txid; + txb.addInput( + txid, + utxoSigningData[i].utxo.vout, + null, + utxoSigningData[i].output!, + _network.bech32!, + ); } // Add transaction output @@ -3348,14 +2808,15 @@ class NamecoinWallet extends CoinServiceAPI { try { // Sign the transaction accordingly - for (var i = 0; i < utxosToUse.length; i++) { - final txid = utxosToUse[i].txid; - txb.sign( - vin: i, - keyPair: utxoSigningData[txid]["keyPair"] as ECPair, - witnessValue: utxosToUse[i].value, - redeemScript: utxoSigningData[txid]["redeemScript"] as Uint8List?, - overridePrefix: namecoin.bech32!); + for (var i = 0; i < utxoSigningData.length; i++) { + final txid = utxoSigningData[i].utxo.txid; + txb.addInput( + txid, + utxoSigningData[i].utxo.vout, + null, + utxoSigningData[i].output!, + _network.bech32!, + ); } } catch (e, s) { Logging.instance.log("Caught exception while signing transaction: $e\n$s", @@ -3388,17 +2849,31 @@ class NamecoinWallet extends CoinServiceAPI { await _cachedElectrumXClient.clearSharedTransactionCache(coin: coin); // back up data - await _rescanBackup(); + // await _rescanBackup(); + + // clear blockchain info + await db.deleteWalletBlockchainData(walletId); + await _deleteDerivations(); try { - final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic'); + final _mnemonic = await mnemonicString; + final _mnemonicPassphrase = await mnemonicPassphrase; + if (_mnemonicPassphrase == null) { + Logging.instance.log( + "Exception in fullRescan: mnemonic passphrase null, possible migration issue; if using internal builds, delete wallet and restore from seed, if using a release build, please file bug report", + level: LogLevel.Error); + } + await _recoverWalletFromBIP32SeedPhrase( - mnemonic: mnemonic!, + mnemonic: _mnemonic!, + mnemonicPassphrase: _mnemonicPassphrase!, maxUnusedAddressGap: maxUnusedAddressGap, maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + isRescan: true, ); longMutex = false; + await refresh(); Logging.instance.log("Full rescan complete!", level: LogLevel.Info); GlobalEventBus.instance.fire( WalletSyncStatusChangedEvent( @@ -3417,7 +2892,7 @@ class NamecoinWallet extends CoinServiceAPI { ); // restore from backup - await _rescanRestore(); + // await _rescanRestore(); longMutex = false; Logging.instance.log("Exception rethrown from fullRescan(): $e\n$s", @@ -3426,346 +2901,360 @@ class NamecoinWallet extends CoinServiceAPI { } } - Future _rescanRestore() async { - Logging.instance.log("starting rescan restore", level: LogLevel.Info); - - // restore from backup - // p2pkh - final tempReceivingAddressesP2PKH = DB.instance - .get(boxName: walletId, key: 'receivingAddressesP2PKH_BACKUP'); - final tempChangeAddressesP2PKH = DB.instance - .get(boxName: walletId, key: 'changeAddressesP2PKH_BACKUP'); - final tempReceivingIndexP2PKH = DB.instance - .get(boxName: walletId, key: 'receivingIndexP2PKH_BACKUP'); - final tempChangeIndexP2PKH = DB.instance - .get(boxName: walletId, key: 'changeIndexP2PKH_BACKUP'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2PKH', - value: tempReceivingAddressesP2PKH); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2PKH', - value: tempChangeAddressesP2PKH); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2PKH', - value: tempReceivingIndexP2PKH); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2PKH', - value: tempChangeIndexP2PKH); - await DB.instance.delete( - key: 'receivingAddressesP2PKH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'changeAddressesP2PKH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'receivingIndexP2PKH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'changeIndexP2PKH_BACKUP', boxName: walletId); - - // p2Sh - final tempReceivingAddressesP2SH = DB.instance - .get(boxName: walletId, key: 'receivingAddressesP2SH_BACKUP'); - final tempChangeAddressesP2SH = DB.instance - .get(boxName: walletId, key: 'changeAddressesP2SH_BACKUP'); - final tempReceivingIndexP2SH = DB.instance - .get(boxName: walletId, key: 'receivingIndexP2SH_BACKUP'); - final tempChangeIndexP2SH = DB.instance - .get(boxName: walletId, key: 'changeIndexP2SH_BACKUP'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2SH', - value: tempReceivingAddressesP2SH); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2SH', - value: tempChangeAddressesP2SH); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2SH', - value: tempReceivingIndexP2SH); - await DB.instance.put( - boxName: walletId, key: 'changeIndexP2SH', value: tempChangeIndexP2SH); - await DB.instance.delete( - key: 'receivingAddressesP2SH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'changeAddressesP2SH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'receivingIndexP2SH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'changeIndexP2SH_BACKUP', boxName: walletId); - - // p2wpkh - final tempReceivingAddressesP2WPKH = DB.instance.get( - boxName: walletId, key: 'receivingAddressesP2WPKH_BACKUP'); - final tempChangeAddressesP2WPKH = DB.instance - .get(boxName: walletId, key: 'changeAddressesP2WPKH_BACKUP'); - final tempReceivingIndexP2WPKH = DB.instance - .get(boxName: walletId, key: 'receivingIndexP2WPKH_BACKUP'); - final tempChangeIndexP2WPKH = DB.instance - .get(boxName: walletId, key: 'changeIndexP2WPKH_BACKUP'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2WPKH', - value: tempReceivingAddressesP2WPKH); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2WPKH', - value: tempChangeAddressesP2WPKH); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2WPKH', - value: tempReceivingIndexP2WPKH); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2WPKH', - value: tempChangeIndexP2WPKH); - await DB.instance.delete( - key: 'receivingAddressesP2WPKH_BACKUP', boxName: walletId); - await DB.instance.delete( - key: 'changeAddressesP2WPKH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'receivingIndexP2WPKH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'changeIndexP2WPKH_BACKUP', boxName: walletId); - + Future _deleteDerivations() async { // P2PKH derivations - final p2pkhReceiveDerivationsString = await _secureStore.read( - key: "${walletId}_receiveDerivationsP2PKH_BACKUP"); - final p2pkhChangeDerivationsString = await _secureStore.read( - key: "${walletId}_changeDerivationsP2PKH_BACKUP"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2PKH", - value: p2pkhReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2PKH", - value: p2pkhChangeDerivationsString); - - await _secureStore.delete( - key: "${walletId}_receiveDerivationsP2PKH_BACKUP"); - await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH_BACKUP"); - - // P2SH derivations - final p2shReceiveDerivationsString = await _secureStore.read( - key: "${walletId}_receiveDerivationsP2SH_BACKUP"); - final p2shChangeDerivationsString = await _secureStore.read( - key: "${walletId}_changeDerivationsP2SH_BACKUP"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2SH", - value: p2shReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2SH", - value: p2shChangeDerivationsString); - - await _secureStore.delete(key: "${walletId}_receiveDerivationsP2SH_BACKUP"); - await _secureStore.delete(key: "${walletId}_changeDerivationsP2SH_BACKUP"); - - // P2WPKH derivations - final p2wpkhReceiveDerivationsString = await _secureStore.read( - key: "${walletId}_receiveDerivationsP2WPKH_BACKUP"); - final p2wpkhChangeDerivationsString = await _secureStore.read( - key: "${walletId}_changeDerivationsP2WPKH_BACKUP"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2WPKH", - value: p2wpkhReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2WPKH", - value: p2wpkhChangeDerivationsString); - - await _secureStore.delete( - key: "${walletId}_receiveDerivationsP2WPKH_BACKUP"); - await _secureStore.delete( - key: "${walletId}_changeDerivationsP2WPKH_BACKUP"); - - // UTXOs - final utxoData = DB.instance - .get(boxName: walletId, key: 'latest_utxo_model_BACKUP'); - await DB.instance.put( - boxName: walletId, key: 'latest_utxo_model', value: utxoData); - await DB.instance - .delete(key: 'latest_utxo_model_BACKUP', boxName: walletId); - - Logging.instance.log("rescan restore complete", level: LogLevel.Info); - } - - Future _rescanBackup() async { - Logging.instance.log("starting rescan backup", level: LogLevel.Info); - - // backup current and clear data - // p2pkh - final tempReceivingAddressesP2PKH = DB.instance - .get(boxName: walletId, key: 'receivingAddressesP2PKH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2PKH_BACKUP', - value: tempReceivingAddressesP2PKH); - await DB.instance - .delete(key: 'receivingAddressesP2PKH', boxName: walletId); - - final tempChangeAddressesP2PKH = DB.instance - .get(boxName: walletId, key: 'changeAddressesP2PKH'); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2PKH_BACKUP', - value: tempChangeAddressesP2PKH); - await DB.instance - .delete(key: 'changeAddressesP2PKH', boxName: walletId); - - final tempReceivingIndexP2PKH = - DB.instance.get(boxName: walletId, key: 'receivingIndexP2PKH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2PKH_BACKUP', - value: tempReceivingIndexP2PKH); - await DB.instance - .delete(key: 'receivingIndexP2PKH', boxName: walletId); - - final tempChangeIndexP2PKH = - DB.instance.get(boxName: walletId, key: 'changeIndexP2PKH'); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2PKH_BACKUP', - value: tempChangeIndexP2PKH); - await DB.instance - .delete(key: 'changeIndexP2PKH', boxName: walletId); - - // p2sh - final tempReceivingAddressesP2SH = DB.instance - .get(boxName: walletId, key: 'receivingAddressesP2SH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2SH_BACKUP', - value: tempReceivingAddressesP2SH); - await DB.instance - .delete(key: 'receivingAddressesP2SH', boxName: walletId); - - final tempChangeAddressesP2SH = - DB.instance.get(boxName: walletId, key: 'changeAddressesP2SH'); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2SH_BACKUP', - value: tempChangeAddressesP2SH); - await DB.instance - .delete(key: 'changeAddressesP2SH', boxName: walletId); - - final tempReceivingIndexP2SH = - DB.instance.get(boxName: walletId, key: 'receivingIndexP2SH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2SH_BACKUP', - value: tempReceivingIndexP2SH); - await DB.instance - .delete(key: 'receivingIndexP2SH', boxName: walletId); - - final tempChangeIndexP2SH = - DB.instance.get(boxName: walletId, key: 'changeIndexP2SH'); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2SH_BACKUP', - value: tempChangeIndexP2SH); - await DB.instance - .delete(key: 'changeIndexP2SH', boxName: walletId); - - // p2wpkh - final tempReceivingAddressesP2WPKH = DB.instance - .get(boxName: walletId, key: 'receivingAddressesP2WPKH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2WPKH_BACKUP', - value: tempReceivingAddressesP2WPKH); - await DB.instance - .delete(key: 'receivingAddressesP2WPKH', boxName: walletId); - - final tempChangeAddressesP2WPKH = DB.instance - .get(boxName: walletId, key: 'changeAddressesP2WPKH'); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2WPKH_BACKUP', - value: tempChangeAddressesP2WPKH); - await DB.instance - .delete(key: 'changeAddressesP2WPKH', boxName: walletId); - - final tempReceivingIndexP2WPKH = DB.instance - .get(boxName: walletId, key: 'receivingIndexP2WPKH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2WPKH_BACKUP', - value: tempReceivingIndexP2WPKH); - await DB.instance - .delete(key: 'receivingIndexP2WPKH', boxName: walletId); - - final tempChangeIndexP2WPKH = - DB.instance.get(boxName: walletId, key: 'changeIndexP2WPKH'); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2WPKH_BACKUP', - value: tempChangeIndexP2WPKH); - await DB.instance - .delete(key: 'changeIndexP2WPKH', boxName: walletId); - - // P2PKH derivations - final p2pkhReceiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivationsP2PKH"); - final p2pkhChangeDerivationsString = - await _secureStore.read(key: "${walletId}_changeDerivationsP2PKH"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2PKH_BACKUP", - value: p2pkhReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2PKH_BACKUP", - value: p2pkhChangeDerivationsString); - await _secureStore.delete(key: "${walletId}_receiveDerivationsP2PKH"); await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH"); // P2SH derivations - final p2shReceiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivationsP2SH"); - final p2shChangeDerivationsString = - await _secureStore.read(key: "${walletId}_changeDerivationsP2SH"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2SH_BACKUP", - value: p2shReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2SH_BACKUP", - value: p2shChangeDerivationsString); - await _secureStore.delete(key: "${walletId}_receiveDerivationsP2SH"); await _secureStore.delete(key: "${walletId}_changeDerivationsP2SH"); // P2WPKH derivations - final p2wpkhReceiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivationsP2WPKH"); - final p2wpkhChangeDerivationsString = - await _secureStore.read(key: "${walletId}_changeDerivationsP2WPKH"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2WPKH_BACKUP", - value: p2wpkhReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2WPKH_BACKUP", - value: p2wpkhChangeDerivationsString); - await _secureStore.delete(key: "${walletId}_receiveDerivationsP2WPKH"); await _secureStore.delete(key: "${walletId}_changeDerivationsP2WPKH"); - - // UTXOs - final utxoData = - DB.instance.get(boxName: walletId, key: 'latest_utxo_model'); - await DB.instance.put( - boxName: walletId, key: 'latest_utxo_model_BACKUP', value: utxoData); - await DB.instance - .delete(key: 'latest_utxo_model', boxName: walletId); - - Logging.instance.log("rescan backup complete", level: LogLevel.Info); } + // Future _rescanRestore() async { + // Logging.instance.log("starting rescan restore", level: LogLevel.Info); + // + // // restore from backup + // // p2pkh + // final tempReceivingAddressesP2PKH = DB.instance + // .get(boxName: walletId, key: 'receivingAddressesP2PKH_BACKUP'); + // final tempChangeAddressesP2PKH = DB.instance + // .get(boxName: walletId, key: 'changeAddressesP2PKH_BACKUP'); + // final tempReceivingIndexP2PKH = DB.instance + // .get(boxName: walletId, key: 'receivingIndexP2PKH_BACKUP'); + // final tempChangeIndexP2PKH = DB.instance + // .get(boxName: walletId, key: 'changeIndexP2PKH_BACKUP'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingAddressesP2PKH', + // value: tempReceivingAddressesP2PKH); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeAddressesP2PKH', + // value: tempChangeAddressesP2PKH); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingIndexP2PKH', + // value: tempReceivingIndexP2PKH); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeIndexP2PKH', + // value: tempChangeIndexP2PKH); + // await DB.instance.delete( + // key: 'receivingAddressesP2PKH_BACKUP', boxName: walletId); + // await DB.instance + // .delete(key: 'changeAddressesP2PKH_BACKUP', boxName: walletId); + // await DB.instance + // .delete(key: 'receivingIndexP2PKH_BACKUP', boxName: walletId); + // await DB.instance + // .delete(key: 'changeIndexP2PKH_BACKUP', boxName: walletId); + // + // // p2Sh + // final tempReceivingAddressesP2SH = DB.instance + // .get(boxName: walletId, key: 'receivingAddressesP2SH_BACKUP'); + // final tempChangeAddressesP2SH = DB.instance + // .get(boxName: walletId, key: 'changeAddressesP2SH_BACKUP'); + // final tempReceivingIndexP2SH = DB.instance + // .get(boxName: walletId, key: 'receivingIndexP2SH_BACKUP'); + // final tempChangeIndexP2SH = DB.instance + // .get(boxName: walletId, key: 'changeIndexP2SH_BACKUP'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingAddressesP2SH', + // value: tempReceivingAddressesP2SH); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeAddressesP2SH', + // value: tempChangeAddressesP2SH); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingIndexP2SH', + // value: tempReceivingIndexP2SH); + // await DB.instance.put( + // boxName: walletId, key: 'changeIndexP2SH', value: tempChangeIndexP2SH); + // await DB.instance.delete( + // key: 'receivingAddressesP2SH_BACKUP', boxName: walletId); + // await DB.instance + // .delete(key: 'changeAddressesP2SH_BACKUP', boxName: walletId); + // await DB.instance + // .delete(key: 'receivingIndexP2SH_BACKUP', boxName: walletId); + // await DB.instance + // .delete(key: 'changeIndexP2SH_BACKUP', boxName: walletId); + // + // // p2wpkh + // final tempReceivingAddressesP2WPKH = DB.instance.get( + // boxName: walletId, key: 'receivingAddressesP2WPKH_BACKUP'); + // final tempChangeAddressesP2WPKH = DB.instance + // .get(boxName: walletId, key: 'changeAddressesP2WPKH_BACKUP'); + // final tempReceivingIndexP2WPKH = DB.instance + // .get(boxName: walletId, key: 'receivingIndexP2WPKH_BACKUP'); + // final tempChangeIndexP2WPKH = DB.instance + // .get(boxName: walletId, key: 'changeIndexP2WPKH_BACKUP'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingAddressesP2WPKH', + // value: tempReceivingAddressesP2WPKH); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeAddressesP2WPKH', + // value: tempChangeAddressesP2WPKH); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingIndexP2WPKH', + // value: tempReceivingIndexP2WPKH); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeIndexP2WPKH', + // value: tempChangeIndexP2WPKH); + // await DB.instance.delete( + // key: 'receivingAddressesP2WPKH_BACKUP', boxName: walletId); + // await DB.instance.delete( + // key: 'changeAddressesP2WPKH_BACKUP', boxName: walletId); + // await DB.instance + // .delete(key: 'receivingIndexP2WPKH_BACKUP', boxName: walletId); + // await DB.instance + // .delete(key: 'changeIndexP2WPKH_BACKUP', boxName: walletId); + // + // // P2PKH derivations + // final p2pkhReceiveDerivationsString = await _secureStore.read( + // key: "${walletId}_receiveDerivationsP2PKH_BACKUP"); + // final p2pkhChangeDerivationsString = await _secureStore.read( + // key: "${walletId}_changeDerivationsP2PKH_BACKUP"); + // + // await _secureStore.write( + // key: "${walletId}_receiveDerivationsP2PKH", + // value: p2pkhReceiveDerivationsString); + // await _secureStore.write( + // key: "${walletId}_changeDerivationsP2PKH", + // value: p2pkhChangeDerivationsString); + // + // await _secureStore.delete( + // key: "${walletId}_receiveDerivationsP2PKH_BACKUP"); + // await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH_BACKUP"); + // + // // P2SH derivations + // final p2shReceiveDerivationsString = await _secureStore.read( + // key: "${walletId}_receiveDerivationsP2SH_BACKUP"); + // final p2shChangeDerivationsString = await _secureStore.read( + // key: "${walletId}_changeDerivationsP2SH_BACKUP"); + // + // await _secureStore.write( + // key: "${walletId}_receiveDerivationsP2SH", + // value: p2shReceiveDerivationsString); + // await _secureStore.write( + // key: "${walletId}_changeDerivationsP2SH", + // value: p2shChangeDerivationsString); + // + // await _secureStore.delete(key: "${walletId}_receiveDerivationsP2SH_BACKUP"); + // await _secureStore.delete(key: "${walletId}_changeDerivationsP2SH_BACKUP"); + // + // // P2WPKH derivations + // final p2wpkhReceiveDerivationsString = await _secureStore.read( + // key: "${walletId}_receiveDerivationsP2WPKH_BACKUP"); + // final p2wpkhChangeDerivationsString = await _secureStore.read( + // key: "${walletId}_changeDerivationsP2WPKH_BACKUP"); + // + // await _secureStore.write( + // key: "${walletId}_receiveDerivationsP2WPKH", + // value: p2wpkhReceiveDerivationsString); + // await _secureStore.write( + // key: "${walletId}_changeDerivationsP2WPKH", + // value: p2wpkhChangeDerivationsString); + // + // await _secureStore.delete( + // key: "${walletId}_receiveDerivationsP2WPKH_BACKUP"); + // await _secureStore.delete( + // key: "${walletId}_changeDerivationsP2WPKH_BACKUP"); + // + // // UTXOs + // final utxoData = DB.instance + // .get(boxName: walletId, key: 'latest_utxo_model_BACKUP'); + // await DB.instance.put( + // boxName: walletId, key: 'latest_utxo_model', value: utxoData); + // await DB.instance + // .delete(key: 'latest_utxo_model_BACKUP', boxName: walletId); + // + // Logging.instance.log("rescan restore complete", level: LogLevel.Info); + // } + // + // Future _rescanBackup() async { + // Logging.instance.log("starting rescan backup", level: LogLevel.Info); + // + // // backup current and clear data + // // p2pkh + // final tempReceivingAddressesP2PKH = DB.instance + // .get(boxName: walletId, key: 'receivingAddressesP2PKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingAddressesP2PKH_BACKUP', + // value: tempReceivingAddressesP2PKH); + // await DB.instance + // .delete(key: 'receivingAddressesP2PKH', boxName: walletId); + // + // final tempChangeAddressesP2PKH = DB.instance + // .get(boxName: walletId, key: 'changeAddressesP2PKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeAddressesP2PKH_BACKUP', + // value: tempChangeAddressesP2PKH); + // await DB.instance + // .delete(key: 'changeAddressesP2PKH', boxName: walletId); + // + // final tempReceivingIndexP2PKH = + // DB.instance.get(boxName: walletId, key: 'receivingIndexP2PKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingIndexP2PKH_BACKUP', + // value: tempReceivingIndexP2PKH); + // await DB.instance + // .delete(key: 'receivingIndexP2PKH', boxName: walletId); + // + // final tempChangeIndexP2PKH = + // DB.instance.get(boxName: walletId, key: 'changeIndexP2PKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeIndexP2PKH_BACKUP', + // value: tempChangeIndexP2PKH); + // await DB.instance + // .delete(key: 'changeIndexP2PKH', boxName: walletId); + // + // // p2sh + // final tempReceivingAddressesP2SH = DB.instance + // .get(boxName: walletId, key: 'receivingAddressesP2SH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingAddressesP2SH_BACKUP', + // value: tempReceivingAddressesP2SH); + // await DB.instance + // .delete(key: 'receivingAddressesP2SH', boxName: walletId); + // + // final tempChangeAddressesP2SH = + // DB.instance.get(boxName: walletId, key: 'changeAddressesP2SH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeAddressesP2SH_BACKUP', + // value: tempChangeAddressesP2SH); + // await DB.instance + // .delete(key: 'changeAddressesP2SH', boxName: walletId); + // + // final tempReceivingIndexP2SH = + // DB.instance.get(boxName: walletId, key: 'receivingIndexP2SH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingIndexP2SH_BACKUP', + // value: tempReceivingIndexP2SH); + // await DB.instance + // .delete(key: 'receivingIndexP2SH', boxName: walletId); + // + // final tempChangeIndexP2SH = + // DB.instance.get(boxName: walletId, key: 'changeIndexP2SH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeIndexP2SH_BACKUP', + // value: tempChangeIndexP2SH); + // await DB.instance + // .delete(key: 'changeIndexP2SH', boxName: walletId); + // + // // p2wpkh + // final tempReceivingAddressesP2WPKH = DB.instance + // .get(boxName: walletId, key: 'receivingAddressesP2WPKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingAddressesP2WPKH_BACKUP', + // value: tempReceivingAddressesP2WPKH); + // await DB.instance + // .delete(key: 'receivingAddressesP2WPKH', boxName: walletId); + // + // final tempChangeAddressesP2WPKH = DB.instance + // .get(boxName: walletId, key: 'changeAddressesP2WPKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeAddressesP2WPKH_BACKUP', + // value: tempChangeAddressesP2WPKH); + // await DB.instance + // .delete(key: 'changeAddressesP2WPKH', boxName: walletId); + // + // final tempReceivingIndexP2WPKH = DB.instance + // .get(boxName: walletId, key: 'receivingIndexP2WPKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingIndexP2WPKH_BACKUP', + // value: tempReceivingIndexP2WPKH); + // await DB.instance + // .delete(key: 'receivingIndexP2WPKH', boxName: walletId); + // + // final tempChangeIndexP2WPKH = + // DB.instance.get(boxName: walletId, key: 'changeIndexP2WPKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeIndexP2WPKH_BACKUP', + // value: tempChangeIndexP2WPKH); + // await DB.instance + // .delete(key: 'changeIndexP2WPKH', boxName: walletId); + // + // // P2PKH derivations + // final p2pkhReceiveDerivationsString = + // await _secureStore.read(key: "${walletId}_receiveDerivationsP2PKH"); + // final p2pkhChangeDerivationsString = + // await _secureStore.read(key: "${walletId}_changeDerivationsP2PKH"); + // + // await _secureStore.write( + // key: "${walletId}_receiveDerivationsP2PKH_BACKUP", + // value: p2pkhReceiveDerivationsString); + // await _secureStore.write( + // key: "${walletId}_changeDerivationsP2PKH_BACKUP", + // value: p2pkhChangeDerivationsString); + // + // await _secureStore.delete(key: "${walletId}_receiveDerivationsP2PKH"); + // await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH"); + // + // // P2SH derivations + // final p2shReceiveDerivationsString = + // await _secureStore.read(key: "${walletId}_receiveDerivationsP2SH"); + // final p2shChangeDerivationsString = + // await _secureStore.read(key: "${walletId}_changeDerivationsP2SH"); + // + // await _secureStore.write( + // key: "${walletId}_receiveDerivationsP2SH_BACKUP", + // value: p2shReceiveDerivationsString); + // await _secureStore.write( + // key: "${walletId}_changeDerivationsP2SH_BACKUP", + // value: p2shChangeDerivationsString); + // + // await _secureStore.delete(key: "${walletId}_receiveDerivationsP2SH"); + // await _secureStore.delete(key: "${walletId}_changeDerivationsP2SH"); + // + // // P2WPKH derivations + // final p2wpkhReceiveDerivationsString = + // await _secureStore.read(key: "${walletId}_receiveDerivationsP2WPKH"); + // final p2wpkhChangeDerivationsString = + // await _secureStore.read(key: "${walletId}_changeDerivationsP2WPKH"); + // + // await _secureStore.write( + // key: "${walletId}_receiveDerivationsP2WPKH_BACKUP", + // value: p2wpkhReceiveDerivationsString); + // await _secureStore.write( + // key: "${walletId}_changeDerivationsP2WPKH_BACKUP", + // value: p2wpkhChangeDerivationsString); + // + // await _secureStore.delete(key: "${walletId}_receiveDerivationsP2WPKH"); + // await _secureStore.delete(key: "${walletId}_changeDerivationsP2WPKH"); + // + // // UTXOs + // final utxoData = + // DB.instance.get(boxName: walletId, key: 'latest_utxo_model'); + // await DB.instance.put( + // boxName: walletId, key: 'latest_utxo_model_BACKUP', value: utxoData); + // await DB.instance + // .delete(key: 'latest_utxo_model', boxName: walletId); + // + // Logging.instance.log("rescan backup complete", level: LogLevel.Info); + // } + bool isActive = false; @override @@ -3773,42 +3262,50 @@ class NamecoinWallet extends CoinServiceAPI { (isActive) => this.isActive = isActive; @override - Future estimateFeeFor(int satoshiAmount, int feeRate) async { - final available = - Format.decimalAmountToSatoshis(await availableBalance, coin); + Future estimateFeeFor(Amount amount, int feeRate) async { + final available = balance.spendable; - if (available == satoshiAmount) { - return satoshiAmount - sweepAllEstimate(feeRate); - } else if (satoshiAmount <= 0 || satoshiAmount > available) { + if (available == amount) { + return amount - (await sweepAllEstimate(feeRate)); + } else if (amount <= Amount.zero || amount > available) { return roughFeeEstimate(1, 2, feeRate); } - int runningBalance = 0; + Amount runningBalance = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); int inputCount = 0; - for (final output in outputsList) { - runningBalance += output.value; - inputCount++; - if (runningBalance > satoshiAmount) { - break; + for (final output in (await utxos)) { + if (!output.isBlocked) { + runningBalance = runningBalance + + Amount( + rawValue: BigInt.from(output.value), + fractionDigits: coin.decimals, + ); + inputCount++; + if (runningBalance > amount) { + break; + } } } final oneOutPutFee = roughFeeEstimate(inputCount, 1, feeRate); final twoOutPutFee = roughFeeEstimate(inputCount, 2, feeRate); - if (runningBalance - satoshiAmount > oneOutPutFee) { - if (runningBalance - satoshiAmount > oneOutPutFee + DUST_LIMIT) { - final change = runningBalance - satoshiAmount - twoOutPutFee; + if (runningBalance - amount > oneOutPutFee) { + if (runningBalance - amount > oneOutPutFee + DUST_LIMIT) { + final change = runningBalance - amount - twoOutPutFee; if (change > DUST_LIMIT && - runningBalance - satoshiAmount - change == twoOutPutFee) { - return runningBalance - satoshiAmount - change; + runningBalance - amount - change == twoOutPutFee) { + return runningBalance - amount - change; } else { - return runningBalance - satoshiAmount; + return runningBalance - amount; } } else { - return runningBalance - satoshiAmount; + return runningBalance - amount; } - } else if (runningBalance - satoshiAmount == oneOutPutFee) { + } else if (runningBalance - amount == oneOutPutFee) { return oneOutPutFee; } else { return twoOutPutFee; @@ -3816,16 +3313,21 @@ class NamecoinWallet extends CoinServiceAPI { } // TODO: Check if this is the correct formula for namecoin - int roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { - return ((42 + (272 * inputCount) + (128 * outputCount)) / 4).ceil() * - (feeRatePerKB / 1000).ceil(); + Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { + return Amount( + rawValue: BigInt.from( + ((42 + (272 * inputCount) + (128 * outputCount)) / 4).ceil() * + (feeRatePerKB / 1000).ceil()), + fractionDigits: coin.decimals, + ); } - int sweepAllEstimate(int feeRate) { + Future sweepAllEstimate(int feeRate) async { int available = 0; int inputCount = 0; - for (final output in outputsList) { - if (output.status.confirmed) { + for (final output in (await utxos)) { + if (!output.isBlocked && + output.isConfirmed(storedChainHeight, MINIMUM_CONFIRMATIONS)) { available += output.value; inputCount++; } @@ -3834,29 +3336,26 @@ class NamecoinWallet extends CoinServiceAPI { // transaction will only have 1 output minus the fee final estimatedFee = roughFeeEstimate(inputCount, 1, feeRate); - return available - estimatedFee; + return Amount( + rawValue: BigInt.from(available), + fractionDigits: coin.decimals, + ) - + estimatedFee; } @override Future generateNewAddress() async { try { - await _incrementAddressIndexForChain( - 0, DerivePathType.bip84); // First increment the receiving index - final newReceivingIndex = DB.instance.get( - boxName: walletId, - key: 'receivingIndexP2WPKH') as int; // Check the new receiving index + final currentReceiving = await _currentReceivingAddress; + + final newReceivingIndex = currentReceiving.derivationIndex + 1; + + // Use new index to derive a new receiving address final newReceivingAddress = await _generateAddressForChain( - 0, - newReceivingIndex, - DerivePathType - .bip84); // Use new index to derive a new receiving address - await _addToAddressesArrayForChain( - newReceivingAddress, - 0, - DerivePathType - .bip84); // Add that new receiving address to the array of receiving addresses - _currentReceivingAddress = Future(() => - newReceivingAddress); // Set the new receiving address that the service + 0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin)); + + // Add that new receiving address + await db.putAddress(newReceivingAddress); return true; } catch (e, s) { @@ -3866,6 +3365,17 @@ class NamecoinWallet extends CoinServiceAPI { return false; } } + + @override + Future get xpub async { + final node = await Bip32Utils.getBip32Root( + (await mnemonic).join(" "), + await mnemonicPassphrase ?? "", + _network, + ); + + return node.neutered().toBase58(); + } } // Namecoin Network diff --git a/lib/services/coins/particl/particl_wallet.dart b/lib/services/coins/particl/particl_wallet.dart index 396f6db88..f16d8a5f2 100644 --- a/lib/services/coins/particl/particl_wallet.dart +++ b/lib/services/coins/particl/particl_wallet.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'dart:typed_data'; import 'package:bech32/bech32.dart'; import 'package:bip32/bip32.dart' as bip32; @@ -10,137 +9,126 @@ import 'package:bitcoindart/bitcoindart.dart'; import 'package:bs58check/bs58check.dart' as bs58check; import 'package:crypto/crypto.dart'; import 'package:decimal/decimal.dart'; -import 'package:devicelocale/devicelocale.dart'; import 'package:flutter/foundation.dart'; -import 'package:http/http.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; -import 'package:stackwallet/hive/db.dart'; -import 'package:stackwallet/models/models.dart' as models; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; import 'package:stackwallet/models/paymint/fee_object_model.dart'; -import 'package:stackwallet/models/paymint/transactions_model.dart'; -import 'package:stackwallet/models/paymint/utxo_model.dart'; +import 'package:stackwallet/models/signing_data.dart'; import 'package:stackwallet/services/coins/coin_service.dart'; import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +import 'package:stackwallet/services/mixins/coin_control_interface.dart'; +import 'package:stackwallet/services/mixins/wallet_cache.dart'; +import 'package:stackwallet/services/mixins/wallet_db.dart'; +import 'package:stackwallet/services/mixins/xpubable.dart'; import 'package:stackwallet/services/node_service.dart'; -import 'package:stackwallet/services/notifications_api.dart'; -import 'package:stackwallet/services/price.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; -import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/bip32_utils.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/default_nodes.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart'; import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; +import 'package:stackwallet/widgets/crypto_notifications.dart'; import 'package:tuple/tuple.dart'; import 'package:uuid/uuid.dart'; const int MINIMUM_CONFIRMATIONS = 1; -const int DUST_LIMIT = 294; +final Amount DUST_LIMIT = Amount( + rawValue: BigInt.from(294), + fractionDigits: Coin.particl.decimals, +); const String GENESIS_HASH_MAINNET = "0000ee0784c195317ac95623e22fddb8c7b8825dc3998e0bb924d66866eccf4c"; const String GENESIS_HASH_TESTNET = "0000594ada5310b367443ee0afd4fa3d0bbd5850ea4e33cdc7d6a904a7ec7c90"; -enum DerivePathType { bip44, bip84 } - -bip32.BIP32 getBip32Node( - int chain, - int index, - String mnemonic, - NetworkType network, - DerivePathType derivePathType, -) { - final root = getBip32Root(mnemonic, network); - - final node = getBip32NodeFromRoot(chain, index, root, derivePathType); - return node; -} - -/// wrapper for compute() -bip32.BIP32 getBip32NodeWrapper( - Tuple5 args, -) { - return getBip32Node( - args.item1, - args.item2, - args.item3, - args.item4, - args.item5, - ); -} - -bip32.BIP32 getBip32NodeFromRoot( - int chain, - int index, - bip32.BIP32 root, - DerivePathType derivePathType, -) { +String constructDerivePath({ + required DerivePathType derivePathType, + required int networkWIF, + int account = 0, + required int chain, + required int index, +}) { String coinType; - switch (root.network.wif) { + switch (networkWIF) { case 0x6c: // PART mainnet wif coinType = "44"; // PART mainnet break; default: - throw Exception("Invalid Particl network type used!"); + throw Exception("Invalid Particl network wif used!"); } + + int purpose; switch (derivePathType) { case DerivePathType.bip44: - return root.derivePath("m/44'/$coinType'/0'/$chain/$index"); + purpose = 44; + break; case DerivePathType.bip84: - return root.derivePath("m/84'/$coinType'/0'/$chain/$index"); + purpose = 84; + break; default: - throw Exception("DerivePathType must not be null."); + throw Exception("DerivePathType $derivePathType not supported"); } + + return "m/$purpose'/$coinType'/$account'/$chain/$index"; } -/// wrapper for compute() -bip32.BIP32 getBip32NodeFromRootWrapper( - Tuple4 args, -) { - return getBip32NodeFromRoot( - args.item1, - args.item2, - args.item3, - args.item4, - ); -} +class ParticlWallet extends CoinServiceAPI + with WalletCache, WalletDB, CoinControlInterface + implements XPubAble { + ParticlWallet({ + required String walletId, + required String walletName, + required Coin coin, + required ElectrumX client, + required CachedElectrumX cachedClient, + required TransactionNotificationTracker tracker, + required SecureStorageInterface secureStore, + MainDB? mockableOverride, + }) { + txTracker = tracker; + _walletId = walletId; + _walletName = walletName; + _coin = coin; + _electrumXClient = client; + _cachedElectrumXClient = cachedClient; + _secureStore = secureStore; + initCache(walletId, coin); + initWalletDB(mockableOverride: mockableOverride); + initCoinControlInterface( + walletId: walletId, + walletName: walletName, + coin: coin, + db: db, + getChainHeight: () => chainHeight, + refreshedBalanceCallback: (balance) async { + _balance = balance; + await updateCachedBalance(_balance!); + }, + ); + } -bip32.BIP32 getBip32Root(String mnemonic, NetworkType network) { - final seed = bip39.mnemonicToSeed(mnemonic); - final networkType = bip32.NetworkType( - wif: network.wif, - bip32: bip32.Bip32Type( - public: network.bip32.public, - private: network.bip32.private, - ), - ); - - final root = bip32.BIP32.fromSeed(seed, networkType); - return root; -} - -/// wrapper for compute() -bip32.BIP32 getBip32RootWrapper(Tuple2 args) { - return getBip32Root(args.item1, args.item2); -} - -class ParticlWallet extends CoinServiceAPI { static const integrationTestFlag = bool.fromEnvironment("IS_INTEGRATION_TEST"); final _prefs = Prefs.instance; Timer? timer; - late Coin _coin; + late final Coin _coin; late final TransactionNotificationTracker txTracker; @@ -153,87 +141,53 @@ class ParticlWallet extends CoinServiceAPI { } } - List outputsList = []; - @override set isFavorite(bool markFavorite) { - DB.instance.put( - boxName: walletId, key: "isFavorite", value: markFavorite); + _isFavorite = markFavorite; + updateCachedIsFavorite(markFavorite); } @override - bool get isFavorite { - try { - return DB.instance.get(boxName: walletId, key: "isFavorite") - as bool; - } catch (e, s) { - Logging.instance - .log("isFavorite fetch failed: $e\n$s", level: LogLevel.Error); - rethrow; - } - } + bool get isFavorite => _isFavorite ??= getCachedIsFavorite(); + + bool? _isFavorite; @override Coin get coin => _coin; @override - Future> get allOwnAddresses => - _allOwnAddresses ??= _fetchAllOwnAddresses(); - Future>? _allOwnAddresses; - - Future? _utxoData; - Future get utxoData => _utxoData ??= _fetchUtxoData(); + Future> get utxos => db.getUTXOs(walletId).findAll(); @override - Future> get unspentOutputs async => - (await utxoData).unspentOutputArray; + Future> get transactions => + db.getTransactions(walletId).sortByTimestampDesc().findAll(); @override - Future get availableBalance async { - final data = await utxoData; - return Format.satoshisToAmount( - data.satoshiBalance - data.satoshiBalanceUnconfirmed, - coin: coin); - } + Future get currentReceivingAddress async => + (await _currentReceivingAddress).value; - @override - Future get pendingBalance async { - final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalanceUnconfirmed, coin: coin); - } + Future get _currentReceivingAddress async => + (await db + .getAddresses(walletId) + .filter() + .typeEqualTo(isar_models.AddressType.p2wpkh) + .subTypeEqualTo(isar_models.AddressSubType.receiving) + .sortByDerivationIndexDesc() + .findFirst()) ?? + await _generateAddressForChain(0, 0, DerivePathTypeExt.primaryFor(coin)); - @override - Future get balanceMinusMaxFee async => - (await availableBalance) - - (Decimal.fromInt((await maxFee)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(); + Future get currentChangeAddress async => + (await _currentChangeAddress).value; - @override - Future get totalBalance async { - if (!isActive) { - final totalBalance = DB.instance - .get(boxName: walletId, key: 'totalBalance') as int?; - if (totalBalance == null) { - final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalance, coin: coin); - } else { - return Format.satoshisToAmount(totalBalance, coin: coin); - } - } - final data = await utxoData; - return Format.satoshisToAmount(data.satoshiBalance, coin: coin); - } - - @override - Future get currentReceivingAddress => _currentReceivingAddress ??= - _getCurrentAddressForChain(0, DerivePathType.bip84); - Future? _currentReceivingAddress; - - Future get currentLegacyReceivingAddress => - _currentReceivingAddressP2PKH ??= - _getCurrentAddressForChain(0, DerivePathType.bip44); - Future? _currentReceivingAddressP2PKH; + Future get _currentChangeAddress async => + (await db + .getAddresses(walletId) + .filter() + .typeEqualTo(isar_models.AddressType.p2wpkh) + .subTypeEqualTo(isar_models.AddressSubType.change) + .sortByDerivationIndexDesc() + .findFirst()) ?? + await _generateAddressForChain(1, 0, DerivePathTypeExt.primaryFor(coin)); @override Future exit() async { @@ -263,27 +217,38 @@ class ParticlWallet extends CoinServiceAPI { @override Future> get mnemonic => _getMnemonicList(); + @override + Future get mnemonicString => + _secureStore.read(key: '${_walletId}_mnemonic'); + + @override + Future get mnemonicPassphrase => _secureStore.read( + key: '${_walletId}_mnemonicPassphrase', + ); + Future get chainHeight async { try { final result = await _electrumXClient.getBlockHeadTip(); - return result["height"] as int; + final height = result["height"] as int; + await updateCachedChainHeight(height); + if (height > storedChainHeight) { + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "Updated current chain height in $walletId $walletName!", + walletId, + ), + ); + } + return height; } catch (e, s) { Logging.instance.log("Exception caught in chainHeight: $e\n$s", level: LogLevel.Error); - return -1; + return storedChainHeight; } } - int get storedChainHeight { - final storedHeight = DB.instance - .get(boxName: walletId, key: "storedChainHeight") as int?; - return storedHeight ?? 0; - } - - Future updateStoredChainHeight({required int newHeight}) async { - await DB.instance.put( - boxName: walletId, key: "storedChainHeight", value: newHeight); - } + @override + int get storedChainHeight => getCachedChainHeight(); DerivePathType addressType({required String address}) { Uint8List? decodeBase58; @@ -323,6 +288,7 @@ class ParticlWallet extends CoinServiceAPI { @override Future recoverFromMnemonic({ required String mnemonic, + String? mnemonicPassphrase, required int maxUnusedAddressGap, required int maxNumberOfIndexesToCheck, required int height, @@ -357,14 +323,21 @@ class ParticlWallet extends CoinServiceAPI { } // check to make sure we aren't overwriting a mnemonic // this should never fail - if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) { + if ((await mnemonicString) != null || + (await this.mnemonicPassphrase) != null) { longMutex = false; throw Exception("Attempted to overwrite mnemonic on restore!"); } await _secureStore.write( key: '${_walletId}_mnemonic', value: mnemonic.trim()); + await _secureStore.write( + key: '${_walletId}_mnemonicPassphrase', + value: mnemonicPassphrase ?? "", + ); + await _recoverWalletFromBIP32SeedPhrase( mnemonic: mnemonic.trim(), + mnemonicPassphrase: mnemonicPassphrase ?? "", maxUnusedAddressGap: maxUnusedAddressGap, maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, ); @@ -389,8 +362,8 @@ class ParticlWallet extends CoinServiceAPI { int txCountBatchSize, bip32.BIP32 root, DerivePathType type, - int account) async { - List addressArray = []; + int chain) async { + List addressArray = []; int returningIndex = -1; Map> derivations = {}; int gapCounter = 0; @@ -399,7 +372,7 @@ class ParticlWallet extends CoinServiceAPI { index += txCountBatchSize) { List iterationsAddressArray = []; Logging.instance.log( - "index: $index, \t GapCounter $account ${type.name}: $gapCounter", + "index: $index, \t GapCounter $chain ${type.name}: $gapCounter", level: LogLevel.Info); final _id = "k_$index"; @@ -407,34 +380,49 @@ class ParticlWallet extends CoinServiceAPI { final Map receivingNodes = {}; for (int j = 0; j < txCountBatchSize; j++) { - final node = await compute( - getBip32NodeFromRootWrapper, - Tuple4( - account, - index + j, - root, - type, - ), + final derivePath = constructDerivePath( + derivePathType: type, + networkWIF: root.network.wif, + chain: chain, + index: index + j, ); - String? address; + final node = await Bip32Utils.getBip32NodeFromRoot(root, derivePath); + + String addressString; + isar_models.AddressType addrType; switch (type) { case DerivePathType.bip44: - address = P2PKH( + addressString = P2PKH( data: PaymentData(pubkey: node.publicKey), network: _network) .data .address!; + addrType = isar_models.AddressType.p2pkh; break; case DerivePathType.bip84: - address = P2WPKH( + addressString = P2WPKH( network: _network, data: PaymentData(pubkey: node.publicKey)) .data .address!; + addrType = isar_models.AddressType.p2wpkh; break; default: - throw Exception("No Path type $type exists"); + throw Exception("DerivePathType $type not supported"); } + + final address = isar_models.Address( + walletId: walletId, + subType: chain == 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change, + type: addrType, + publicKey: node.publicKey, + value: addressString, + derivationIndex: index + j, + derivationPath: isar_models.DerivationPath()..value = derivePath, + ); + receivingNodes.addAll({ "${_id}_$j": { "node": node, @@ -442,7 +430,7 @@ class ParticlWallet extends CoinServiceAPI { } }); txCountCallArgs.addAll({ - "${_id}_$j": address, + "${_id}_$j": addressString, }); } @@ -454,15 +442,16 @@ class ParticlWallet extends CoinServiceAPI { int count = counts["${_id}_$k"]!; if (count > 0) { final node = receivingNodes["${_id}_$k"]; + final address = node["address"] as isar_models.Address; // add address to array - addressArray.add(node["address"] as String); - iterationsAddressArray.add(node["address"] as String); + addressArray.add(address); + iterationsAddressArray.add(address.value); // set current index returningIndex = index + k; // reset counter gapCounter = 0; // add info to derivations - derivations[node["address"] as String] = { + derivations[address.value] = { "pubKey": Format.uint8listToString( (node["node"] as bip32.BIP32).publicKey), "wif": (node["node"] as bip32.BIP32).toWIF(), @@ -506,8 +495,10 @@ class ParticlWallet extends CoinServiceAPI { Future _recoverWalletFromBIP32SeedPhrase({ required String mnemonic, + required String mnemonicPassphrase, int maxUnusedAddressGap = 20, int maxNumberOfIndexesToCheck = 1000, + bool isRescan = false, }) async { longMutex = true; @@ -516,15 +507,19 @@ class ParticlWallet extends CoinServiceAPI { Map> p2pkhChangeDerivations = {}; Map> p2wpkhChangeDerivations = {}; - final root = await compute(getBip32RootWrapper, Tuple2(mnemonic, _network)); + final root = await Bip32Utils.getBip32Root( + mnemonic, + mnemonicPassphrase, + _network, + ); - List p2pkhReceiveAddressArray = []; - List p2wpkhReceiveAddressArray = []; + List p2pkhReceiveAddressArray = []; + List p2wpkhReceiveAddressArray = []; int p2pkhReceiveIndex = -1; int p2wpkhReceiveIndex = -1; - List p2pkhChangeAddressArray = []; - List p2wpkhChangeAddressArray = []; + List p2pkhChangeAddressArray = []; + List p2wpkhChangeAddressArray = []; int p2pkhChangeIndex = -1; int p2wpkhChangeIndex = -1; @@ -554,25 +549,25 @@ class ParticlWallet extends CoinServiceAPI { [resultReceive44, resultReceive84, resultChange44, resultChange84]); p2pkhReceiveAddressArray = - (await resultReceive44)['addressArray'] as List; + (await resultReceive44)['addressArray'] as List; p2pkhReceiveIndex = (await resultReceive44)['index'] as int; p2pkhReceiveDerivations = (await resultReceive44)['derivations'] as Map>; p2wpkhReceiveAddressArray = - (await resultReceive84)['addressArray'] as List; + (await resultReceive84)['addressArray'] as List; p2wpkhReceiveIndex = (await resultReceive84)['index'] as int; p2wpkhReceiveDerivations = (await resultReceive84)['derivations'] as Map>; p2pkhChangeAddressArray = - (await resultChange44)['addressArray'] as List; + (await resultChange44)['addressArray'] as List; p2pkhChangeIndex = (await resultChange44)['index'] as int; p2pkhChangeDerivations = (await resultChange44)['derivations'] as Map>; p2wpkhChangeAddressArray = - (await resultChange84)['addressArray'] as List; + (await resultChange84)['addressArray'] as List; p2wpkhChangeIndex = (await resultChange84)['index'] as int; p2wpkhChangeDerivations = (await resultChange84)['derivations'] as Map>; @@ -611,14 +606,12 @@ class ParticlWallet extends CoinServiceAPI { final address = await _generateAddressForChain(0, 0, DerivePathType.bip44); p2pkhReceiveAddressArray.add(address); - p2pkhReceiveIndex = 0; } if (p2wpkhReceiveIndex == -1) { final address = await _generateAddressForChain(0, 0, DerivePathType.bip84); p2wpkhReceiveAddressArray.add(address); - p2wpkhReceiveIndex = 0; } // If restoring a wallet that never sent any funds with change, then set changeArray @@ -627,50 +620,36 @@ class ParticlWallet extends CoinServiceAPI { final address = await _generateAddressForChain(1, 0, DerivePathType.bip44); p2pkhChangeAddressArray.add(address); - p2pkhChangeIndex = 0; } if (p2wpkhChangeIndex == -1) { final address = await _generateAddressForChain(1, 0, DerivePathType.bip84); p2wpkhChangeAddressArray.add(address); - p2wpkhChangeIndex = 0; } - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2WPKH', - value: p2wpkhReceiveAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2WPKH', - value: p2wpkhChangeAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2PKH', - value: p2pkhReceiveAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2PKH', - value: p2pkhChangeAddressArray); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2WPKH', - value: p2wpkhReceiveIndex); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2WPKH', - value: p2wpkhChangeIndex); - await DB.instance.put( - boxName: walletId, key: 'changeIndexP2PKH', value: p2pkhChangeIndex); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2PKH', - value: p2pkhReceiveIndex); - await DB.instance - .put(boxName: walletId, key: "id", value: _walletId); - await DB.instance - .put(boxName: walletId, key: "isFavorite", value: false); + if (isRescan) { + await db.updateOrPutAddresses([ + ...p2wpkhReceiveAddressArray, + ...p2wpkhChangeAddressArray, + ...p2pkhReceiveAddressArray, + ...p2pkhChangeAddressArray, + ]); + } else { + await db.putAddresses([ + ...p2wpkhReceiveAddressArray, + ...p2wpkhChangeAddressArray, + ...p2pkhReceiveAddressArray, + ...p2pkhChangeAddressArray, + ]); + } + + await _updateUTXOs(); + + await Future.wait([ + updateCachedId(walletId), + updateCachedIsFavorite(false), + ]); longMutex = false; } catch (e, s) { @@ -710,11 +689,15 @@ class ParticlWallet extends CoinServiceAPI { } if (!needsRefresh) { var allOwnAddresses = await _fetchAllOwnAddresses(); - List> allTxs = - await _fetchHistory(allOwnAddresses); - final txData = await transactionData; + List> allTxs = await _fetchHistory( + allOwnAddresses.map((e) => e.value).toList(growable: false)); for (Map transaction in allTxs) { - if (txData.findTransaction(transaction['tx_hash'] as String) == + final txid = transaction['tx_hash'] as String; + if ((await db + .getTransactions(walletId) + .filter() + .txidMatches(txid) + .findFirst()) == null) { Logging.instance.log( " txid not found in address history already ${transaction['tx_hash']}", @@ -733,16 +716,25 @@ class ParticlWallet extends CoinServiceAPI { } } - Future getAllTxsToWatch( - TransactionData txData, - ) async { + Future getAllTxsToWatch() async { if (_hasCalledExit) return; - List unconfirmedTxnsToNotifyPending = []; - List unconfirmedTxnsToNotifyConfirmed = []; + List unconfirmedTxnsToNotifyPending = []; + List unconfirmedTxnsToNotifyConfirmed = []; - for (final chunk in txData.txChunks) { - for (final tx in chunk.transactions) { - if (tx.confirmedStatus) { + final currentChainHeight = await chainHeight; + + final txCount = await db.getTransactions(walletId).count(); + + const paginateLimit = 50; + + for (int i = 0; i < txCount; i += paginateLimit) { + final transactions = await db + .getTransactions(walletId) + .offset(i) + .limit(paginateLimit) + .findAll(); + for (final tx in transactions) { + if (tx.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS)) { // get all transactions that were notified as pending but not as confirmed if (txTracker.wasNotifiedPending(tx.txid) && !txTracker.wasNotifiedConfirmed(tx.txid)) { @@ -759,60 +751,74 @@ class ParticlWallet extends CoinServiceAPI { // notify on unconfirmed transactions for (final tx in unconfirmedTxnsToNotifyPending) { - if (tx.txType == "Received") { - unawaited(NotificationApi.showNotification( - title: "Incoming transaction", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS, - coinName: coin.name, - txid: tx.txid, - confirmations: tx.confirmations, - requiredConfirmations: MINIMUM_CONFIRMATIONS, - )); + final confirmations = tx.getConfirmations(currentChainHeight); + + if (tx.type == isar_models.TransactionType.incoming) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Incoming transaction", + walletId: walletId, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, + txid: tx.txid, + confirmations: confirmations, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); + await txTracker.addNotifiedPending(tx.txid); - } else if (tx.txType == "Sent") { - unawaited(NotificationApi.showNotification( - title: "Sending transaction", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: tx.confirmations < MINIMUM_CONFIRMATIONS, - coinName: coin.name, - txid: tx.txid, - confirmations: tx.confirmations, - requiredConfirmations: MINIMUM_CONFIRMATIONS, - )); + } else if (tx.type == isar_models.TransactionType.outgoing) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Sending transaction", + walletId: walletId, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: confirmations < MINIMUM_CONFIRMATIONS, + txid: tx.txid, + confirmations: confirmations, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); + await txTracker.addNotifiedPending(tx.txid); } } // notify on confirmed for (final tx in unconfirmedTxnsToNotifyConfirmed) { - if (tx.txType == "Received") { - unawaited(NotificationApi.showNotification( - title: "Incoming transaction confirmed", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: false, - coinName: coin.name, - )); + if (tx.type == isar_models.TransactionType.incoming) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Incoming transaction confirmed", + walletId: walletId, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: false, + txid: tx.txid, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); + await txTracker.addNotifiedConfirmed(tx.txid); - } else if (tx.txType == "Sent") { - unawaited(NotificationApi.showNotification( - title: "Outgoing transaction confirmed", - body: walletName, - walletId: walletId, - iconAssetName: Assets.svg.iconFor(coin: coin), - date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), - shouldWatchForUpdates: false, - coinName: coin.name, - )); + } else if (tx.type == isar_models.TransactionType.outgoing) { + CryptoNotificationsEventBus.instance.fire( + CryptoNotificationEvent( + title: "Outgoing transaction confirmed", + walletId: walletId, + date: DateTime.fromMillisecondsSinceEpoch(tx.timestamp * 1000), + shouldWatchForUpdates: false, + txid: tx.txid, + requiredConfirmations: MINIMUM_CONFIRMATIONS, + walletName: walletName, + coin: coin, + ), + ); + await txTracker.addNotifiedConfirmed(tx.txid); } } @@ -877,46 +883,31 @@ class ParticlWallet extends CoinServiceAPI { .log("cached height: $storedHeight", level: LogLevel.Info); if (currentHeight != storedHeight) { - if (currentHeight != -1) { - // -1 failed to fetch current height - unawaited(updateStoredChainHeight(newHeight: currentHeight)); - } - GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.2, walletId)); - final changeAddressForTransactions = - _checkChangeAddressForTransactions(DerivePathType.bip84); + await _checkChangeAddressForTransactions(); GlobalEventBus.instance.fire(RefreshPercentChangedEvent(0.3, walletId)); - final currentReceivingAddressesForTransactions = - _checkCurrentReceivingAddressesForTransactions(); + await _checkCurrentReceivingAddressesForTransactions(); - final newTxData = _fetchTransactionData(); + final fetchFuture = _refreshTransactions(); + final utxosRefreshFuture = _updateUTXOs(); GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.50, walletId)); - final newUtxoData = _fetchUtxoData(); final feeObj = _getFees(); GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.60, walletId)); - _transactionData = Future(() => newTxData); - GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.70, walletId)); _feeObject = Future(() => feeObj); - _utxoData = Future(() => newUtxoData); + + await utxosRefreshFuture; GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.80, walletId)); - final allTxsToWatch = getAllTxsToWatch(await newTxData); - await Future.wait([ - newTxData, - changeAddressForTransactions, - currentReceivingAddressesForTransactions, - newUtxoData, - feeObj, - allTxsToWatch, - ]); + await fetchFuture; + await getAllTxsToWatch(); GlobalEventBus.instance .fire(RefreshPercentChangedEvent(0.90, walletId)); } @@ -969,12 +960,13 @@ class ParticlWallet extends CoinServiceAPI { @override Future> prepareSend({ required String address, - required int satoshiAmount, + required Amount amount, Map? args, }) async { try { final feeRateType = args?["feeRate"]; final feeRateAmount = args?["feeRateAmount"]; + final utxos = args?["UTXOs"] as Set?; if (feeRateType is FeeRateType || feeRateAmount is int) { late final int rate; if (feeRateType is FeeRateType) { @@ -998,14 +990,20 @@ class ParticlWallet extends CoinServiceAPI { // check for send all bool isSendAll = false; - final balance = - Format.decimalAmountToSatoshis(await availableBalance, coin); - if (satoshiAmount == balance) { + if (amount == balance.spendable) { isSendAll = true; } - final txData = - await coinSelection(satoshiAmount, rate, address, isSendAll); + final bool coinControl = utxos != null; + + final txData = await coinSelection( + satoshiAmountToSend: amount.raw.toInt(), + selectedTxFeeRate: rate, + recipientAddress: address, + isSendAll: isSendAll, + utxos: utxos?.toList(), + coinControl: coinControl, + ); Logging.instance.log("prepare send: $txData", level: LogLevel.Info); try { @@ -1068,6 +1066,11 @@ class ParticlWallet extends CoinServiceAPI { final txHash = await _electrumXClient.broadcastTransaction(rawTx: hex); Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info); + final utxos = txData["usedUTXOs"] as List; + + // mark utxos as used + await db.putUTXOs(utxos.map((e) => e.copyWith(used: true)).toList()); + return txHash; } catch (e, s) { Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s", @@ -1076,24 +1079,6 @@ class ParticlWallet extends CoinServiceAPI { } } - @override - Future send({ - required String toAddress, - required int amount, - Map args = const {}, - }) async { - try { - final txData = await prepareSend( - address: toAddress, satoshiAmount: amount, args: args); - final txHash = await confirmSend(txData: txData); - return txHash; - } catch (e, s) { - Logging.instance - .log("Exception rethrown from send(): $e\n$s", level: LogLevel.Error); - rethrow; - } - } - @override Future testNetworkConnection() async { try { @@ -1146,7 +1131,7 @@ class ParticlWallet extends CoinServiceAPI { Logging.instance .log("Generating new ${coin.prettyName} wallet.", level: LogLevel.Info); - if ((DB.instance.get(boxName: walletId, key: "id")) != null) { + if (getCachedId() != null) { throw Exception( "Attempted to initialize a new wallet using an existing wallet ID!"); } @@ -1160,82 +1145,61 @@ class ParticlWallet extends CoinServiceAPI { rethrow; } await Future.wait([ - DB.instance.put(boxName: walletId, key: "id", value: walletId), - DB.instance - .put(boxName: walletId, key: "isFavorite", value: false), + updateCachedId(walletId), + updateCachedIsFavorite(false), ]); } @override Future initializeExisting() async { - Logging.instance.log("Opening existing ${coin.prettyName} wallet.", + Logging.instance.log("initializeExisting() ${coin.prettyName} wallet.", level: LogLevel.Info); - if ((DB.instance.get(boxName: walletId, key: "id")) == null) { + if (getCachedId() == null) { throw Exception( "Attempted to initialize an existing wallet using an unknown wallet ID!"); } await _prefs.init(); - final data = - DB.instance.get(boxName: walletId, key: "latest_tx_model") - as TransactionData?; - if (data != null) { - _transactionData = Future(() => data); - } + // await _checkCurrentChangeAddressesForTransactions(); + // await _checkCurrentReceivingAddressesForTransactions(); } - @override - Future get transactionData => - _transactionData ??= _fetchTransactionData(); - Future? _transactionData; - - TransactionData? cachedTxData; - // TODO make sure this copied implementation from bitcoin_wallet.dart applies for particl just as well--or import it // hack to add tx to txData before refresh completes // required based on current app architecture where we don't properly store // transactions locally in a good way @override Future updateSentCachedTxData(Map txData) async { - final priceData = - await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final locale = Platform.isWindows ? "en_US" : await Devicelocale.currentLocale; - final String worthNow = Format.localizedStringAsFixed( - value: - ((currentPrice * Decimal.fromInt(txData["recipientAmt"] as int)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2), - decimalPlaces: 2, - locale: locale!); - - final tx = models.Transaction( + final transaction = isar_models.Transaction( + walletId: walletId, txid: txData["txid"] as String, - confirmedStatus: false, timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, - txType: "Sent", - amount: txData["recipientAmt"] as int, - worthNow: worthNow, - worthAtBlockTimestamp: worthNow, - fees: txData["fee"] as int, - inputSize: 0, - outputSize: 0, + type: isar_models.TransactionType.outgoing, + subType: isar_models.TransactionSubType.none, + // precision may be lost here hence the following amountString + amount: (txData["recipientAmt"] as Amount).raw.toInt(), + amountString: (txData["recipientAmt"] as Amount).toJsonString(), + fee: txData["fee"] as int, + height: null, + isCancelled: false, + isLelantus: false, + otherData: null, + slateId: null, + nonce: null, inputs: [], outputs: [], - address: txData["address"] as String, - height: -1, - confirmations: 0, ); - if (cachedTxData == null) { - final data = await _fetchTransactionData(); - _transactionData = Future(() => data); - } else { - final transactions = cachedTxData!.getAllTransactions(); - transactions[tx.txid] = tx; - cachedTxData = models.TransactionData.fromMap(transactions); - _transactionData = Future(() => cachedTxData!); - } + final address = txData["address"] is String + ? await db.getAddress(walletId, txData["address"] as String) + : null; + + await db.addNewTransactionData( + [ + Tuple2(transaction, address), + ], + walletId, + ); } @override @@ -1245,7 +1209,7 @@ class ParticlWallet extends CoinServiceAPI { @override String get walletId => _walletId; - late String _walletId; + late final String _walletId; @override String get walletName => _walletName; @@ -1265,29 +1229,6 @@ class ParticlWallet extends CoinServiceAPI { late SecureStorageInterface _secureStore; - late PriceAPI _priceAPI; - - ParticlWallet({ - required String walletId, - required String walletName, - required Coin coin, - required ElectrumX client, - required CachedElectrumX cachedClient, - required TransactionNotificationTracker tracker, - PriceAPI? priceAPI, - required SecureStorageInterface secureStore, - }) { - txTracker = tracker; - _walletId = walletId; - _walletName = walletName; - _coin = coin; - _electrumXClient = client; - _cachedElectrumXClient = cachedClient; - - _priceAPI = priceAPI ?? PriceAPI(Client()); - _secureStore = secureStore; - } - @override Future updateNode(bool shouldRefresh) async { final failovers = NodeService(secureStorageInterface: _secureStore) @@ -1318,12 +1259,11 @@ class ParticlWallet extends CoinServiceAPI { } Future> _getMnemonicList() async { - final mnemonicString = - await _secureStore.read(key: '${_walletId}_mnemonic'); - if (mnemonicString == null) { + final _mnemonicString = await mnemonicString; + if (_mnemonicString == null) { return []; } - final List data = mnemonicString.split(' '); + final List data = _mnemonicString.split(' '); return data; } @@ -1341,38 +1281,50 @@ class ParticlWallet extends CoinServiceAPI { ); } - Future> _fetchAllOwnAddresses() async { - final List allAddresses = []; - final receivingAddresses = DB.instance.get( - boxName: walletId, key: 'receivingAddressesP2WPKH') as List; - final changeAddresses = DB.instance.get( - boxName: walletId, key: 'changeAddressesP2WPKH') as List; - final receivingAddressesP2PKH = DB.instance.get( - boxName: walletId, key: 'receivingAddressesP2PKH') as List; - final changeAddressesP2PKH = - DB.instance.get(boxName: walletId, key: 'changeAddressesP2PKH') - as List; + Future> _fetchAllOwnAddresses() async { + final allAddresses = await db + .getAddresses(walletId) + .filter() + .not() + .typeEqualTo(isar_models.AddressType.nonWallet) + .and() + .group((q) => q + .subTypeEqualTo(isar_models.AddressSubType.receiving) + .or() + .subTypeEqualTo(isar_models.AddressSubType.change)) + .findAll(); - for (var i = 0; i < receivingAddresses.length; i++) { - if (!allAddresses.contains(receivingAddresses[i])) { - allAddresses.add(receivingAddresses[i] as String); - } - } - for (var i = 0; i < changeAddresses.length; i++) { - if (!allAddresses.contains(changeAddresses[i])) { - allAddresses.add(changeAddresses[i] as String); - } - } - for (var i = 0; i < receivingAddressesP2PKH.length; i++) { - if (!allAddresses.contains(receivingAddressesP2PKH[i])) { - allAddresses.add(receivingAddressesP2PKH[i] as String); - } - } - for (var i = 0; i < changeAddressesP2PKH.length; i++) { - if (!allAddresses.contains(changeAddressesP2PKH[i])) { - allAddresses.add(changeAddressesP2PKH[i] as String); - } - } + // final List allAddresses = []; + // final receivingAddresses = DB.instance.get( + // boxName: walletId, key: 'receivingAddressesP2WPKH') as List; + // final changeAddresses = DB.instance.get( + // boxName: walletId, key: 'changeAddressesP2WPKH') as List; + // final receivingAddressesP2PKH = DB.instance.get( + // boxName: walletId, key: 'receivingAddressesP2PKH') as List; + // final changeAddressesP2PKH = + // DB.instance.get(boxName: walletId, key: 'changeAddressesP2PKH') + // as List; + // + // for (var i = 0; i < receivingAddresses.length; i++) { + // if (!allAddresses.contains(receivingAddresses[i])) { + // allAddresses.add(receivingAddresses[i] as String); + // } + // } + // for (var i = 0; i < changeAddresses.length; i++) { + // if (!allAddresses.contains(changeAddresses[i])) { + // allAddresses.add(changeAddresses[i] as String); + // } + // } + // for (var i = 0; i < receivingAddressesP2PKH.length; i++) { + // if (!allAddresses.contains(receivingAddressesP2PKH[i])) { + // allAddresses.add(receivingAddressesP2PKH[i] as String); + // } + // } + // for (var i = 0; i < changeAddressesP2PKH.length; i++) { + // if (!allAddresses.contains(changeAddressesP2PKH[i])) { + // allAddresses.add(changeAddressesP2PKH[i] as String); + // } + // } return allAddresses; } @@ -1390,9 +1342,18 @@ class ParticlWallet extends CoinServiceAPI { numberOfBlocksFast: f, numberOfBlocksAverage: m, numberOfBlocksSlow: s, - fast: Format.decimalAmountToSatoshis(fast, coin), - medium: Format.decimalAmountToSatoshis(medium, coin), - slow: Format.decimalAmountToSatoshis(slow, coin), + fast: Amount.fromDecimal( + fast, + fractionDigits: coin.decimals, + ).raw.toInt(), + medium: Amount.fromDecimal( + medium, + fractionDigits: coin.decimals, + ).raw.toInt(), + slow: Amount.fromDecimal( + slow, + fractionDigits: coin.decimals, + ).raw.toInt(), ); Logging.instance.log("fetched fees: $feeObject", level: LogLevel.Info); @@ -1408,118 +1369,97 @@ class ParticlWallet extends CoinServiceAPI { Logging.instance .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); if (!integrationTestFlag) { - final features = await electrumXClient.getServerFeatures(); - Logging.instance.log("features: $features", level: LogLevel.Info); - switch (coin) { - case Coin.particl: - if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { - throw Exception("genesis hash does not match main net!"); - } - break; - default: - throw Exception( - "Attempted to generate a ParticlWallet using a non particl coin type: ${coin.name}"); + try { + final features = await electrumXClient.getServerFeatures(); + Logging.instance.log("features: $features", level: LogLevel.Info); + switch (coin) { + case Coin.particl: + if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { + throw Exception("genesis hash does not match main net!"); + } + break; + default: + throw Exception( + "Attempted to generate a ParticlWallet using a non particl coin type: ${coin.name}"); + } + } catch (e, s) { + Logging.instance.log("$e/n$s", level: LogLevel.Info); } } // this should never fail - if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) { + if ((await mnemonicString) != null || (await mnemonicPassphrase) != null) { throw Exception( "Attempted to overwrite mnemonic on generate new wallet!"); } await _secureStore.write( key: '${_walletId}_mnemonic', value: bip39.generateMnemonic(strength: 256)); - - // Set relevant indexes - await DB.instance - .put(boxName: walletId, key: "receivingIndexP2WPKH", value: 0); - await DB.instance - .put(boxName: walletId, key: "changeIndexP2WPKH", value: 0); - await DB.instance - .put(boxName: walletId, key: "receivingIndexP2PKH", value: 0); - await DB.instance - .put(boxName: walletId, key: "changeIndexP2PKH", value: 0); - await DB.instance.put( - boxName: walletId, - key: 'blocked_tx_hashes', - value: ["0xdefault"], - ); // A list of transaction hashes to represent frozen utxos in wallet - // initialize address book entries - await DB.instance.put( - boxName: walletId, - key: 'addressBookEntries', - value: {}); + await _secureStore.write( + key: '${_walletId}_mnemonicPassphrase', + value: "", + ); // Generate and add addresses to relevant arrays - await Future.wait([ + final initialAddresses = await Future.wait([ // P2WPKH - _generateAddressForChain(0, 0, DerivePathType.bip84).then( - (initialReceivingAddressP2WPKH) { - _addToAddressesArrayForChain( - initialReceivingAddressP2WPKH, 0, DerivePathType.bip84); - _currentReceivingAddress = - Future(() => initialReceivingAddressP2WPKH); - }, - ), - _generateAddressForChain(1, 0, DerivePathType.bip84).then( - (initialChangeAddressP2WPKH) => _addToAddressesArrayForChain( - initialChangeAddressP2WPKH, - 1, - DerivePathType.bip84, - ), - ), + _generateAddressForChain(0, 0, DerivePathType.bip84), + _generateAddressForChain(1, 0, DerivePathType.bip84), // P2PKH - _generateAddressForChain(0, 0, DerivePathType.bip44).then( - (initialReceivingAddressP2PKH) { - _addToAddressesArrayForChain( - initialReceivingAddressP2PKH, 0, DerivePathType.bip44); - _currentReceivingAddressP2PKH = - Future(() => initialReceivingAddressP2PKH); - }, - ), - _generateAddressForChain(1, 0, DerivePathType.bip44).then( - (initialChangeAddressP2PKH) => _addToAddressesArrayForChain( - initialChangeAddressP2PKH, - 1, - DerivePathType.bip44, - ), - ), + _generateAddressForChain(0, 0, DerivePathType.bip44), + _generateAddressForChain(1, 0, DerivePathType.bip44), ]); + await db.putAddresses(initialAddresses); + Logging.instance.log("_generateNewWalletFinished", level: LogLevel.Info); } /// Generates a new internal or external chain address for the wallet using a BIP84, BIP44, or BIP49 derivation path. /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! /// [index] - This can be any integer >= 0 - Future _generateAddressForChain( + Future _generateAddressForChain( int chain, int index, DerivePathType derivePathType, ) async { - final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic'); - final node = await compute( - getBip32NodeWrapper, - Tuple5( - chain, - index, - mnemonic!, - _network, - derivePathType, - ), + final _mnemonic = await mnemonicString; + final _mnemonicPassphrase = await mnemonicPassphrase; + if (_mnemonicPassphrase == null) { + Logging.instance.log( + "Exception in _generateAddressForChain: mnemonic passphrase null, possible migration issue; if using internal builds, delete wallet and restore from seed, if using a release build, please file bug report", + level: LogLevel.Error); + } + + final derivePath = constructDerivePath( + derivePathType: derivePathType, + networkWIF: _network.wif, + chain: chain, + index: index, ); + final node = await Bip32Utils.getBip32Node( + _mnemonic!, + _mnemonicPassphrase!, + _network, + derivePath, + ); + final data = PaymentData(pubkey: node.publicKey); String address; + isar_models.AddressType addrType; switch (derivePathType) { case DerivePathType.bip44: address = P2PKH(data: data, network: _network).data.address!; + addrType = isar_models.AddressType.p2pkh; break; case DerivePathType.bip84: address = P2WPKH(network: _network, data: data).data.address!; + addrType = isar_models.AddressType.p2wpkh; break; + default: + throw Exception("DerivePathType $derivePathType not supported"); } // add generated address & info to derivations @@ -1531,88 +1471,50 @@ class ParticlWallet extends CoinServiceAPI { derivePathType: derivePathType, ); - return address; - } - - /// Increases the index for either the internal or external chain, depending on [chain]. - /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! - Future _incrementAddressIndexForChain( - int chain, DerivePathType derivePathType) async { - // Here we assume chain == 1 if it isn't 0 - String indexKey = chain == 0 ? "receivingIndex" : "changeIndex"; - switch (derivePathType) { - case DerivePathType.bip44: - indexKey += "P2PKH"; - break; - case DerivePathType.bip84: - indexKey += "P2WPKH"; - break; - } - final newIndex = - (DB.instance.get(boxName: walletId, key: indexKey)) + 1; - await DB.instance - .put(boxName: walletId, key: indexKey, value: newIndex); - } - - /// Adds [address] to the relevant chain's address array, which is determined by [chain]. - /// [address] - Expects a standard native segwit address - /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! - Future _addToAddressesArrayForChain( - String address, int chain, DerivePathType derivePathType) async { - String chainArray = ''; - if (chain == 0) { - chainArray = 'receivingAddresses'; - } else { - chainArray = 'changeAddresses'; - } - switch (derivePathType) { - case DerivePathType.bip44: - chainArray += "P2PKH"; - break; - case DerivePathType.bip84: - chainArray += "P2WPKH"; - break; - } - - final addressArray = - DB.instance.get(boxName: walletId, key: chainArray); - if (addressArray == null) { - Logging.instance.log( - 'Attempting to add the following to $chainArray array for chain $chain:${[ - address - ]}', - level: LogLevel.Info); - await DB.instance - .put(boxName: walletId, key: chainArray, value: [address]); - } else { - // Make a deep copy of the existing list - final List newArray = []; - addressArray - .forEach((dynamic _address) => newArray.add(_address as String)); - newArray.add(address); // Add the address passed into the method - await DB.instance - .put(boxName: walletId, key: chainArray, value: newArray); - } + return isar_models.Address( + walletId: walletId, + derivationIndex: index, + derivationPath: isar_models.DerivationPath()..value = derivePath, + value: address, + publicKey: node.publicKey, + type: addrType, + subType: chain == 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change, + ); } /// Returns the latest receiving/change (external/internal) address for the wallet depending on [chain] /// and /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! Future _getCurrentAddressForChain( - int chain, DerivePathType derivePathType) async { - // Here, we assume that chain == 1 if it isn't 0 - String arrayKey = chain == 0 ? "receivingAddresses" : "changeAddresses"; + int chain, + DerivePathType derivePathType, + ) async { + final subType = chain == 0 // Here, we assume that chain == 1 if it isn't 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change; + + isar_models.AddressType type; + isar_models.Address? address; switch (derivePathType) { case DerivePathType.bip44: - arrayKey += "P2PKH"; + type = isar_models.AddressType.p2pkh; break; case DerivePathType.bip84: - arrayKey += "P2WPKH"; + type = isar_models.AddressType.p2wpkh; break; + default: + throw Exception("DerivePathType $derivePathType not supported"); } - final internalChainArray = - DB.instance.get(boxName: walletId, key: arrayKey); - return internalChainArray.last as String; + address = await db + .getAddresses(walletId) + .filter() + .typeEqualTo(type) + .subTypeEqualTo(subType) + .sortByDerivationIndexDesc() + .findFirst(); + return address!.value; } String _buildDerivationStorageKey({ @@ -1629,6 +1531,8 @@ class ParticlWallet extends CoinServiceAPI { case DerivePathType.bip84: key = "${walletId}_${chainId}DerivationsP2WPKH"; break; + default: + throw Exception("DerivePathType $derivePathType not supported"); } return key; } @@ -1715,8 +1619,8 @@ class ParticlWallet extends CoinServiceAPI { await _secureStore.write(key: key, value: newReceiveDerivationsString); } - Future _fetchUtxoData() async { - final List allAddresses = await _fetchAllOwnAddresses(); + Future _updateUTXOs() async { + final allAddresses = await _fetchAllOwnAddresses(); try { final fetchedUtxoList = >>[]; @@ -1728,7 +1632,8 @@ class ParticlWallet extends CoinServiceAPI { if (batches[batchNumber] == null) { batches[batchNumber] = {}; } - final scripthash = _convertToScriptHash(allAddresses[i], _network); + final scripthash = + _convertToScriptHash(allAddresses[i].value, _network); batches[batchNumber]!.addAll({ scripthash: [scripthash] }); @@ -1746,143 +1651,112 @@ class ParticlWallet extends CoinServiceAPI { } } } - final priceData = - await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final List> outputArray = []; - int satoshiBalance = 0; - int satoshiBalancePending = 0; + + final List outputArray = []; for (int i = 0; i < fetchedUtxoList.length; i++) { for (int j = 0; j < fetchedUtxoList[i].length; j++) { - int value = fetchedUtxoList[i][j]["value"] as int; - satoshiBalance += value; + final jsonUTXO = fetchedUtxoList[i][j]; final txn = await cachedElectrumXClient.getTransaction( - txHash: fetchedUtxoList[i][j]["tx_hash"] as String, + txHash: jsonUTXO["tx_hash"] as String, verbose: true, coin: coin, ); - final Map utxo = {}; - final int confirmations = txn["confirmations"] as int? ?? 0; - final bool confirmed = confirmations >= MINIMUM_CONFIRMATIONS; - if (!confirmed) { - satoshiBalancePending += value; + final vout = jsonUTXO["tx_pos"] as int; + + final outputs = txn["vout"] as List; + + String? utxoOwnerAddress; + // get UTXO owner address + for (final output in outputs) { + if (output["n"] == vout) { + utxoOwnerAddress = + output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]?["address"] as String?; + } } - utxo["txid"] = txn["txid"]; - utxo["vout"] = fetchedUtxoList[i][j]["tx_pos"]; - utxo["value"] = value; + final utxo = isar_models.UTXO( + walletId: walletId, + txid: txn["txid"] as String, + vout: vout, + value: jsonUTXO["value"] as int, + name: "", + isBlocked: false, + blockedReason: null, + isCoinbase: txn["is_coinbase"] as bool? ?? false, + blockHash: txn["blockhash"] as String?, + blockHeight: jsonUTXO["height"] as int?, + blockTime: txn["blocktime"] as int?, + address: utxoOwnerAddress, + ); - utxo["status"] = {}; - utxo["status"]["confirmed"] = confirmed; - utxo["status"]["confirmations"] = confirmations; - utxo["status"]["block_height"] = fetchedUtxoList[i][j]["height"]; - utxo["status"]["block_hash"] = txn["blockhash"]; - utxo["status"]["block_time"] = txn["blocktime"]; - - final fiatValue = ((Decimal.fromInt(value) * currentPrice) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2); - utxo["rawWorth"] = fiatValue; - utxo["fiatWorth"] = fiatValue.toString(); outputArray.add(utxo); } } - Decimal currencyBalanceRaw = - ((Decimal.fromInt(satoshiBalance) * currentPrice) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2); - - final Map result = { - "total_user_currency": currencyBalanceRaw.toString(), - "total_sats": satoshiBalance, - "total_btc": (Decimal.fromInt(satoshiBalance) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal( - scaleOnInfinitePrecision: Constants.decimalPlacesForCoin(coin)) - .toString(), - "outputArray": outputArray, - "unconfirmed": satoshiBalancePending, - }; - - final dataModel = UtxoData.fromJson(result); - - final List allOutputs = dataModel.unspentOutputArray; Logging.instance - .log('Outputs fetched: $allOutputs', level: LogLevel.Info); - await _sortOutputs(allOutputs); - await DB.instance.put( - boxName: walletId, key: 'latest_utxo_model', value: dataModel); - await DB.instance.put( - boxName: walletId, - key: 'totalBalance', - value: dataModel.satoshiBalance); - return dataModel; + .log('Outputs fetched: $outputArray', level: LogLevel.Info); + + await db.updateUTXOs(walletId, outputArray); + + // finally update balance + await _updateBalance(); } catch (e, s) { Logging.instance .log("Output fetch unsuccessful: $e\n$s", level: LogLevel.Error); - final latestTxModel = - DB.instance.get(boxName: walletId, key: 'latest_utxo_model') - as models.UtxoData?; - - if (latestTxModel == null) { - final emptyModel = { - "total_user_currency": "0.00", - "total_sats": 0, - "total_btc": "0", - "outputArray": [] - }; - return UtxoData.fromJson(emptyModel); - } else { - Logging.instance - .log("Old output model located", level: LogLevel.Warning); - return latestTxModel; - } } } - /// Takes in a list of UtxoObjects and adds a name (dependent on object index within list) - /// and checks for the txid associated with the utxo being blocked and marks it accordingly. - /// Now also checks for output labeling. - Future _sortOutputs(List utxos) async { - final blockedHashArray = - DB.instance.get(boxName: walletId, key: 'blocked_tx_hashes') - as List?; - final List lst = []; - if (blockedHashArray != null) { - for (var hash in blockedHashArray) { - lst.add(hash as String); - } - } - final labels = - DB.instance.get(boxName: walletId, key: 'labels') as Map? ?? - {}; - - outputsList = []; - - for (var i = 0; i < utxos.length; i++) { - if (labels[utxos[i].txid] != null) { - utxos[i].txName = labels[utxos[i].txid] as String? ?? ""; - } else { - utxos[i].txName = 'Output #$i'; - } - - if (utxos[i].status.confirmed == false) { - outputsList.add(utxos[i]); - } else { - if (lst.contains(utxos[i].txid)) { - utxos[i].blocked = true; - outputsList.add(utxos[i]); - } else if (!lst.contains(utxos[i].txid)) { - outputsList.add(utxos[i]); - } - } - } + Future _updateBalance() async { + await refreshBalance(); } + @override + Balance get balance => _balance ??= getCachedBalance(); + Balance? _balance; + + // /// Takes in a list of UtxoObjects and adds a name (dependent on object index within list) + // /// and checks for the txid associated with the utxo being blocked and marks it accordingly. + // /// Now also checks for output labeling. + // Future _sortOutputs(List utxos) async { + // final blockedHashArray = + // DB.instance.get(boxName: walletId, key: 'blocked_tx_hashes') + // as List?; + // final List lst = []; + // if (blockedHashArray != null) { + // for (var hash in blockedHashArray) { + // lst.add(hash as String); + // } + // } + // final labels = + // DB.instance.get(boxName: walletId, key: 'labels') as Map? ?? + // {}; + // + // outputsList = []; + // + // for (var i = 0; i < utxos.length; i++) { + // if (labels[utxos[i].txid] != null) { + // utxos[i].txName = labels[utxos[i].txid] as String? ?? ""; + // } else { + // utxos[i].txName = 'Output #$i'; + // } + // + // if (utxos[i].status.confirmed == false) { + // outputsList.add(utxos[i]); + // } else { + // if (lst.contains(utxos[i].txid)) { + // utxos[i].blocked = true; + // outputsList.add(utxos[i]); + // } else if (!lst.contains(utxos[i].txid)) { + // outputsList.add(utxos[i]); + // } + // } + // } + // } + Future getTxCount({required String address}) async { String? scripthash; try { @@ -1921,102 +1795,85 @@ class ParticlWallet extends CoinServiceAPI { } } - Future _checkReceivingAddressForTransactions( - DerivePathType derivePathType) async { + Future _checkReceivingAddressForTransactions() async { try { - final String currentExternalAddr = - await _getCurrentAddressForChain(0, derivePathType); - final int txCount = await getTxCount(address: currentExternalAddr); + final currentReceiving = await _currentReceivingAddress; + + final int txCount = await getTxCount(address: currentReceiving.value); Logging.instance.log( - 'Number of txs for current receiving address $currentExternalAddr: $txCount', + 'Number of txs for current receiving address $currentReceiving: $txCount', level: LogLevel.Info); - if (txCount >= 1) { + if (txCount >= 1 || currentReceiving.derivationIndex < 0) { // First increment the receiving index - await _incrementAddressIndexForChain(0, derivePathType); - - // Check the new receiving index - String indexKey = "receivingIndex"; - switch (derivePathType) { - case DerivePathType.bip44: - indexKey += "P2PKH"; - break; - case DerivePathType.bip84: - indexKey += "P2WPKH"; - break; - } - final newReceivingIndex = - DB.instance.get(boxName: walletId, key: indexKey) as int; + final newReceivingIndex = currentReceiving.derivationIndex + 1; // Use new index to derive a new receiving address final newReceivingAddress = await _generateAddressForChain( - 0, newReceivingIndex, derivePathType); + 0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin)); - // Add that new receiving address to the array of receiving addresses - await _addToAddressesArrayForChain( - newReceivingAddress, 0, derivePathType); - - // Set the new receiving address that the service - - switch (derivePathType) { - case DerivePathType.bip44: - _currentReceivingAddressP2PKH = Future(() => newReceivingAddress); - break; - case DerivePathType.bip84: - _currentReceivingAddress = Future(() => newReceivingAddress); - break; + final existing = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(newReceivingAddress.value) + .findFirst(); + if (existing == null) { + // Add that new change address + await db.putAddress(newReceivingAddress); + } else { + // we need to update the address + await db.updateAddress(existing, newReceivingAddress); } + // keep checking until address with no tx history is set as current + await _checkReceivingAddressForTransactions(); } } catch (e, s) { Logging.instance.log( - "Exception rethrown from _checkReceivingAddressForTransactions($derivePathType): $e\n$s", + "Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s", level: LogLevel.Error); rethrow; } } - Future _checkChangeAddressForTransactions( - DerivePathType derivePathType) async { + Future _checkChangeAddressForTransactions() async { try { - final String currentExternalAddr = - await _getCurrentAddressForChain(1, derivePathType); - final int txCount = await getTxCount(address: currentExternalAddr); + final currentChange = await _currentChangeAddress; + final int txCount = await getTxCount(address: currentChange.value); Logging.instance.log( - 'Number of txs for current change address $currentExternalAddr: $txCount', + 'Number of txs for current change address $currentChange: $txCount', level: LogLevel.Info); - if (txCount >= 1) { + if (txCount >= 1 || currentChange.derivationIndex < 0) { // First increment the change index - await _incrementAddressIndexForChain(1, derivePathType); - - // Check the new change index - String indexKey = "changeIndex"; - switch (derivePathType) { - case DerivePathType.bip44: - indexKey += "P2PKH"; - break; - case DerivePathType.bip84: - indexKey += "P2WPKH"; - break; - } - final newChangeIndex = - DB.instance.get(boxName: walletId, key: indexKey) as int; + final newChangeIndex = currentChange.derivationIndex + 1; // Use new index to derive a new change address - final newChangeAddress = - await _generateAddressForChain(1, newChangeIndex, derivePathType); + final newChangeAddress = await _generateAddressForChain( + 1, newChangeIndex, DerivePathTypeExt.primaryFor(coin)); - // Add that new receiving address to the array of change addresses - await _addToAddressesArrayForChain(newChangeAddress, 1, derivePathType); + final existing = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(newChangeAddress.value) + .findFirst(); + if (existing == null) { + // Add that new change address + await db.putAddress(newChangeAddress); + } else { + // we need to update the address + await db.updateAddress(existing, newChangeAddress); + } + // keep checking until address with no tx history is set as current + await _checkChangeAddressForTransactions(); } } on SocketException catch (se, s) { Logging.instance.log( - "SocketException caught in _checkReceivingAddressForTransactions($derivePathType): $se\n$s", + "SocketException caught in _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $se\n$s", level: LogLevel.Error); return; } catch (e, s) { Logging.instance.log( - "Exception rethrown from _checkReceivingAddressForTransactions($derivePathType): $e\n$s", + "Exception rethrown from _checkReceivingAddressForTransactions(${DerivePathTypeExt.primaryFor(coin)}): $e\n$s", level: LogLevel.Error); rethrow; } @@ -2024,9 +1881,9 @@ class ParticlWallet extends CoinServiceAPI { Future _checkCurrentReceivingAddressesForTransactions() async { try { - for (final type in DerivePathType.values) { - await _checkReceivingAddressForTransactions(type); - } + // for (final type in DerivePathType.values) { + await _checkReceivingAddressForTransactions(); + // } } catch (e, s) { Logging.instance.log( "Exception rethrown from _checkCurrentReceivingAddressesForTransactions(): $e\n$s", @@ -2048,9 +1905,9 @@ class ParticlWallet extends CoinServiceAPI { Future _checkCurrentChangeAddressesForTransactions() async { try { - for (final type in DerivePathType.values) { - await _checkChangeAddressForTransactions(type); - } + // for (final type in DerivePathType.values) { + await _checkChangeAddressForTransactions(); + // } } catch (e, s) { Logging.instance.log( "Exception rethrown from _checkCurrentChangeAddressesForTransactions(): $e\n$s", @@ -2184,39 +2041,16 @@ class ParticlWallet extends CoinServiceAPI { return allTransactions; } - Future _fetchTransactionData() async { - final List allAddresses = await _fetchAllOwnAddresses(); + Future _refreshTransactions() async { + final allAddresses = await _fetchAllOwnAddresses(); - final changeAddresses = DB.instance.get( - boxName: walletId, key: 'changeAddressesP2WPKH') as List; + List changeAddresses = allAddresses + .where((e) => e.subType == isar_models.AddressSubType.change) + .map((e) => e.value) + .toList(); - final List> allTxHashes = - await _fetchHistory(allAddresses); - - final cachedTransactions = - DB.instance.get(boxName: walletId, key: 'latest_tx_model') - as TransactionData?; - int latestTxnBlockHeight = - DB.instance.get(boxName: walletId, key: "storedTxnDataHeight") - as int? ?? - 0; - - final unconfirmedCachedTransactions = - cachedTransactions?.getAllTransactions() ?? {}; - unconfirmedCachedTransactions - .removeWhere((key, value) => value.confirmedStatus); - - if (cachedTransactions != null) { - for (final tx in allTxHashes.toList(growable: false)) { - final txHeight = tx["height"] as int; - if (txHeight > 0 && - txHeight < latestTxnBlockHeight - MINIMUM_CONFIRMATIONS) { - if (unconfirmedCachedTransactions[tx["tx_hash"] as String] == null) { - allTxHashes.remove(tx); - } - } - } - } + final List> allTxHashes = await _fetchHistory( + allAddresses.map((e) => e.value).toList(growable: false)); Set hashes = {}; for (var element in allTxHashes) { @@ -2224,33 +2058,44 @@ class ParticlWallet extends CoinServiceAPI { } await fastFetch(hashes.toList()); List> allTransactions = []; + final currentHeight = await chainHeight; for (final txHash in allTxHashes) { - final tx = await cachedElectrumXClient.getTransaction( - txHash: txHash["tx_hash"] as String, - verbose: true, - coin: coin, - ); + final storedTx = await db + .getTransactions(walletId) + .filter() + .txidEqualTo(txHash["tx_hash"] as String) + .findFirst(); - // Logging.instance.log("TRANSACTION: ${jsonEncode(tx)}"); - // TODO fix this for sent to self transactions? - if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { - tx["address"] = txHash["address"]; - tx["height"] = txHash["height"]; - allTransactions.add(tx); + if (storedTx == null || + !storedTx.isConfirmed(currentHeight, MINIMUM_CONFIRMATIONS)) { + final tx = await cachedElectrumXClient.getTransaction( + txHash: txHash["tx_hash"] as String, + verbose: true, + coin: coin, + ); + + if (!_duplicateTxCheck(allTransactions, tx["txid"] as String)) { + tx["address"] = (await db + .getAddresses(walletId) + .filter() + .valueEqualTo(txHash["address"] as String) + .findFirst())!; + tx["height"] = txHash["height"]; + allTransactions.add(tx); + } } } - Logging.instance.log("addAddresses: $allAddresses", level: LogLevel.Info); - Logging.instance.log("allTxHashes: $allTxHashes", level: LogLevel.Info); + Logging.instance.log("addAddresses: $allAddresses", + level: LogLevel.Info, printFullLength: true); + Logging.instance.log("allTxHashes: $allTxHashes", + level: LogLevel.Info, printFullLength: true); Logging.instance.log("allTransactions length: ${allTransactions.length}", level: LogLevel.Info); - final priceData = - await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final List> midSortedArray = []; + // final List> midSortedArray = []; Set vHashes = {}; for (final txObject in allTransactions) { @@ -2262,6 +2107,8 @@ class ParticlWallet extends CoinServiceAPI { } await fastFetch(vHashes.toList()); + final List> txns = []; + for (final txObject in allTransactions) { List sendersArray = []; List recipientsArray = []; @@ -2286,7 +2133,8 @@ class ParticlWallet extends CoinServiceAPI { for (final out in tx["vout"] as List) { if (prevOut == out["n"]) { - final address = out["scriptPubKey"]["address"] as String?; + final address = out["scriptPubKey"]?["address"] as String? ?? + out["scriptPubKey"]?["addresses"]?[0] as String?; if (address != null) { sendersArray.add(address); } @@ -2300,7 +2148,8 @@ class ParticlWallet extends CoinServiceAPI { // Particl has different tx types that need to be detected and handled here if (output.containsKey('scriptPubKey') as bool) { // Logging.instance.log("output is transparent", level: LogLevel.Info); - final address = output["scriptPubKey"]["address"] as String?; + final address = output["scriptPubKey"]?["address"] as String? ?? + output["scriptPubKey"]?["addresses"]?[0] as String?; if (address != null) { recipientsArray.add(address); } @@ -2313,7 +2162,7 @@ class ParticlWallet extends CoinServiceAPI { .log("output is private (RingCT)", level: LogLevel.Info); } else { // TODO detect staking - Logging.instance.log("output type not detected; output: ${output}", + Logging.instance.log("output type not detected; output: $output", level: LogLevel.Info); } } @@ -2322,7 +2171,7 @@ class ParticlWallet extends CoinServiceAPI { .log("recipientsArray: $recipientsArray", level: LogLevel.Info); final foundInSenders = - allAddresses.any((element) => sendersArray.contains(element)); + allAddresses.any((element) => sendersArray.contains(element.value)); Logging.instance .log("foundInSenders: $foundInSenders", level: LogLevel.Info); @@ -2351,40 +2200,58 @@ class ParticlWallet extends CoinServiceAPI { totalInput = inputAmtSentFromWallet; int totalOutput = 0; - Logging.instance.log("txObject: ${txObject}", level: LogLevel.Info); + Logging.instance.log("txObject: $txObject", level: LogLevel.Info); for (final output in txObject["vout"] as List) { // Particl has different tx types that need to be detected and handled here if (output.containsKey('scriptPubKey') as bool) { - // Logging.instance.log("output is transparent", level: LogLevel.Info); - final String address = output["scriptPubKey"]!["address"] as String; - final value = output["value"]!; - final _value = (Decimal.parse(value.toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - totalOutput += _value; - if (changeAddresses.contains(address)) { - inputAmtSentFromWallet -= _value; - } else { - // change address from 'sent from' to the 'sent to' address - txObject["address"] = address; + try { + final String address = + output["scriptPubKey"]!["addresses"][0] as String; + final value = output["value"]!; + final _value = (Decimal.parse(value.toString()) * + Decimal.fromInt(Constants.satsPerCoin(coin))) + .toBigInt() + .toInt(); + totalOutput += _value; + if (changeAddresses.contains(address)) { + inputAmtSentFromWallet -= _value; + } else { + // change address from 'sent from' to the 'sent to' address + txObject["address"] = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(address) + .findFirst() ?? + isar_models.Address( + walletId: walletId, + type: isar_models.AddressType.nonWallet, + subType: isar_models.AddressSubType.nonWallet, + value: address, + publicKey: [], + derivationIndex: -1, + derivationPath: null, + ); + } + } catch (s) { + Logging.instance.log(s.toString(), level: LogLevel.Warning); } + // Logging.instance.log("output is transparent", level: LogLevel.Info); } else if (output.containsKey('ct_fee') as bool) { // or type: data // TODO handle CT tx Logging.instance.log( "output is blinded (CT); cannot parse output values", level: LogLevel.Info); - final ct_fee = output["ct_fee"]!; - final fee_value = (Decimal.parse(ct_fee.toString()) * + final ctFee = output["ct_fee"]!; + final feeValue = (Decimal.parse(ctFee.toString()) * Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); Logging.instance.log( - "ct_fee ${ct_fee} subtracted from inputAmtSentFromWallet ${inputAmtSentFromWallet}", + "ct_fee $ctFee subtracted from inputAmtSentFromWallet $inputAmtSentFromWallet", level: LogLevel.Info); - inputAmtSentFromWallet += fee_value; + inputAmtSentFromWallet += feeValue; } else if (output.containsKey('rangeproof') as bool) { // or valueCommitment or type: anon // TODO handle RingCT tx @@ -2393,7 +2260,7 @@ class ParticlWallet extends CoinServiceAPI { level: LogLevel.Info); } else { // TODO detect staking - Logging.instance.log("output type not detected; output: ${output}", + Logging.instance.log("output type not detected; output: $output", level: LogLevel.Info); } } @@ -2408,16 +2275,21 @@ class ParticlWallet extends CoinServiceAPI { // add up received tx value for (final output in txObject["vout"] as List) { - final address = output["scriptPubKey"]["address"]; - if (address != null) { - final value = (Decimal.parse(output["value"].toString()) * - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toBigInt() - .toInt(); - totalOut += value; - if (allAddresses.contains(address)) { - outputAmtAddressedToWallet += value; + try { + final address = output["scriptPubKey"]?["address"] as String? ?? + output["scriptPubKey"]?["addresses"]?[0] as String?; + if (address != null) { + final value = (Decimal.parse((output["value"] ?? 0).toString()) * + Decimal.fromInt(Constants.satsPerCoin(coin))) + .toBigInt() + .toInt(); + totalOut += value; + if (allAddresses.where((e) => e.value == address).isNotEmpty) { + outputAmtAddressedToWallet += value; + } } + } catch (s) { + Logging.instance.log(s.toString(), level: LogLevel.Info); } } @@ -2433,7 +2305,7 @@ class ParticlWallet extends CoinServiceAPI { for (final out in tx["vout"] as List) { if (prevOut == out["n"]) { - totalIn += (Decimal.parse(out["value"].toString()) * + totalIn += (Decimal.parse((out["value"] ?? 0).toString()) * Decimal.fromInt(Constants.satsPerCoin(coin))) .toBigInt() .toInt(); @@ -2445,112 +2317,99 @@ class ParticlWallet extends CoinServiceAPI { // create final tx map midSortedTx["txid"] = txObject["txid"]; - midSortedTx["confirmed_status"] = (txObject["confirmations"] != null) && - (txObject["confirmations"] as int >= MINIMUM_CONFIRMATIONS); - midSortedTx["confirmations"] = txObject["confirmations"] ?? 0; + midSortedTx["timestamp"] = txObject["blocktime"] ?? (DateTime.now().millisecondsSinceEpoch ~/ 1000); - if (foundInSenders) { - midSortedTx["txType"] = "Sent"; - midSortedTx["amount"] = inputAmtSentFromWallet; - final String worthNow = - ((currentPrice * Decimal.fromInt(inputAmtSentFromWallet)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2) - .toStringAsFixed(2); - midSortedTx["worthNow"] = worthNow; - midSortedTx["worthAtBlockTimestamp"] = worthNow; - } else { - midSortedTx["txType"] = "Received"; - midSortedTx["amount"] = outputAmtAddressedToWallet; - final worthNow = - ((currentPrice * Decimal.fromInt(outputAmtAddressedToWallet)) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal(scaleOnInfinitePrecision: 2) - .toStringAsFixed(2); - midSortedTx["worthNow"] = worthNow; - } - midSortedTx["aliens"] = []; - midSortedTx["fees"] = fee; midSortedTx["address"] = txObject["address"]; - midSortedTx["inputSize"] = txObject["vin"].length; - midSortedTx["outputSize"] = txObject["vout"].length; midSortedTx["inputs"] = txObject["vin"]; midSortedTx["outputs"] = txObject["vout"]; - final int height = txObject["height"] as int; - midSortedTx["height"] = height; - - if (height >= latestTxnBlockHeight) { - latestTxnBlockHeight = height; - } - - midSortedArray.add(midSortedTx); - } - - // sort by date ---- //TODO not sure if needed - // shouldn't be any issues with a null timestamp but I got one at some point? - midSortedArray - .sort((a, b) => (b["timestamp"] as int) - (a["timestamp"] as int)); - // { - // final aT = a["timestamp"]; - // final bT = b["timestamp"]; - // - // if (aT == null && bT == null) { - // return 0; - // } else if (aT == null) { - // return -1; - // } else if (bT == null) { - // return 1; - // } else { - // return bT - aT; - // } - // }); - - // buildDateTimeChunks - final Map result = {"dateTimeChunks": []}; - final dateArray = []; - - for (int i = 0; i < midSortedArray.length; i++) { - final txObject = midSortedArray[i]; - final date = extractDateFromTimestamp(txObject["timestamp"] as int); - final txTimeArray = [txObject["timestamp"], date]; - - if (dateArray.contains(txTimeArray[1])) { - result["dateTimeChunks"].forEach((dynamic chunk) { - if (extractDateFromTimestamp(chunk["timestamp"] as int) == - txTimeArray[1]) { - if (chunk["transactions"] == null) { - chunk["transactions"] = >[]; - } - chunk["transactions"].add(txObject); - } - }); + // midSortedArray.add(midSortedTx); + isar_models.TransactionType type; + int amount; + if (foundInSenders) { + type = isar_models.TransactionType.outgoing; + amount = inputAmtSentFromWallet; } else { - dateArray.add(txTimeArray[1]); - final chunk = { - "timestamp": txTimeArray[0], - "transactions": [txObject], - }; - result["dateTimeChunks"].add(chunk); + type = isar_models.TransactionType.incoming; + amount = outputAmtAddressedToWallet; } + + isar_models.Address transactionAddress = + midSortedTx["address"] as isar_models.Address; + + List inputs = []; + List outputs = []; + + for (final json in txObject["vin"] as List) { + bool isCoinBase = json['coinbase'] != null; + final input = isar_models.Input( + txid: json['txid'] as String, + vout: json['vout'] as int? ?? -1, + scriptSig: json['scriptSig']?['hex'] as String?, + scriptSigAsm: json['scriptSig']?['asm'] as String?, + isCoinbase: isCoinBase ? isCoinBase : json['is_coinbase'] as bool?, + sequence: json['sequence'] as int?, + innerRedeemScriptAsm: json['innerRedeemscriptAsm'] as String?, + ); + inputs.add(input); + } + + for (final json in txObject["vout"] as List) { + final output = isar_models.Output( + scriptPubKey: json['scriptPubKey']?['hex'] as String?, + scriptPubKeyAsm: json['scriptPubKey']?['asm'] as String?, + scriptPubKeyType: json['scriptPubKey']?['type'] as String?, + scriptPubKeyAddress: + json["scriptPubKey"]?["addresses"]?[0] as String? ?? + json['scriptPubKey']?['type'] as String? ?? + "", + value: Amount.fromDecimal( + Decimal.parse((json["value"] ?? 0).toString()), + fractionDigits: coin.decimals, + ).raw.toInt(), + ); + outputs.add(output); + } + + final tx = isar_models.Transaction( + walletId: walletId, + txid: midSortedTx["txid"] as String, + timestamp: midSortedTx["timestamp"] as int, + type: type, + subType: isar_models.TransactionSubType.none, + amount: amount, + amountString: Amount( + rawValue: BigInt.from(amount), + fractionDigits: coin.decimals, + ).toJsonString(), + fee: fee, + height: txObject["height"] as int, + inputs: inputs, + outputs: outputs, + isCancelled: false, + isLelantus: false, + nonce: null, + slateId: null, + otherData: null, + ); + + txns.add(Tuple2(tx, transactionAddress)); } - final transactionsMap = cachedTransactions?.getAllTransactions() ?? {}; - transactionsMap - .addAll(TransactionData.fromJson(result).getAllTransactions()); + await db.addNewTransactionData(txns, walletId); - final txModel = TransactionData.fromMap(transactionsMap); - - await DB.instance.put( - boxName: walletId, - key: 'storedTxnDataHeight', - value: latestTxnBlockHeight); - await DB.instance.put( - boxName: walletId, key: 'latest_tx_model', value: txModel); - - return txModel; + // quick hack to notify manager to call notifyListeners if + // transactions changed + if (txns.isNotEmpty) { + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "Transactions updated/added for: $walletId $walletName ", + walletId, + ), + ); + } } int estimateTxFee({required int vSize, required int feeRatePerKB}) { @@ -2561,32 +2420,43 @@ class ParticlWallet extends CoinServiceAPI { /// with [satoshiAmountToSend] and [selectedTxFeeRate]. If so, it will call buildTrasaction() and return /// a map containing the tx hex along with other important information. If not, then it will return /// an integer (1 or 2) - dynamic coinSelection( - int satoshiAmountToSend, - int selectedTxFeeRate, - String _recipientAddress, - bool isSendAll, { + dynamic coinSelection({ + required int satoshiAmountToSend, + required int selectedTxFeeRate, + required String recipientAddress, + required bool coinControl, + required bool isSendAll, int additionalOutputs = 0, - List? utxos, + List? utxos, }) async { Logging.instance .log("Starting coinSelection ----------", level: LogLevel.Info); - final List availableOutputs = utxos ?? outputsList; - final List spendableOutputs = []; + final List availableOutputs = utxos ?? await this.utxos; + final currentChainHeight = await chainHeight; + final List spendableOutputs = []; int spendableSatoshiValue = 0; - print("AVAILABLE UTXOS IS ::::: ${availableOutputs}"); + // Build list of spendable outputs and totaling their satoshi amount - for (var i = 0; i < availableOutputs.length; i++) { - if (availableOutputs[i].blocked == false && - availableOutputs[i].status.confirmed == true) { - spendableOutputs.add(availableOutputs[i]); - spendableSatoshiValue += availableOutputs[i].value; + for (final utxo in availableOutputs) { + if (utxo.isBlocked == false && + utxo.isConfirmed(currentChainHeight, MINIMUM_CONFIRMATIONS) && + utxo.used != true) { + spendableOutputs.add(utxo); + spendableSatoshiValue += utxo.value; } } - // sort spendable by age (oldest first) - spendableOutputs.sort( - (a, b) => b.status.confirmations.compareTo(a.status.confirmations)); + if (coinControl) { + if (spendableOutputs.length < availableOutputs.length) { + throw ArgumentError("Attempted to use an unavailable utxo"); + } + } + + // don't care about sorting if using all utxos + if (!coinControl) { + // sort spendable by age (oldest first) + spendableOutputs.sort((a, b) => b.blockTime!.compareTo(a.blockTime!)); + } Logging.instance.log("spendableOutputs.length: ${spendableOutputs.length}", level: LogLevel.Info); @@ -2613,21 +2483,29 @@ class ParticlWallet extends CoinServiceAPI { // Possible situation right here int satoshisBeingUsed = 0; int inputsBeingConsumed = 0; - List utxoObjectsToUse = []; + List utxoObjectsToUse = []; - for (var i = 0; - satoshisBeingUsed < satoshiAmountToSend && i < spendableOutputs.length; - i++) { - utxoObjectsToUse.add(spendableOutputs[i]); - satoshisBeingUsed += spendableOutputs[i].value; - inputsBeingConsumed += 1; - } - for (int i = 0; - i < additionalOutputs && inputsBeingConsumed < spendableOutputs.length; - i++) { - utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]); - satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value; - inputsBeingConsumed += 1; + if (!coinControl) { + for (var i = 0; + satoshisBeingUsed < satoshiAmountToSend && + i < spendableOutputs.length; + i++) { + utxoObjectsToUse.add(spendableOutputs[i]); + satoshisBeingUsed += spendableOutputs[i].value; + inputsBeingConsumed += 1; + } + for (int i = 0; + i < additionalOutputs && + inputsBeingConsumed < spendableOutputs.length; + i++) { + utxoObjectsToUse.add(spendableOutputs[inputsBeingConsumed]); + satoshisBeingUsed += spendableOutputs[inputsBeingConsumed].value; + inputsBeingConsumed += 1; + } + } else { + satoshisBeingUsed = spendableSatoshiValue; + utxoObjectsToUse = spendableOutputs; + inputsBeingConsumed = spendableOutputs.length; } Logging.instance @@ -2638,7 +2516,7 @@ class ParticlWallet extends CoinServiceAPI { .log('utxoObjectsToUse: $utxoObjectsToUse', level: LogLevel.Info); // numberOfOutputs' length must always be equal to that of recipientsArray and recipientsAmtArray - List recipientsArray = [_recipientAddress]; + List recipientsArray = [recipientAddress]; List recipientsAmtArray = [satoshiAmountToSend]; // gather required signing data @@ -2649,9 +2527,8 @@ class ParticlWallet extends CoinServiceAPI { .log("Attempting to send all $coin", level: LogLevel.Info); final int vSizeForOneOutput = (await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, - recipients: [_recipientAddress], + recipients: [recipientAddress], satoshiAmounts: [satoshisBeingUsed - 1], ))["vSize"] as int; int feeForOneOutput = estimateTxFee( @@ -2659,15 +2536,17 @@ class ParticlWallet extends CoinServiceAPI { feeRatePerKB: selectedTxFeeRate, ); - final int roughEstimate = - roughFeeEstimate(spendableOutputs.length, 1, selectedTxFeeRate); + final int roughEstimate = roughFeeEstimate( + spendableOutputs.length, + 1, + selectedTxFeeRate, + ).raw.toInt(); if (feeForOneOutput < roughEstimate) { feeForOneOutput = roughEstimate; } final int amount = satoshiAmountToSend - feeForOneOutput; dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: [amount], @@ -2675,25 +2554,27 @@ class ParticlWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": amount, + "recipientAmt": Amount( + rawValue: BigInt.from(amount), + fractionDigits: coin.decimals, + ), "fee": feeForOneOutput, "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), }; return transactionObject; } final int vSizeForOneOutput = (await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, - recipients: [_recipientAddress], + recipients: [recipientAddress], satoshiAmounts: [satoshisBeingUsed - 1], ))["vSize"] as int; final int vSizeForTwoOutPuts = (await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: [ - _recipientAddress, - await _getCurrentAddressForChain(1, DerivePathType.bip84), + recipientAddress, + await _getCurrentAddressForChain(1, DerivePathTypeExt.primaryFor(coin)), ], satoshiAmounts: [ satoshiAmountToSend, @@ -2719,7 +2600,7 @@ class ParticlWallet extends CoinServiceAPI { if (satoshisBeingUsed - satoshiAmountToSend > feeForOneOutput) { if (satoshisBeingUsed - satoshiAmountToSend > - feeForOneOutput + DUST_LIMIT) { + feeForOneOutput + DUST_LIMIT.raw.toInt()) { // Here, we know that theoretically, we may be able to include another output(change) but we first need to // factor in the value of this output in satoshis. int changeOutputSize = @@ -2727,13 +2608,13 @@ class ParticlWallet extends CoinServiceAPI { // We check to see if the user can pay for the new transaction with 2 outputs instead of one. If they can and // the second output's size > DUST_LIMIT satoshis, we perform the mechanics required to properly generate and use a new // change address. - if (changeOutputSize > DUST_LIMIT && + if (changeOutputSize > DUST_LIMIT.raw.toInt() && satoshisBeingUsed - satoshiAmountToSend - changeOutputSize == feeForTwoOutputs) { // generate new change address if current change address has been used - await _checkChangeAddressForTransactions(DerivePathType.bip84); - final String newChangeAddress = - await _getCurrentAddressForChain(1, DerivePathType.bip84); + await _checkChangeAddressForTransactions(); + final String newChangeAddress = await _getCurrentAddressForChain( + 1, DerivePathTypeExt.primaryFor(coin)); int feeBeingPaid = satoshisBeingUsed - satoshiAmountToSend - changeOutputSize; @@ -2755,7 +2636,6 @@ class ParticlWallet extends CoinServiceAPI { Logging.instance .log('Estimated fee: $feeForTwoOutputs', level: LogLevel.Info); dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -2783,7 +2663,6 @@ class ParticlWallet extends CoinServiceAPI { Logging.instance.log('Adjusted Estimated fee: $feeForTwoOutputs', level: LogLevel.Info); txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -2793,9 +2672,13 @@ class ParticlWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": feeBeingPaid, "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), }; return transactionObject; } else { @@ -2812,7 +2695,6 @@ class ParticlWallet extends CoinServiceAPI { Logging.instance .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -2820,9 +2702,13 @@ class ParticlWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": satoshisBeingUsed - satoshiAmountToSend, "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), }; return transactionObject; } @@ -2841,7 +2727,6 @@ class ParticlWallet extends CoinServiceAPI { Logging.instance .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -2849,9 +2734,13 @@ class ParticlWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": satoshisBeingUsed - satoshiAmountToSend, "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), }; return transactionObject; } @@ -2870,7 +2759,6 @@ class ParticlWallet extends CoinServiceAPI { Logging.instance .log('Estimated fee: $feeForOneOutput', level: LogLevel.Info); dynamic txn = await buildTransaction( - utxosToUse: utxoObjectsToUse, utxoSigningData: utxoSigningData, recipients: recipientsArray, satoshiAmounts: recipientsAmtArray, @@ -2878,9 +2766,13 @@ class ParticlWallet extends CoinServiceAPI { Map transactionObject = { "hex": txn["hex"], "recipient": recipientsArray[0], - "recipientAmt": recipientsAmtArray[0], + "recipientAmt": Amount( + rawValue: BigInt.from(recipientsAmtArray[0]), + fractionDigits: coin.decimals, + ), "fee": feeForOneOutput, "vSize": txn["vSize"], + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), }; return transactionObject; } else { @@ -2892,176 +2784,144 @@ class ParticlWallet extends CoinServiceAPI { level: LogLevel.Warning); // try adding more outputs if (spendableOutputs.length > inputsBeingConsumed) { - return coinSelection(satoshiAmountToSend, selectedTxFeeRate, - _recipientAddress, isSendAll, - additionalOutputs: additionalOutputs + 1, utxos: utxos); + return coinSelection( + satoshiAmountToSend: satoshiAmountToSend, + selectedTxFeeRate: selectedTxFeeRate, + recipientAddress: recipientAddress, + isSendAll: isSendAll, + additionalOutputs: additionalOutputs + 1, + utxos: utxos, + coinControl: coinControl, + ); } return 2; } } - Future> fetchBuildTxData( - List utxosToUse, + Future> fetchBuildTxData( + List utxosToUse, ) async { // return data - Map results = {}; - Map> addressTxid = {}; - - print("CALLING FETCH BUILD TX DATA"); - - // addresses to check - List addressesP2PKH = []; - List addressesP2WPKH = []; + List signingData = []; try { // Populating the addresses to check for (var i = 0; i < utxosToUse.length; i++) { - final txid = utxosToUse[i].txid; - final tx = await _cachedElectrumXClient.getTransaction( - txHash: txid, - coin: coin, - ); - - for (final output in tx["vout"] as List) { - final n = output["n"]; - if (n != null && n == utxosToUse[i].vout) { - print("SCRIPT PUB KEY IS ${output["scriptPubKey"]}"); - final address = output["scriptPubKey"]["address"] as String; - if (!addressTxid.containsKey(address)) { - addressTxid[address] = []; - } - (addressTxid[address] as List).add(txid); - switch (addressType(address: address)) { - case DerivePathType.bip44: - addressesP2PKH.add(address); - break; - case DerivePathType.bip84: - addressesP2WPKH.add(address); - break; + if (utxosToUse[i].address == null) { + final txid = utxosToUse[i].txid; + final tx = await _cachedElectrumXClient.getTransaction( + txHash: txid, + coin: coin, + ); + for (final output in tx["vout"] as List) { + final n = output["n"]; + if (n != null && n == utxosToUse[i].vout) { + utxosToUse[i] = utxosToUse[i].copyWith( + address: output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]["address"] as String, + ); } } } + + final derivePathType = addressType(address: utxosToUse[i].address!); + + signingData.add( + SigningData( + derivePathType: derivePathType, + utxo: utxosToUse[i], + ), + ); } - // p2pkh / bip44 - final p2pkhLength = addressesP2PKH.length; - if (p2pkhLength > 0) { - final receiveDerivations = await _fetchDerivations( - chain: 0, - derivePathType: DerivePathType.bip44, - ); - final changeDerivations = await _fetchDerivations( - chain: 1, - derivePathType: DerivePathType.bip44, - ); - for (int i = 0; i < p2pkhLength; i++) { - // receives - final receiveDerivation = receiveDerivations[addressesP2PKH[i]]; - // if a match exists it will not be null - if (receiveDerivation != null) { - final data = P2PKH( - data: PaymentData( - pubkey: Format.stringToUint8List( - receiveDerivation["pubKey"] as String)), - network: _network, - ).data; + Map> receiveDerivations = {}; + Map> changeDerivations = {}; - for (String tx in addressTxid[addressesP2PKH[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - receiveDerivation["wif"] as String, - network: _network, - ), - }; - } - } else { - // if its not a receive, check change - final changeDerivation = changeDerivations[addressesP2PKH[i]]; - // if a match exists it will not be null - if (changeDerivation != null) { - final data = P2PKH( + for (final sd in signingData) { + String? pubKey; + String? wif; + + // fetch receiving derivations if null + receiveDerivations[sd.derivePathType] ??= await _fetchDerivations( + chain: 0, + derivePathType: sd.derivePathType, + ); + final receiveDerivation = + receiveDerivations[sd.derivePathType]![sd.utxo.address!]; + + if (receiveDerivation != null) { + pubKey = receiveDerivation["pubKey"] as String; + wif = receiveDerivation["wif"] as String; + } else { + // fetch change derivations if null + changeDerivations[sd.derivePathType] ??= await _fetchDerivations( + chain: 1, + derivePathType: sd.derivePathType, + ); + final changeDerivation = + changeDerivations[sd.derivePathType]![sd.utxo.address!]; + if (changeDerivation != null) { + pubKey = changeDerivation["pubKey"] as String; + wif = changeDerivation["wif"] as String; + } + } + + if (wif == null || pubKey == null) { + final address = await db.getAddress(walletId, sd.utxo.address!); + if (address?.derivationPath != null) { + final node = await Bip32Utils.getBip32Node( + (await mnemonicString)!, + (await mnemonicPassphrase)!, + _network, + address!.derivationPath!.value, + ); + + wif = node.toWIF(); + pubKey = Format.uint8listToString(node.publicKey); + } + } + + if (wif != null && pubKey != null) { + final PaymentData data; + final Uint8List? redeemScript; + + switch (sd.derivePathType) { + case DerivePathType.bip44: + data = P2PKH( data: PaymentData( - pubkey: Format.stringToUint8List( - changeDerivation["pubKey"] as String)), + pubkey: Format.stringToUint8List(pubKey), + ), network: _network, ).data; + redeemScript = null; + break; - for (String tx in addressTxid[addressesP2PKH[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - changeDerivation["wif"] as String, - network: _network, - ), - }; - } - } - } - } - } - - // p2wpkh / bip84 - final p2wpkhLength = addressesP2WPKH.length; - if (p2wpkhLength > 0) { - final receiveDerivations = await _fetchDerivations( - chain: 0, - derivePathType: DerivePathType.bip84, - ); - final changeDerivations = await _fetchDerivations( - chain: 1, - derivePathType: DerivePathType.bip84, - ); - - for (int i = 0; i < p2wpkhLength; i++) { - // receives - final receiveDerivation = receiveDerivations[addressesP2WPKH[i]]; - // if a match exists it will not be null - if (receiveDerivation != null) { - final data = P2WPKH( - data: PaymentData( - pubkey: Format.stringToUint8List( - receiveDerivation["pubKey"] as String)), - network: _network, - ).data; - - for (String tx in addressTxid[addressesP2WPKH[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - receiveDerivation["wif"] as String, - network: _network, - ), - }; - } - } else { - // if its not a receive, check change - final changeDerivation = changeDerivations[addressesP2WPKH[i]]; - // if a match exists it will not be null - if (changeDerivation != null) { - final data = P2WPKH( + case DerivePathType.bip84: + data = P2WPKH( data: PaymentData( - pubkey: Format.stringToUint8List( - changeDerivation["pubKey"] as String)), + pubkey: Format.stringToUint8List(pubKey), + ), network: _network, ).data; + redeemScript = null; + break; - for (String tx in addressTxid[addressesP2WPKH[i]]!) { - results[tx] = { - "output": data.output, - "keyPair": ECPair.fromWIF( - changeDerivation["wif"] as String, - network: _network, - ), - }; - } - } + default: + throw Exception("DerivePathType unsupported"); } + + final keyPair = ECPair.fromWIF( + wif, + network: _network, + ); + + sd.redeemScript = redeemScript; + sd.output = data.output; + sd.keyPair = keyPair; } } - Logging.instance.log("FETCHED TX BUILD DATA IS -----$results", - level: LogLevel.Info, printFullLength: true); - return results; + + return signingData; } catch (e, s) { Logging.instance .log("fetchBuildTxData() threw: $e,\n$s", level: LogLevel.Error); @@ -3071,8 +2931,7 @@ class ParticlWallet extends CoinServiceAPI { /// Builds and signs a transaction Future> buildTransaction({ - required List utxosToUse, - required Map utxoSigningData, + required List utxoSigningData, required List recipients, required List satoshiAmounts, }) async { @@ -3086,10 +2945,15 @@ class ParticlWallet extends CoinServiceAPI { txb.setVersion(160); // Add transaction inputs - for (var i = 0; i < utxosToUse.length; i++) { - final txid = utxosToUse[i].txid; - txb.addInput(txid, utxosToUse[i].vout, null, - utxoSigningData[txid]["output"] as Uint8List, ''); + for (var i = 0; i < utxoSigningData.length; i++) { + final txid = utxoSigningData[i].utxo.txid; + txb.addInput( + txid, + utxoSigningData[i].utxo.vout, + null, + utxoSigningData[i].output!, + '', + ); } // Add transaction output @@ -3099,13 +2963,13 @@ class ParticlWallet extends CoinServiceAPI { try { // Sign the transaction accordingly - for (var i = 0; i < utxosToUse.length; i++) { - final txid = utxosToUse[i].txid; + for (var i = 0; i < utxoSigningData.length; i++) { txb.sign( - vin: i, - keyPair: utxoSigningData[txid]["keyPair"] as ECPair, - witnessValue: utxosToUse[i].value, - redeemScript: utxoSigningData[txid]["redeemScript"] as Uint8List?); + vin: i, + keyPair: utxoSigningData[i].keyPair!, + witnessValue: utxoSigningData[i].utxo.value, + redeemScript: utxoSigningData[i].redeemScript, + ); } } catch (e, s) { Logging.instance.log("Caught exception while signing transaction: $e\n$s", @@ -3150,17 +3014,30 @@ class ParticlWallet extends CoinServiceAPI { await _cachedElectrumXClient.clearSharedTransactionCache(coin: coin); // back up data - await _rescanBackup(); + // await _rescanBackup(); + + await db.deleteWalletBlockchainData(walletId); + await _deleteDerivations(); try { - final mnemonic = await _secureStore.read(key: '${_walletId}_mnemonic'); + final _mnemonic = await mnemonicString; + final _mnemonicPassphrase = await mnemonicPassphrase; + if (_mnemonicPassphrase == null) { + Logging.instance.log( + "Exception in fullRescan: mnemonic passphrase null, possible migration issue; if using internal builds, delete wallet and restore from seed, if using a release build, please file bug report", + level: LogLevel.Error); + } + await _recoverWalletFromBIP32SeedPhrase( - mnemonic: mnemonic!, + mnemonic: _mnemonic!, + mnemonicPassphrase: _mnemonicPassphrase!, maxUnusedAddressGap: maxUnusedAddressGap, maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + isRescan: true, ); longMutex = false; + await refresh(); Logging.instance.log("Full rescan complete!", level: LogLevel.Info); GlobalEventBus.instance.fire( WalletSyncStatusChangedEvent( @@ -3179,7 +3056,7 @@ class ParticlWallet extends CoinServiceAPI { ); // restore from backup - await _rescanRestore(); + // await _rescanRestore(); longMutex = false; Logging.instance.log("Exception rethrown from fullRescan(): $e\n$s", @@ -3188,245 +3065,255 @@ class ParticlWallet extends CoinServiceAPI { } } - Future _rescanRestore() async { - Logging.instance.log("starting rescan restore", level: LogLevel.Info); - - // restore from backup - // p2pkh - final tempReceivingAddressesP2PKH = DB.instance - .get(boxName: walletId, key: 'receivingAddressesP2PKH_BACKUP'); - final tempChangeAddressesP2PKH = DB.instance - .get(boxName: walletId, key: 'changeAddressesP2PKH_BACKUP'); - final tempReceivingIndexP2PKH = DB.instance - .get(boxName: walletId, key: 'receivingIndexP2PKH_BACKUP'); - final tempChangeIndexP2PKH = DB.instance - .get(boxName: walletId, key: 'changeIndexP2PKH_BACKUP'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2PKH', - value: tempReceivingAddressesP2PKH); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2PKH', - value: tempChangeAddressesP2PKH); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2PKH', - value: tempReceivingIndexP2PKH); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2PKH', - value: tempChangeIndexP2PKH); - await DB.instance.delete( - key: 'receivingAddressesP2PKH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'changeAddressesP2PKH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'receivingIndexP2PKH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'changeIndexP2PKH_BACKUP', boxName: walletId); - - // p2wpkh - final tempReceivingAddressesP2WPKH = DB.instance.get( - boxName: walletId, key: 'receivingAddressesP2WPKH_BACKUP'); - final tempChangeAddressesP2WPKH = DB.instance - .get(boxName: walletId, key: 'changeAddressesP2WPKH_BACKUP'); - final tempReceivingIndexP2WPKH = DB.instance - .get(boxName: walletId, key: 'receivingIndexP2WPKH_BACKUP'); - final tempChangeIndexP2WPKH = DB.instance - .get(boxName: walletId, key: 'changeIndexP2WPKH_BACKUP'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2WPKH', - value: tempReceivingAddressesP2WPKH); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2WPKH', - value: tempChangeAddressesP2WPKH); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2WPKH', - value: tempReceivingIndexP2WPKH); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2WPKH', - value: tempChangeIndexP2WPKH); - await DB.instance.delete( - key: 'receivingAddressesP2WPKH_BACKUP', boxName: walletId); - await DB.instance.delete( - key: 'changeAddressesP2WPKH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'receivingIndexP2WPKH_BACKUP', boxName: walletId); - await DB.instance - .delete(key: 'changeIndexP2WPKH_BACKUP', boxName: walletId); - + Future _deleteDerivations() async { // P2PKH derivations - final p2pkhReceiveDerivationsString = await _secureStore.read( - key: "${walletId}_receiveDerivationsP2PKH_BACKUP"); - final p2pkhChangeDerivationsString = await _secureStore.read( - key: "${walletId}_changeDerivationsP2PKH_BACKUP"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2PKH", - value: p2pkhReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2PKH", - value: p2pkhChangeDerivationsString); - - await _secureStore.delete( - key: "${walletId}_receiveDerivationsP2PKH_BACKUP"); - await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH_BACKUP"); - - // P2WPKH derivations - final p2wpkhReceiveDerivationsString = await _secureStore.read( - key: "${walletId}_receiveDerivationsP2WPKH_BACKUP"); - final p2wpkhChangeDerivationsString = await _secureStore.read( - key: "${walletId}_changeDerivationsP2WPKH_BACKUP"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2WPKH", - value: p2wpkhReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2WPKH", - value: p2wpkhChangeDerivationsString); - - await _secureStore.delete( - key: "${walletId}_receiveDerivationsP2WPKH_BACKUP"); - await _secureStore.delete( - key: "${walletId}_changeDerivationsP2WPKH_BACKUP"); - - // UTXOs - final utxoData = DB.instance - .get(boxName: walletId, key: 'latest_utxo_model_BACKUP'); - await DB.instance.put( - boxName: walletId, key: 'latest_utxo_model', value: utxoData); - await DB.instance - .delete(key: 'latest_utxo_model_BACKUP', boxName: walletId); - - Logging.instance.log("rescan restore complete", level: LogLevel.Info); - } - - Future _rescanBackup() async { - Logging.instance.log("starting rescan backup", level: LogLevel.Info); - - // backup current and clear data - // p2pkh - final tempReceivingAddressesP2PKH = DB.instance - .get(boxName: walletId, key: 'receivingAddressesP2PKH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2PKH_BACKUP', - value: tempReceivingAddressesP2PKH); - await DB.instance - .delete(key: 'receivingAddressesP2PKH', boxName: walletId); - - final tempChangeAddressesP2PKH = DB.instance - .get(boxName: walletId, key: 'changeAddressesP2PKH'); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2PKH_BACKUP', - value: tempChangeAddressesP2PKH); - await DB.instance - .delete(key: 'changeAddressesP2PKH', boxName: walletId); - - final tempReceivingIndexP2PKH = - DB.instance.get(boxName: walletId, key: 'receivingIndexP2PKH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2PKH_BACKUP', - value: tempReceivingIndexP2PKH); - await DB.instance - .delete(key: 'receivingIndexP2PKH', boxName: walletId); - - final tempChangeIndexP2PKH = - DB.instance.get(boxName: walletId, key: 'changeIndexP2PKH'); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2PKH_BACKUP', - value: tempChangeIndexP2PKH); - await DB.instance - .delete(key: 'changeIndexP2PKH', boxName: walletId); - - // p2wpkh - final tempReceivingAddressesP2WPKH = DB.instance - .get(boxName: walletId, key: 'receivingAddressesP2WPKH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingAddressesP2WPKH_BACKUP', - value: tempReceivingAddressesP2WPKH); - await DB.instance - .delete(key: 'receivingAddressesP2WPKH', boxName: walletId); - - final tempChangeAddressesP2WPKH = DB.instance - .get(boxName: walletId, key: 'changeAddressesP2WPKH'); - await DB.instance.put( - boxName: walletId, - key: 'changeAddressesP2WPKH_BACKUP', - value: tempChangeAddressesP2WPKH); - await DB.instance - .delete(key: 'changeAddressesP2WPKH', boxName: walletId); - - final tempReceivingIndexP2WPKH = DB.instance - .get(boxName: walletId, key: 'receivingIndexP2WPKH'); - await DB.instance.put( - boxName: walletId, - key: 'receivingIndexP2WPKH_BACKUP', - value: tempReceivingIndexP2WPKH); - await DB.instance - .delete(key: 'receivingIndexP2WPKH', boxName: walletId); - - final tempChangeIndexP2WPKH = - DB.instance.get(boxName: walletId, key: 'changeIndexP2WPKH'); - await DB.instance.put( - boxName: walletId, - key: 'changeIndexP2WPKH_BACKUP', - value: tempChangeIndexP2WPKH); - await DB.instance - .delete(key: 'changeIndexP2WPKH', boxName: walletId); - - // P2PKH derivations - final p2pkhReceiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivationsP2PKH"); - final p2pkhChangeDerivationsString = - await _secureStore.read(key: "${walletId}_changeDerivationsP2PKH"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2PKH_BACKUP", - value: p2pkhReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2PKH_BACKUP", - value: p2pkhChangeDerivationsString); - await _secureStore.delete(key: "${walletId}_receiveDerivationsP2PKH"); await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH"); // P2WPKH derivations - final p2wpkhReceiveDerivationsString = - await _secureStore.read(key: "${walletId}_receiveDerivationsP2WPKH"); - final p2wpkhChangeDerivationsString = - await _secureStore.read(key: "${walletId}_changeDerivationsP2WPKH"); - - await _secureStore.write( - key: "${walletId}_receiveDerivationsP2WPKH_BACKUP", - value: p2wpkhReceiveDerivationsString); - await _secureStore.write( - key: "${walletId}_changeDerivationsP2WPKH_BACKUP", - value: p2wpkhChangeDerivationsString); - await _secureStore.delete(key: "${walletId}_receiveDerivationsP2WPKH"); await _secureStore.delete(key: "${walletId}_changeDerivationsP2WPKH"); - - // UTXOs - final utxoData = - DB.instance.get(boxName: walletId, key: 'latest_utxo_model'); - await DB.instance.put( - boxName: walletId, key: 'latest_utxo_model_BACKUP', value: utxoData); - await DB.instance - .delete(key: 'latest_utxo_model', boxName: walletId); - - Logging.instance.log("rescan backup complete", level: LogLevel.Info); } + // Future _rescanRestore() async { + // Logging.instance.log("starting rescan restore", level: LogLevel.Info); + // + // // restore from backup + // // p2pkh + // final tempReceivingAddressesP2PKH = DB.instance + // .get(boxName: walletId, key: 'receivingAddressesP2PKH_BACKUP'); + // final tempChangeAddressesP2PKH = DB.instance + // .get(boxName: walletId, key: 'changeAddressesP2PKH_BACKUP'); + // final tempReceivingIndexP2PKH = DB.instance + // .get(boxName: walletId, key: 'receivingIndexP2PKH_BACKUP'); + // final tempChangeIndexP2PKH = DB.instance + // .get(boxName: walletId, key: 'changeIndexP2PKH_BACKUP'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingAddressesP2PKH', + // value: tempReceivingAddressesP2PKH); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeAddressesP2PKH', + // value: tempChangeAddressesP2PKH); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingIndexP2PKH', + // value: tempReceivingIndexP2PKH); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeIndexP2PKH', + // value: tempChangeIndexP2PKH); + // await DB.instance.delete( + // key: 'receivingAddressesP2PKH_BACKUP', boxName: walletId); + // await DB.instance + // .delete(key: 'changeAddressesP2PKH_BACKUP', boxName: walletId); + // await DB.instance + // .delete(key: 'receivingIndexP2PKH_BACKUP', boxName: walletId); + // await DB.instance + // .delete(key: 'changeIndexP2PKH_BACKUP', boxName: walletId); + // + // // p2wpkh + // final tempReceivingAddressesP2WPKH = DB.instance.get( + // boxName: walletId, key: 'receivingAddressesP2WPKH_BACKUP'); + // final tempChangeAddressesP2WPKH = DB.instance + // .get(boxName: walletId, key: 'changeAddressesP2WPKH_BACKUP'); + // final tempReceivingIndexP2WPKH = DB.instance + // .get(boxName: walletId, key: 'receivingIndexP2WPKH_BACKUP'); + // final tempChangeIndexP2WPKH = DB.instance + // .get(boxName: walletId, key: 'changeIndexP2WPKH_BACKUP'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingAddressesP2WPKH', + // value: tempReceivingAddressesP2WPKH); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeAddressesP2WPKH', + // value: tempChangeAddressesP2WPKH); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingIndexP2WPKH', + // value: tempReceivingIndexP2WPKH); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeIndexP2WPKH', + // value: tempChangeIndexP2WPKH); + // await DB.instance.delete( + // key: 'receivingAddressesP2WPKH_BACKUP', boxName: walletId); + // await DB.instance.delete( + // key: 'changeAddressesP2WPKH_BACKUP', boxName: walletId); + // await DB.instance + // .delete(key: 'receivingIndexP2WPKH_BACKUP', boxName: walletId); + // await DB.instance + // .delete(key: 'changeIndexP2WPKH_BACKUP', boxName: walletId); + // + // // P2PKH derivations + // final p2pkhReceiveDerivationsString = await _secureStore.read( + // key: "${walletId}_receiveDerivationsP2PKH_BACKUP"); + // final p2pkhChangeDerivationsString = await _secureStore.read( + // key: "${walletId}_changeDerivationsP2PKH_BACKUP"); + // + // await _secureStore.write( + // key: "${walletId}_receiveDerivationsP2PKH", + // value: p2pkhReceiveDerivationsString); + // await _secureStore.write( + // key: "${walletId}_changeDerivationsP2PKH", + // value: p2pkhChangeDerivationsString); + // + // await _secureStore.delete( + // key: "${walletId}_receiveDerivationsP2PKH_BACKUP"); + // await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH_BACKUP"); + // + // // P2WPKH derivations + // final p2wpkhReceiveDerivationsString = await _secureStore.read( + // key: "${walletId}_receiveDerivationsP2WPKH_BACKUP"); + // final p2wpkhChangeDerivationsString = await _secureStore.read( + // key: "${walletId}_changeDerivationsP2WPKH_BACKUP"); + // + // await _secureStore.write( + // key: "${walletId}_receiveDerivationsP2WPKH", + // value: p2wpkhReceiveDerivationsString); + // await _secureStore.write( + // key: "${walletId}_changeDerivationsP2WPKH", + // value: p2wpkhChangeDerivationsString); + // + // await _secureStore.delete( + // key: "${walletId}_receiveDerivationsP2WPKH_BACKUP"); + // await _secureStore.delete( + // key: "${walletId}_changeDerivationsP2WPKH_BACKUP"); + // + // // UTXOs + // final utxoData = DB.instance + // .get(boxName: walletId, key: 'latest_utxo_model_BACKUP'); + // await DB.instance.put( + // boxName: walletId, key: 'latest_utxo_model', value: utxoData); + // await DB.instance + // .delete(key: 'latest_utxo_model_BACKUP', boxName: walletId); + // + // Logging.instance.log("rescan restore complete", level: LogLevel.Info); + // } + // + // Future _rescanBackup() async { + // Logging.instance.log("starting rescan backup", level: LogLevel.Info); + // + // // backup current and clear data + // // p2pkh + // final tempReceivingAddressesP2PKH = DB.instance + // .get(boxName: walletId, key: 'receivingAddressesP2PKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingAddressesP2PKH_BACKUP', + // value: tempReceivingAddressesP2PKH); + // await DB.instance + // .delete(key: 'receivingAddressesP2PKH', boxName: walletId); + // + // final tempChangeAddressesP2PKH = DB.instance + // .get(boxName: walletId, key: 'changeAddressesP2PKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeAddressesP2PKH_BACKUP', + // value: tempChangeAddressesP2PKH); + // await DB.instance + // .delete(key: 'changeAddressesP2PKH', boxName: walletId); + // + // final tempReceivingIndexP2PKH = + // DB.instance.get(boxName: walletId, key: 'receivingIndexP2PKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingIndexP2PKH_BACKUP', + // value: tempReceivingIndexP2PKH); + // await DB.instance + // .delete(key: 'receivingIndexP2PKH', boxName: walletId); + // + // final tempChangeIndexP2PKH = + // DB.instance.get(boxName: walletId, key: 'changeIndexP2PKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeIndexP2PKH_BACKUP', + // value: tempChangeIndexP2PKH); + // await DB.instance + // .delete(key: 'changeIndexP2PKH', boxName: walletId); + // + // // p2wpkh + // final tempReceivingAddressesP2WPKH = DB.instance + // .get(boxName: walletId, key: 'receivingAddressesP2WPKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingAddressesP2WPKH_BACKUP', + // value: tempReceivingAddressesP2WPKH); + // await DB.instance + // .delete(key: 'receivingAddressesP2WPKH', boxName: walletId); + // + // final tempChangeAddressesP2WPKH = DB.instance + // .get(boxName: walletId, key: 'changeAddressesP2WPKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeAddressesP2WPKH_BACKUP', + // value: tempChangeAddressesP2WPKH); + // await DB.instance + // .delete(key: 'changeAddressesP2WPKH', boxName: walletId); + // + // final tempReceivingIndexP2WPKH = DB.instance + // .get(boxName: walletId, key: 'receivingIndexP2WPKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'receivingIndexP2WPKH_BACKUP', + // value: tempReceivingIndexP2WPKH); + // await DB.instance + // .delete(key: 'receivingIndexP2WPKH', boxName: walletId); + // + // final tempChangeIndexP2WPKH = + // DB.instance.get(boxName: walletId, key: 'changeIndexP2WPKH'); + // await DB.instance.put( + // boxName: walletId, + // key: 'changeIndexP2WPKH_BACKUP', + // value: tempChangeIndexP2WPKH); + // await DB.instance + // .delete(key: 'changeIndexP2WPKH', boxName: walletId); + // + // // P2PKH derivations + // final p2pkhReceiveDerivationsString = + // await _secureStore.read(key: "${walletId}_receiveDerivationsP2PKH"); + // final p2pkhChangeDerivationsString = + // await _secureStore.read(key: "${walletId}_changeDerivationsP2PKH"); + // + // await _secureStore.write( + // key: "${walletId}_receiveDerivationsP2PKH_BACKUP", + // value: p2pkhReceiveDerivationsString); + // await _secureStore.write( + // key: "${walletId}_changeDerivationsP2PKH_BACKUP", + // value: p2pkhChangeDerivationsString); + // + // await _secureStore.delete(key: "${walletId}_receiveDerivationsP2PKH"); + // await _secureStore.delete(key: "${walletId}_changeDerivationsP2PKH"); + // + // // P2WPKH derivations + // final p2wpkhReceiveDerivationsString = + // await _secureStore.read(key: "${walletId}_receiveDerivationsP2WPKH"); + // final p2wpkhChangeDerivationsString = + // await _secureStore.read(key: "${walletId}_changeDerivationsP2WPKH"); + // + // await _secureStore.write( + // key: "${walletId}_receiveDerivationsP2WPKH_BACKUP", + // value: p2wpkhReceiveDerivationsString); + // await _secureStore.write( + // key: "${walletId}_changeDerivationsP2WPKH_BACKUP", + // value: p2wpkhChangeDerivationsString); + // + // await _secureStore.delete(key: "${walletId}_receiveDerivationsP2WPKH"); + // await _secureStore.delete(key: "${walletId}_changeDerivationsP2WPKH"); + // + // // UTXOs + // final utxoData = + // DB.instance.get(boxName: walletId, key: 'latest_utxo_model'); + // await DB.instance.put( + // boxName: walletId, key: 'latest_utxo_model_BACKUP', value: utxoData); + // await DB.instance + // .delete(key: 'latest_utxo_model', boxName: walletId); + // + // Logging.instance.log("rescan backup complete", level: LogLevel.Info); + // } + bool isActive = false; @override @@ -3434,58 +3321,70 @@ class ParticlWallet extends CoinServiceAPI { (isActive) => this.isActive = isActive; @override - Future estimateFeeFor(int satoshiAmount, int feeRate) async { - final available = - Format.decimalAmountToSatoshis(await availableBalance, coin); + Future estimateFeeFor(Amount amount, int feeRate) async { + final available = balance.spendable; - if (available == satoshiAmount) { - return satoshiAmount - sweepAllEstimate(feeRate); - } else if (satoshiAmount <= 0 || satoshiAmount > available) { + if (available == amount) { + return amount - (await sweepAllEstimate(feeRate)); + } else if (amount <= Amount.zero || amount > available) { return roughFeeEstimate(1, 2, feeRate); } - int runningBalance = 0; + Amount runningBalance = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); int inputCount = 0; - for (final output in outputsList) { - runningBalance += output.value; - inputCount++; - if (runningBalance > satoshiAmount) { - break; + for (final output in (await utxos)) { + if (!output.isBlocked) { + runningBalance += Amount( + rawValue: BigInt.from(output.value), + fractionDigits: coin.decimals, + ); + inputCount++; + if (runningBalance > amount) { + break; + } } } final oneOutPutFee = roughFeeEstimate(inputCount, 1, feeRate); final twoOutPutFee = roughFeeEstimate(inputCount, 2, feeRate); - if (runningBalance - satoshiAmount > oneOutPutFee) { - if (runningBalance - satoshiAmount > oneOutPutFee + DUST_LIMIT) { - final change = runningBalance - satoshiAmount - twoOutPutFee; + if (runningBalance - amount > oneOutPutFee) { + if (runningBalance - amount > oneOutPutFee + DUST_LIMIT) { + final change = runningBalance - amount - twoOutPutFee; if (change > DUST_LIMIT && - runningBalance - satoshiAmount - change == twoOutPutFee) { - return runningBalance - satoshiAmount - change; + runningBalance - amount - change == twoOutPutFee) { + return runningBalance - amount - change; } else { - return runningBalance - satoshiAmount; + return runningBalance - amount; } } else { - return runningBalance - satoshiAmount; + return runningBalance - amount; } - } else if (runningBalance - satoshiAmount == oneOutPutFee) { + } else if (runningBalance - amount == oneOutPutFee) { return oneOutPutFee; } else { return twoOutPutFee; } } - int roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { - return ((42 + (272 * inputCount) + (128 * outputCount)) / 4).ceil() * - (feeRatePerKB / 1000).ceil(); + Amount roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) { + return Amount( + rawValue: BigInt.from( + ((42 + (272 * inputCount) + (128 * outputCount)) / 4).ceil() * + (feeRatePerKB / 1000).ceil()), + fractionDigits: coin.decimals, + ); } - int sweepAllEstimate(int feeRate) { + Future sweepAllEstimate(int feeRate) async { int available = 0; int inputCount = 0; - for (final output in outputsList) { - if (output.status.confirmed) { + for (final output in (await utxos)) { + if (!output.isBlocked && + output.isConfirmed(storedChainHeight, MINIMUM_CONFIRMATIONS)) { available += output.value; inputCount++; } @@ -3494,29 +3393,26 @@ class ParticlWallet extends CoinServiceAPI { // transaction will only have 1 output minus the fee final estimatedFee = roughFeeEstimate(inputCount, 1, feeRate); - return available - estimatedFee; + return Amount( + rawValue: BigInt.from(available), + fractionDigits: coin.decimals, + ) - + estimatedFee; } @override Future generateNewAddress() async { try { - await _incrementAddressIndexForChain( - 0, DerivePathType.bip84); // First increment the receiving index - final newReceivingIndex = DB.instance.get( - boxName: walletId, - key: 'receivingIndexP2WPKH') as int; // Check the new receiving index + final currentReceiving = await _currentReceivingAddress; + + final newReceivingIndex = currentReceiving.derivationIndex + 1; + + // Use new index to derive a new receiving address final newReceivingAddress = await _generateAddressForChain( - 0, - newReceivingIndex, - DerivePathType - .bip84); // Use new index to derive a new receiving address - await _addToAddressesArrayForChain( - newReceivingAddress, - 0, - DerivePathType - .bip84); // Add that new receiving address to the array of receiving addresses - _currentReceivingAddress = Future(() => - newReceivingAddress); // Set the new receiving address that the service + 0, newReceivingIndex, DerivePathTypeExt.primaryFor(coin)); + + // Add that new receiving address + await db.putAddress(newReceivingAddress); return true; } catch (e, s) { @@ -3526,6 +3422,17 @@ class ParticlWallet extends CoinServiceAPI { return false; } } + + @override + Future get xpub async { + final node = await Bip32Utils.getBip32Root( + (await mnemonic).join(" "), + await mnemonicPassphrase ?? "", + _network, + ); + + return node.neutered().toBase58(); + } } // Particl Network diff --git a/lib/services/coins/wownero/wownero_wallet.dart b/lib/services/coins/wownero/wownero_wallet.dart index 9e152d9e6..c07582fa5 100644 --- a/lib/services/coins/wownero/wownero_wallet.dart +++ b/lib/services/coins/wownero/wownero_wallet.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:io'; +import 'dart:math'; import 'package:cw_core/monero_transaction_priority.dart'; import 'package:cw_core/node.dart'; @@ -14,9 +15,7 @@ import 'package:cw_core/wallet_type.dart'; import 'package:cw_wownero/api/exceptions/creation_transaction_exception.dart'; import 'package:cw_wownero/api/wallet.dart'; import 'package:cw_wownero/pending_wownero_transaction.dart'; -import 'package:cw_wownero/wownero_amount_format.dart'; import 'package:cw_wownero/wownero_wallet.dart'; -import 'package:dart_numerics/dart_numerics.dart'; import 'package:decimal/decimal.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_libmonero/core/key_service.dart'; @@ -24,74 +23,89 @@ import 'package:flutter_libmonero/core/wallet_creation_service.dart'; import 'package:flutter_libmonero/view_model/send/output.dart' as wownero_output; import 'package:flutter_libmonero/wownero/wownero.dart'; -import 'package:http/http.dart'; +import 'package:isar/isar.dart'; import 'package:mutex/mutex.dart'; -import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/db/hive/db.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/models/paymint/fee_object_model.dart'; -import 'package:stackwallet/models/paymint/transactions_model.dart'; -import 'package:stackwallet/models/paymint/utxo_model.dart'; import 'package:stackwallet/services/coins/coin_service.dart'; import 'package:stackwallet/services/event_bus/events/global/blocks_remaining_event.dart'; -import 'package:stackwallet/services/event_bus/events/global/node_connection_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/refresh_percent_changed_event.dart'; import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +import 'package:stackwallet/services/mixins/wallet_cache.dart'; +import 'package:stackwallet/services/mixins/wallet_db.dart'; import 'package:stackwallet/services/node_service.dart'; -import 'package:stackwallet/services/price.dart'; -import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/default_nodes.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; -import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; import 'package:stackwallet/utilities/stack_file_system.dart'; +import 'package:tuple/tuple.dart'; -const int MINIMUM_CONFIRMATIONS = 10; +const int MINIMUM_CONFIRMATIONS = 15; -//https://github.com/wownero-project/wownero/blob/8361d60aef6e17908658128284899e3a11d808d4/src/cryptonote_config.h#L162 -const String GENESIS_HASH_MAINNET = - "013c01ff0001ffffffffffff03029b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd08807121017767aafcde9be00dcfd098715ebcf7f410daebc582fda69d24a28e9d0bc890d1"; -const String GENESIS_HASH_TESTNET = - "013c01ff0001ffffffffffff03029b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd08807121017767aafcde9be00dcfd098715ebcf7f410daebc582fda69d24a28e9d0bc890d1"; - -class WowneroWallet extends CoinServiceAPI { - static const integrationTestFlag = - bool.fromEnvironment("IS_INTEGRATION_TEST"); - final _prefs = Prefs.instance; - - Timer? timer; - Timer? wowneroAutosaveTimer; - late Coin _coin; - - late SecureStorageInterface _secureStore; - - late PriceAPI _priceAPI; - - Future getCurrentNode() async { - return NodeService(secureStorageInterface: _secureStore) - .getPrimaryNodeFor(coin: coin) ?? - DefaultNodes.getNodeFor(coin); - } - - WowneroWallet( - {required String walletId, - required String walletName, - required Coin coin, - PriceAPI? priceAPI, - required SecureStorageInterface secureStore}) { +class WowneroWallet extends CoinServiceAPI with WalletCache, WalletDB { + WowneroWallet({ + required String walletId, + required String walletName, + required Coin coin, + required SecureStorageInterface secureStorage, + Prefs? prefs, + MainDB? mockableOverride, + }) { _walletId = walletId; _walletName = walletName; _coin = coin; - - _priceAPI = priceAPI ?? PriceAPI(Client()); - _secureStore = secureStore; + _secureStorage = secureStorage; + _prefs = prefs ?? Prefs.instance; + initCache(walletId, coin); + initWalletDB(mockableOverride: mockableOverride); } + late final String _walletId; + late final Coin _coin; + late final SecureStorageInterface _secureStorage; + late final Prefs _prefs; + + late String _walletName; + bool _shouldAutoSync = false; + bool _isConnected = false; + bool _hasCalledExit = false; + bool refreshMutex = false; + bool longMutex = false; + + WalletService? walletService; + KeyService? keysStorage; + WowneroWalletBase? walletBase; + WalletCreationService? _walletCreationService; + Timer? _autoSaveTimer; + + Future get _currentReceivingAddress => + db.getAddresses(walletId).sortByDerivationIndexDesc().findFirst(); + Future? _feeObject; + + Mutex prepareSendMutex = Mutex(); + Mutex estimateFeeMutex = Mutex(); + + @override + set isFavorite(bool markFavorite) { + _isFavorite = markFavorite; + updateCachedIsFavorite(markFavorite); + } + + @override + bool get isFavorite => _isFavorite ??= getCachedIsFavorite(); + + bool? _isFavorite; @override bool get shouldAutoSync => _shouldAutoSync; @@ -100,1325 +114,30 @@ class WowneroWallet extends CoinServiceAPI { set shouldAutoSync(bool shouldAutoSync) { if (_shouldAutoSync != shouldAutoSync) { _shouldAutoSync = shouldAutoSync; - if (!shouldAutoSync) { - timer?.cancel(); - wowneroAutosaveTimer?.cancel(); - timer = null; - wowneroAutosaveTimer = null; - stopNetworkAlivePinging(); - } else { - startNetworkAlivePinging(); - // Walletbase needs to be open for this to work - refresh(); - } - } - } - - @override - Future updateNode(bool shouldRefresh) async { - final node = await getCurrentNode(); - - final host = Uri.parse(node.host).host; - await walletBase?.connectToNode( - node: Node(uri: "$host:${node.port}", type: WalletType.wownero)); - - // TODO: is this sync call needed? Do we need to notify ui here? - await walletBase?.startSync(); - - if (shouldRefresh) { - await refresh(); - } - } - - Future> _getMnemonicList() async { - final mnemonicString = - await _secureStore.read(key: '${_walletId}_mnemonic'); - if (mnemonicString == null) { - return []; - } - final List data = mnemonicString.split(' '); - return data; - } - - @override - Future> get mnemonic => _getMnemonicList(); - - Future get currentNodeHeight async { - try { - if (walletBase!.syncStatus! is SyncedSyncStatus && - walletBase!.syncStatus!.progress() == 1.0) { - return await walletBase!.getNodeHeight(); - } - } catch (e, s) {} - int _height = -1; - try { - _height = (walletBase!.syncStatus as SyncingSyncStatus).height; - } catch (e, s) { - // Logging.instance.log("$e $s", level: LogLevel.Warning); - } - - int blocksRemaining = -1; - - try { - blocksRemaining = - (walletBase!.syncStatus as SyncingSyncStatus).blocksLeft; - } catch (e, s) { - // Logging.instance.log("$e $s", level: LogLevel.Warning); - } - int currentHeight = _height + blocksRemaining; - if (_height == -1 || blocksRemaining == -1) { - currentHeight = int64MaxValue; - } - final cachedHeight = DB.instance - .get(boxName: walletId, key: "storedNodeHeight") as int? ?? - 0; - - if (currentHeight > cachedHeight && currentHeight != int64MaxValue) { - await DB.instance.put( - boxName: walletId, key: "storedNodeHeight", value: currentHeight); - return currentHeight; - } else { - return cachedHeight; - } - } - - Future get currentSyncingHeight async { - //TODO return the tip of the wownero blockchain - try { - if (walletBase!.syncStatus! is SyncedSyncStatus && - walletBase!.syncStatus!.progress() == 1.0) { - // Logging.instance - // .log("currentSyncingHeight lol", level: LogLevel.Warning); - return getSyncingHeight(); - } - } catch (e, s) {} - int syncingHeight = -1; - try { - syncingHeight = (walletBase!.syncStatus as SyncingSyncStatus).height; - } catch (e, s) { - // Logging.instance.log("$e $s", level: LogLevel.Warning); - } - final cachedHeight = - DB.instance.get(boxName: walletId, key: "storedSyncingHeight") - as int? ?? - 0; - - if (syncingHeight > cachedHeight) { - await DB.instance.put( - boxName: walletId, key: "storedSyncingHeight", value: syncingHeight); - return syncingHeight; - } else { - return cachedHeight; - } - } - - Future updateStoredChainHeight({required int newHeight}) async { - await DB.instance.put( - boxName: walletId, key: "storedChainHeight", value: newHeight); - } - - int get storedChainHeight { - return DB.instance.get(boxName: walletId, key: "storedChainHeight") - as int? ?? - 0; - } - - /// Increases the index for either the internal or external chain, depending on [chain]. - /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! - Future _incrementAddressIndexForChain(int chain) async { - // Here we assume chain == 1 if it isn't 0 - String indexKey = chain == 0 ? "receivingIndex" : "changeIndex"; - - final newIndex = - (DB.instance.get(boxName: walletId, key: indexKey)) + 1; - await DB.instance - .put(boxName: walletId, key: indexKey, value: newIndex); - } - - Future _checkCurrentReceivingAddressesForTransactions() async { - try { - await _checkReceivingAddressForTransactions(); - } catch (e, s) { - Logging.instance.log( - "Exception rethrown from _checkCurrentReceivingAddressesForTransactions(): $e\n$s", - level: LogLevel.Error); - rethrow; - } - } - - Future _checkReceivingAddressForTransactions() async { - try { - int highestIndex = -1; - for (var element - in walletBase!.transactionHistory!.transactions!.entries) { - if (element.value.direction == TransactionDirection.incoming) { - int curAddressIndex = - element.value.additionalInfo!['addressIndex'] as int; - if (curAddressIndex > highestIndex) { - highestIndex = curAddressIndex; - } - } - } - - // Check the new receiving index - String indexKey = "receivingIndex"; - final curIndex = - DB.instance.get(boxName: walletId, key: indexKey) as int; - if (highestIndex >= curIndex) { - // First increment the receiving index - await _incrementAddressIndexForChain(0); - final newReceivingIndex = - DB.instance.get(boxName: walletId, key: indexKey) as int; - - // Use new index to derive a new receiving address - final newReceivingAddress = - await _generateAddressForChain(0, newReceivingIndex); - - // Add that new receiving address to the array of receiving addresses - await _addToAddressesArrayForChain(newReceivingAddress, 0); - - // Set the new receiving address that the service - - _currentReceivingAddress = Future(() => newReceivingAddress); - } - } on SocketException catch (se, s) { - Logging.instance.log( - "SocketException caught in _checkReceivingAddressForTransactions(): $se\n$s", - level: LogLevel.Error); - return; - } catch (e, s) { - Logging.instance.log( - "Exception rethrown from _checkReceivingAddressForTransactions(): $e\n$s", - level: LogLevel.Error); - rethrow; - } - } - - @override - bool get isRefreshing => refreshMutex; - - bool refreshMutex = false; - - Timer? syncPercentTimer; - - Mutex syncHeightMutex = Mutex(); - Future stopSyncPercentTimer() async { - syncPercentTimer?.cancel(); - syncPercentTimer = null; - } - - Future startSyncPercentTimer() async { - if (syncPercentTimer != null) { - return; - } - syncPercentTimer?.cancel(); - GlobalEventBus.instance - .fire(RefreshPercentChangedEvent(highestPercentCached, walletId)); - syncPercentTimer = Timer.periodic(const Duration(seconds: 30), (_) async { - if (syncHeightMutex.isLocked) { - return; - } - await syncHeightMutex.protect(() async { - // int restoreheight = walletBase!.walletInfo.restoreHeight ?? 0; - int _height = await currentSyncingHeight; - int _currentHeight = await currentNodeHeight; - double progress = 0; - try { - progress = walletBase!.syncStatus!.progress(); - } catch (e, s) { - Logging.instance.log("$e $s", level: LogLevel.Warning); - } - - final int blocksRemaining = _currentHeight - _height; - - GlobalEventBus.instance - .fire(BlocksRemainingEvent(blocksRemaining, walletId)); - - if (progress == 1 && _currentHeight > 0 && _height > 0) { - await stopSyncPercentTimer(); - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.synced, - walletId, - coin, - ), - ); - return; - } - - // for some reason this can be 0 which screws up the percent calculation - // int64MaxValue is NOT the best value to use here - if (_currentHeight < 1) { - _currentHeight = int64MaxValue; - } - - if (_height < 1) { - _height = 1; - } - - double restorePercent = progress; - double highestPercent = highestPercentCached; - - Logging.instance.log( - "currentSyncingHeight: $_height, nodeHeight: $_currentHeight, restorePercent: $restorePercent, highestPercentCached: $highestPercentCached", - level: LogLevel.Info); - - if (restorePercent > 0 && restorePercent <= 1) { - // if (restorePercent > highestPercent) { - highestPercent = restorePercent; - highestPercentCached = restorePercent; - // } - } - - GlobalEventBus.instance - .fire(RefreshPercentChangedEvent(highestPercent, walletId)); - }); - }); - } - - double get highestPercentCached => - DB.instance.get(boxName: walletId, key: "highestPercentCached") - as double? ?? - 0; - set highestPercentCached(double value) => DB.instance.put( - boxName: walletId, - key: "highestPercentCached", - value: value, - ); - - /// Refreshes display data for the wallet - @override - Future refresh() async { - if (refreshMutex) { - Logging.instance.log("$walletId $walletName refreshMutex denied", - level: LogLevel.Info); - return; - } else { - refreshMutex = true; - } - - if (walletBase == null) { - throw Exception("Tried to call refresh() in wownero without walletBase!"); - } - - try { - await startSyncPercentTimer(); - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.syncing, - walletId, - coin, - ), - ); - - final int _currentSyncingHeight = await currentSyncingHeight; - final int storedHeight = storedChainHeight; - int _currentNodeHeight = await currentNodeHeight; - - double progress = 0; - try { - progress = (walletBase!.syncStatus!).progress(); - } catch (e, s) { - // Logging.instance.log("$e $s", level: LogLevel.Warning); - } - await _fetchTransactionData(); - - bool stillSyncing = false; - Logging.instance.log( - "storedHeight: $storedHeight, _currentSyncingHeight: $_currentSyncingHeight, _currentNodeHeight: $_currentNodeHeight, progress: $progress, issynced: ${await walletBase!.isConnected()}", - level: LogLevel.Info); - - if (progress < 1.0) { - stillSyncing = true; - } - - if (_currentSyncingHeight > storedHeight) { - // 0 is returned from wownero as I assume an error????? - if (_currentSyncingHeight > 0) { - // 0 failed to fetch current height??? - await updateStoredChainHeight(newHeight: _currentSyncingHeight); - } - } - - await _checkCurrentReceivingAddressesForTransactions(); - String indexKey = "receivingIndex"; - final curIndex = - DB.instance.get(boxName: walletId, key: indexKey) as int; - // Use new index to derive a new receiving address - try { - final newReceivingAddress = await _generateAddressForChain(0, curIndex); - _currentReceivingAddress = Future(() => newReceivingAddress); - } catch (e, s) { - Logging.instance.log( - "Failed to call _generateAddressForChain(0, $curIndex): $e\n$s", - level: LogLevel.Error); - } - final newTxData = await _fetchTransactionData(); - _transactionData = Future(() => newTxData); - - if (isActive || shouldAutoSync) { - timer ??= Timer.periodic(const Duration(seconds: 60), (timer) async { - //todo: check if print needed - // debugPrint("run timer"); - //TODO: check for new data and refresh if needed. if wownero even needs this - // chain height check currently broken - // if ((await chainHeight) != (await storedChainHeight)) { - // if (await refreshIfThereIsNewData()) { - await refresh(); - GlobalEventBus.instance.fire(UpdatedInBackgroundEvent( - "New data found in $walletId $walletName in background!", - walletId)); - // } - // } - }); - wowneroAutosaveTimer ??= - Timer.periodic(const Duration(seconds: 93), (timer) async { - //todo: check if print needed - // debugPrint("run wownero timer"); - if (isActive) { - await walletBase?.save(); - GlobalEventBus.instance.fire(UpdatedInBackgroundEvent( - "New data found in $walletId $walletName in background!", - walletId)); - } - }); - } - - if (stillSyncing) { - debugPrint("still syncing"); - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.syncing, - walletId, - coin, - ), - ); - refreshMutex = false; - return; - } - await stopSyncPercentTimer(); - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.synced, - walletId, - coin, - ), - ); - refreshMutex = false; - } catch (error, strace) { - refreshMutex = false; - await stopSyncPercentTimer(); - GlobalEventBus.instance.fire( - NodeConnectionStatusChangedEvent( - NodeConnectionStatus.disconnected, - walletId, - coin, - ), - ); - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.unableToSync, - walletId, - coin, - ), - ); - Logging.instance.log( - "Caught exception in refreshWalletData(): $error\n$strace", - level: LogLevel.Error); - } - } - - @override - // TODO: implement allOwnAddresses - Future> get allOwnAddresses { - return Future(() => []); - } - - @override - Future get balanceMinusMaxFee async => - (await availableBalance) - - Format.satoshisToAmount(await maxFee, coin: Coin.wownero); - - @override - Future get currentReceivingAddress => - _currentReceivingAddress ??= _getCurrentAddressForChain(0); - - @override - Future exit() async { - await stopSyncPercentTimer(); - _hasCalledExit = true; - isActive = false; - await walletBase?.save(prioritySave: true); - walletBase?.close(); - wowneroAutosaveTimer?.cancel(); - wowneroAutosaveTimer = null; - timer?.cancel(); - timer = null; - stopNetworkAlivePinging(); - } - - bool _hasCalledExit = false; - - @override - bool get hasCalledExit => _hasCalledExit; - - Future? _currentReceivingAddress; - - Future _getFees() async { - // TODO: not use random hard coded values here - return FeeObject( - numberOfBlocksFast: 10, - numberOfBlocksAverage: 15, - numberOfBlocksSlow: 20, - fast: MoneroTransactionPriority.fast.raw!, - medium: MoneroTransactionPriority.regular.raw!, - slow: MoneroTransactionPriority.slow.raw!, - ); - } - - @override - Future get fees => _feeObject ??= _getFees(); - Future? _feeObject; - - @override - // TODO: implement fullRescan - Future fullRescan( - int maxUnusedAddressGap, - int maxNumberOfIndexesToCheck, - ) async { - var restoreHeight = walletBase?.walletInfo.restoreHeight; - await walletBase?.rescan(height: restoreHeight); - GlobalEventBus.instance.fire( - WalletSyncStatusChangedEvent( - WalletSyncStatus.syncing, - walletId, - coin, - ), - ); - return; - } - - Future _generateAddressForChain(int chain, int index) async { - // - String address = walletBase!.getTransactionAddress(chain, index); - - return address; - } - - /// Adds [address] to the relevant chain's address array, which is determined by [chain]. - /// [address] - Expects a standard native segwit address - /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! - Future _addToAddressesArrayForChain(String address, int chain) async { - String chainArray = ''; - if (chain == 0) { - chainArray = 'receivingAddresses'; - } else { - chainArray = 'changeAddresses'; - } - - final addressArray = - DB.instance.get(boxName: walletId, key: chainArray); - if (addressArray == null) { - Logging.instance.log( - 'Attempting to add the following to $chainArray array for chain $chain:${[ - address - ]}', - level: LogLevel.Info); - await DB.instance - .put(boxName: walletId, key: chainArray, value: [address]); - } else { - // Make a deep copy of the existing list - final List newArray = []; - addressArray - .forEach((dynamic _address) => newArray.add(_address as String)); - newArray.add(address); // Add the address passed into the method - await DB.instance - .put(boxName: walletId, key: chainArray, value: newArray); - } - } - - /// Returns the latest receiving/change (external/internal) address for the wallet depending on [chain] - /// and - /// [chain] - Use 0 for receiving (external), 1 for change (internal). Should not be any other value! - Future _getCurrentAddressForChain(int chain) async { - // Here, we assume that chain == 1 if it isn't 0 - String arrayKey = chain == 0 ? "receivingAddresses" : "changeAddresses"; - final internalChainArray = (DB.instance - .get(boxName: walletId, key: arrayKey)) as List; - return internalChainArray.last as String; - } - - //TODO: take in the default language when creating wallet. - Future _generateNewWallet({int seedWordsLength = 14}) async { - Logging.instance - .log("IS_INTEGRATION_TEST: $integrationTestFlag", level: LogLevel.Info); - // TODO: ping wownero server and make sure the genesis hash matches - // if (!integrationTestFlag) { - // final features = await electrumXClient.getServerFeatures(); - // Logging.instance.log("features: $features"); - // if (_networkType == BasicNetworkType.main) { - // if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { - // throw Exception("genesis hash does not match main net!"); - // } - // } else if (_networkType == BasicNetworkType.test) { - // if (features['genesis_hash'] != GENESIS_HASH_TESTNET) { - // throw Exception("genesis hash does not match test net!"); - // } - // } - // } - - // this should never fail - if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) { - throw Exception( - "Attempted to overwrite mnemonic on generate new wallet!"); - } - - // TODO: Wallet Service may need to be switched to Wownero - walletService = - wownero.createWowneroWalletService(DB.instance.moneroWalletInfoBox); - keysStorage = KeyService(_secureStore); - WalletInfo walletInfo; - WalletCredentials credentials; - try { - String name = _walletId; - final dirPath = - await pathForWalletDir(name: name, type: WalletType.wownero); - final path = await pathForWallet(name: name, type: WalletType.wownero); - credentials = wownero.createWowneroNewWalletCredentials( - name: name, language: "English", seedWordsLength: seedWordsLength); - - walletInfo = WalletInfo.external( - id: WalletBase.idFor(name, WalletType.wownero), - name: name, - type: WalletType.wownero, - isRecovery: false, - restoreHeight: credentials.height ?? 0, - date: DateTime.now(), - path: path, - dirPath: dirPath, - // TODO: find out what to put for address - address: ''); - credentials.walletInfo = walletInfo; - - _walletCreationService = WalletCreationService( - secureStorage: _secureStore, - walletService: walletService, - keyService: keysStorage, - ); - _walletCreationService?.changeWalletType(); - // To restore from a seed - final wallet = await _walletCreationService?.create(credentials); - - final bufferedCreateHeight = (seedWordsLength == 14) - ? getSeedHeightSync(wallet?.seed.trim() as String) - : wownero.getHeightByDate( - date: DateTime.now().subtract(const Duration( - days: - 2))); // subtract a couple days to ensure we have a buffer for SWB - - await DB.instance.put( - boxName: walletId, key: "restoreHeight", value: bufferedCreateHeight); - walletInfo.restoreHeight = bufferedCreateHeight; - - await _secureStore.write( - key: '${_walletId}_mnemonic', value: wallet?.seed.trim()); - - walletInfo.address = wallet?.walletAddresses.address; - await DB.instance - .add(boxName: WalletInfo.boxName, value: walletInfo); - walletBase?.close(); - walletBase = wallet as WowneroWalletBase; - } catch (e, s) { - debugPrint(e.toString()); - debugPrint(s.toString()); - } - final node = await getCurrentNode(); - final host = Uri.parse(node.host).host; - await walletBase?.connectToNode( - node: Node(uri: "$host:${node.port}", type: WalletType.wownero)); - await walletBase?.startSync(); - await DB.instance - .put(boxName: walletId, key: "id", value: _walletId); - - // Set relevant indexes - await DB.instance - .put(boxName: walletId, key: "receivingIndex", value: 0); - await DB.instance - .put(boxName: walletId, key: "changeIndex", value: 0); - await DB.instance.put( - boxName: walletId, - key: 'blocked_tx_hashes', - value: ["0xdefault"], - ); // A list of transaction hashes to represent frozen utxos in wallet - // initialize address book entries - await DB.instance.put( - boxName: walletId, - key: 'addressBookEntries', - value: {}); - await DB.instance - .put(boxName: walletId, key: "isFavorite", value: false); - - // Generate and add addresses to relevant arrays - final initialReceivingAddress = await _generateAddressForChain(0, 0); - // final initialChangeAddress = await _generateAddressForChain(1, 0); - - await _addToAddressesArrayForChain(initialReceivingAddress, 0); - // await _addToAddressesArrayForChain(initialChangeAddress, 1); - - await DB.instance.put( - boxName: walletId, - key: 'receivingAddresses', - value: [initialReceivingAddress]); - await DB.instance - .put(boxName: walletId, key: "receivingIndex", value: 0); - - _currentReceivingAddress = Future(() => initialReceivingAddress); - - Logging.instance.log("_generateNewWalletFinished", level: LogLevel.Info); - } - - @override - // TODO: implement initializeWallet - Future initializeNew({int seedWordsLength = 14}) async { - await _prefs.init(); - // TODO: ping actual wownero network - // try { - // final hasNetwork = await _electrumXClient.ping(); - // if (!hasNetwork) { - // return false; - // } - // } catch (e, s) { - // Logging.instance.log("Caught in initializeWallet(): $e\n$s"); - // return false; - // } - walletService = - wownero.createWowneroWalletService(DB.instance.moneroWalletInfoBox); - keysStorage = KeyService(_secureStore); - - await _generateNewWallet(seedWordsLength: seedWordsLength); - // var password; - // try { - // password = - // await keysStorage?.getWalletPassword(walletName: this._walletId); - // } catch (e, s) { - // Logging.instance.log("$e $s"); - // Logging.instance.log("Generating new ${coin.ticker} wallet."); - // // Triggers for new users automatically. Generates new wallet - // await _generateNewWallet(wallet); - // await wallet.put("id", this._walletId); - // return true; - // } - // walletBase = (await walletService?.openWallet(this._walletId, password)) - // as WowneroWalletBase; - // Logging.instance.log("Opening existing ${coin.ticker} wallet."); - // // Wallet already exists, triggers for a returning user - // final currentAddress = awaicurrentHeightt _getCurrentAddressForChain(0); - // this._currentReceivingAddress = Future(() => currentAddress); - // - // await walletBase?.connectToNode( - // node: Node( - // uri: "xmr-node.cakewallet.com:18081", type: WalletType.wownero)); - // walletBase?.startSync(); - - return true; - } - - @override - Future initializeExisting() async { - Logging.instance.log( - "Opening existing ${coin.prettyName} wallet $walletName...", - level: LogLevel.Info); - - if ((DB.instance.get(boxName: walletId, key: "id")) == null) { - //todo: check if print needed - // debugPrint("Exception was thrown"); - throw Exception( - "Attempted to initialize an existing wallet using an unknown wallet ID!"); - } - - walletService = - wownero.createWowneroWalletService(DB.instance.moneroWalletInfoBox); - keysStorage = KeyService(_secureStore); - - await _prefs.init(); - final data = - DB.instance.get(boxName: walletId, key: "latest_tx_model") - as TransactionData?; - if (data != null) { - _transactionData = Future(() => data); - } - - String? password; - try { - password = await keysStorage?.getWalletPassword(walletName: _walletId); - } catch (e, s) { - //todo: check if print needed - // debugPrint("Exception was thrown $e $s"); - throw Exception("Password not found $e, $s"); - } - walletBase = (await walletService?.openWallet(_walletId, password!)) - as WowneroWalletBase; - debugPrint("walletBase $walletBase"); - Logging.instance.log( - "Opened existing ${coin.prettyName} wallet $walletName", - level: LogLevel.Info); - // Wallet already exists, triggers for a returning user - - String indexKey = "receivingIndex"; - final curIndex = - await DB.instance.get(boxName: walletId, key: indexKey) as int; - // Use new index to derive a new receiving address - final newReceivingAddress = await _generateAddressForChain(0, curIndex); - Logging.instance.log( - "wownero address in init existing: $newReceivingAddress", - level: LogLevel.Info); - _currentReceivingAddress = Future(() => newReceivingAddress); - } - - @override - Future get maxFee async { - var bal = await availableBalance; - var fee = walletBase!.calculateEstimatedFee( - wownero.getDefaultTransactionPriority(), - Format.decimalAmountToSatoshis(bal, coin), - ); - - return fee; - } - - @override - // TODO: implement pendingBalance - Future get pendingBalance => throw UnimplementedError(); - - bool longMutex = false; - - // TODO: are these needed? - - WalletService? walletService; - KeyService? keysStorage; - WowneroWalletBase? walletBase; - WalletCreationService? _walletCreationService; - - String toStringForinfo(WalletInfo info) { - return "id: ${info.id} name: ${info.name} type: ${info.type} recovery: ${info.isRecovery}" - " restoreheight: ${info.restoreHeight} timestamp: ${info.timestamp} dirPath: ${info.dirPath} " - "path: ${info.path} address: ${info.address} addresses: ${info.addresses}"; - } - - Future pathForWalletDir({ - required String name, - required WalletType type, - }) async { - Directory root = await StackFileSystem.applicationRootDirectory(); - - final prefix = walletTypeToString(type).toLowerCase(); - final walletsDir = Directory('${root.path}/wallets'); - final walletDire = Directory('${walletsDir.path}/$prefix/$name'); - - if (!walletDire.existsSync()) { - walletDire.createSync(recursive: true); - } - - return walletDire.path; - } - - Future pathForWallet({ - required String name, - required WalletType type, - }) async => - await pathForWalletDir(name: name, type: type) - .then((path) => '$path/$name'); - - // TODO: take in a dynamic height - @override - Future recoverFromMnemonic({ - required String mnemonic, - required int maxUnusedAddressGap, - required int maxNumberOfIndexesToCheck, - required int height, - }) async { - final int seedLength = mnemonic.trim().split(" ").length; - if (!(seedLength == 14 || seedLength == 25)) { - throw Exception("Invalid wownero mnemonic length found: $seedLength"); - } - - await _prefs.init(); - longMutex = true; - final start = DateTime.now(); - try { - // Logging.instance.log("IS_INTEGRATION_TEST: $integrationTestFlag"); - // if (!integrationTestFlag) { - // final features = await electrumXClient.getServerFeatures(); - // Logging.instance.log("features: $features"); - // if (_networkType == BasicNetworkType.main) { - // if (features['genesis_hash'] != GENESIS_HASH_MAINNET) { - // throw Exception("genesis hash does not match main net!"); - // } - // } else if (_networkType == BasicNetworkType.test) { - // if (features['genesis_hash'] != GENESIS_HASH_TESTNET) { - // throw Exception("genesis hash does not match test net!"); - // } - // } + // wow wallets cannot be open at the same time + // leave following commented out for now + + // if (!shouldAutoSync) { + // timer?.cancel(); + // moneroAutosaveTimer?.cancel(); + // timer = null; + // moneroAutosaveTimer = null; + // stopNetworkAlivePinging(); + // } else { + // startNetworkAlivePinging(); + // // Walletbase needs to be open for this to work + // refresh(); // } - // check to make sure we aren't overwriting a mnemonic - // this should never fail - if ((await _secureStore.read(key: '${_walletId}_mnemonic')) != null) { - longMutex = false; - throw Exception("Attempted to overwrite mnemonic on restore!"); - } - await _secureStore.write( - key: '${_walletId}_mnemonic', value: mnemonic.trim()); - - // extract seed height from 14 word seed - if (seedLength == 14) { - height = getSeedHeightSync(mnemonic.trim()); - } else { - // 25 word seed. TODO validate - if (height == 0) { - height = wownero.getHeightByDate( - date: DateTime.now().subtract(const Duration( - days: - 2))); // subtract a couple days to ensure we have a buffer for SWB\ - } - } - - await DB.instance - .put(boxName: walletId, key: "restoreHeight", value: height); - - walletService = - wownero.createWowneroWalletService(DB.instance.moneroWalletInfoBox); - keysStorage = KeyService(_secureStore); - WalletInfo walletInfo; - WalletCredentials credentials; - String name = _walletId; - final dirPath = - await pathForWalletDir(name: name, type: WalletType.wownero); - final path = await pathForWallet(name: name, type: WalletType.wownero); - credentials = wownero.createWowneroRestoreWalletFromSeedCredentials( - name: name, - height: height, - mnemonic: mnemonic.trim(), - ); - try { - walletInfo = WalletInfo.external( - id: WalletBase.idFor(name, WalletType.wownero), - name: name, - type: WalletType.wownero, - isRecovery: false, - restoreHeight: credentials.height ?? 0, - date: DateTime.now(), - path: path, - dirPath: dirPath, - // TODO: find out what to put for address - address: ''); - credentials.walletInfo = walletInfo; - - _walletCreationService = WalletCreationService( - secureStorage: _secureStore, - walletService: walletService, - keyService: keysStorage, - ); - _walletCreationService!.changeWalletType(); - // To restore from a seed - final wallet = - await _walletCreationService!.restoreFromSeed(credentials); - walletInfo.address = wallet.walletAddresses.address; - await DB.instance - .add(boxName: WalletInfo.boxName, value: walletInfo); - walletBase?.close(); - walletBase = wallet as WowneroWalletBase; - await DB.instance.put( - boxName: walletId, - key: 'receivingAddresses', - value: [walletInfo.address!]); - await DB.instance - .put(boxName: walletId, key: "receivingIndex", value: 0); - await DB.instance - .put(boxName: walletId, key: "id", value: _walletId); - await DB.instance - .put(boxName: walletId, key: "changeIndex", value: 0); - await DB.instance.put( - boxName: walletId, - key: 'blocked_tx_hashes', - value: ["0xdefault"], - ); // A list of transaction hashes to represent frozen utxos in wallet - // initialize address book entries - await DB.instance.put( - boxName: walletId, - key: 'addressBookEntries', - value: {}); - await DB.instance - .put(boxName: walletId, key: "isFavorite", value: false); - } catch (e, s) { - //todo: come back to this - debugPrint(e.toString()); - debugPrint(s.toString()); - } - final node = await getCurrentNode(); - final host = Uri.parse(node.host).host; - await walletBase?.connectToNode( - node: Node(uri: "$host:${node.port}", type: WalletType.wownero)); - await walletBase?.rescan(height: credentials.height); - } catch (e, s) { - Logging.instance.log( - "Exception rethrown from recoverFromMnemonic(): $e\n$s", - level: LogLevel.Error); - longMutex = false; - rethrow; - } - longMutex = false; - - final end = DateTime.now(); - Logging.instance.log( - "$walletName Recovery time: ${end.difference(start).inMilliseconds} millis", - level: LogLevel.Info); - } - - @override - Future send({ - required String toAddress, - required int amount, - Map args = const {}, - }) async { - try { - final txData = await prepareSend( - address: toAddress, satoshiAmount: amount, args: args); - final txHash = await confirmSend(txData: txData); - return txHash; - } catch (e, s) { - Logging.instance - .log("Exception rethrown from send(): $e\n$s", level: LogLevel.Error); - rethrow; } } - @override - Future testNetworkConnection() async { - return await walletBase?.isConnected() ?? false; - } - - Timer? _networkAliveTimer; - - void startNetworkAlivePinging() { - // call once on start right away - _periodicPingCheck(); - - // then periodically check - _networkAliveTimer = Timer.periodic( - Constants.networkAliveTimerDuration, - (_) async { - _periodicPingCheck(); - }, - ); - } - - void _periodicPingCheck() async { - bool hasNetwork = await testNetworkConnection(); - _isConnected = hasNetwork; - if (_isConnected != hasNetwork) { - NodeConnectionStatus status = hasNetwork - ? NodeConnectionStatus.connected - : NodeConnectionStatus.disconnected; - GlobalEventBus.instance - .fire(NodeConnectionStatusChangedEvent(status, walletId, coin)); - } - } - - void stopNetworkAlivePinging() { - _networkAliveTimer?.cancel(); - _networkAliveTimer = null; - } - - bool _isConnected = false; - - @override - bool get isConnected => _isConnected; - - @override - Future get totalBalance async { - var transactions = walletBase?.transactionHistory!.transactions; - int transactionBalance = 0; - for (var tx in transactions!.entries) { - if (tx.value.direction == TransactionDirection.incoming) { - transactionBalance += tx.value.amount!; - } else { - transactionBalance += -tx.value.amount! - tx.value.fee!; - } - } - - // TODO: grab total balance - var bal = 0; - for (var element in walletBase!.balance!.entries) { - bal = bal + element.value.fullBalance; - } - //todo: check if print needed - // debugPrint("balances: $transactionBalance $bal"); - if (isActive) { - String am = wowneroAmountToString(amount: bal); - - return Decimal.parse(am); - } else { - String am = wowneroAmountToString(amount: transactionBalance); - - return Decimal.parse(am); - } - } - - @override - // TODO: implement onIsActiveWalletChanged - void Function(bool)? get onIsActiveWalletChanged => (isActive) async { - await walletBase?.save(); - walletBase?.close(); - wowneroAutosaveTimer?.cancel(); - wowneroAutosaveTimer = null; - timer?.cancel(); - timer = null; - await stopSyncPercentTimer(); - if (isActive) { - String? password; - try { - password = - await keysStorage?.getWalletPassword(walletName: _walletId); - } catch (e, s) { - //todo: check if print needed - // debugPrint("Exception was thrown $e $s"); - throw Exception("Password not fou" - "*nd $e, $s"); - } - walletBase = (await walletService?.openWallet(_walletId, password!)) - as WowneroWalletBase?; - if (!(await walletBase!.isConnected())) { - final node = await getCurrentNode(); - final host = Uri.parse(node.host).host; - await walletBase?.connectToNode( - node: - Node(uri: "$host:${node.port}", type: WalletType.wownero)); - await walletBase?.startSync(); - } - await refresh(); - } - this.isActive = isActive; - }; - - bool isActive = false; - - @override - Future get transactionData => - _transactionData ??= _fetchTransactionData(); - Future? _transactionData; - - // not used in wownero - TransactionData? cachedTxData; - - @override - Future updateSentCachedTxData(Map txData) async { - // not used in wownero - } - - Future _fetchTransactionData() async { - final transactions = walletBase?.transactionHistory!.transactions; - - final cachedTransactions = - DB.instance.get(boxName: walletId, key: 'latest_tx_model') - as TransactionData?; - int latestTxnBlockHeight = - DB.instance.get(boxName: walletId, key: "storedTxnDataHeight") - as int? ?? - 0; - - final txidsList = DB.instance - .get(boxName: walletId, key: "cachedTxids") as List? ?? - []; - - final Set cachedTxids = Set.from(txidsList); - - // TODO: filter to skip cached + confirmed txn processing in next step - // final unconfirmedCachedTransactions = - // cachedTransactions?.getAllTransactions() ?? {}; - // unconfirmedCachedTransactions - // .removeWhere((key, value) => value.confirmedStatus); - // - // if (cachedTransactions != null) { - // for (final tx in allTxHashes.toList(growable: false)) { - // final txHeight = tx["height"] as int; - // if (txHeight > 0 && - // txHeight < latestTxnBlockHeight - MINIMUM_CONFIRMATIONS) { - // if (unconfirmedCachedTransactions[tx["tx_hash"] as String] == null) { - // allTxHashes.remove(tx); - // } - // } - // } - // } - - // sort thing stuff - // change to get Wownero price - final priceData = - await _priceAPI.getPricesAnd24hChange(baseCurrency: _prefs.currency); - Decimal currentPrice = priceData[coin]?.item1 ?? Decimal.zero; - final List> midSortedArray = []; - - if (transactions != null) { - for (var tx in transactions.entries) { - cachedTxids.add(tx.value.id); - Logging.instance.log( - "${tx.value.accountIndex} ${tx.value.addressIndex} ${tx.value.amount} ${tx.value.date} " - "${tx.value.direction} ${tx.value.fee} ${tx.value.height} ${tx.value.id} ${tx.value.isPending} ${tx.value.key} " - "${tx.value.recipientAddress}, ${tx.value.additionalInfo} con:${tx.value.confirmations}" - " ${tx.value.keyIndex}", - level: LogLevel.Info); - String am = wowneroAmountToString(amount: tx.value.amount!); - final worthNow = (currentPrice * Decimal.parse(am)).toStringAsFixed(2); - Map midSortedTx = {}; - // // create final tx map - midSortedTx["txid"] = tx.value.id; - midSortedTx["confirmed_status"] = !tx.value.isPending && - tx.value.confirmations != null && - tx.value.confirmations! >= MINIMUM_CONFIRMATIONS; - midSortedTx["confirmations"] = tx.value.confirmations ?? 0; - midSortedTx["timestamp"] = - (tx.value.date.millisecondsSinceEpoch ~/ 1000); - midSortedTx["txType"] = - tx.value.direction == TransactionDirection.incoming - ? "Received" - : "Sent"; - midSortedTx["amount"] = tx.value.amount; - midSortedTx["worthNow"] = worthNow; - midSortedTx["worthAtBlockTimestamp"] = worthNow; - midSortedTx["fees"] = tx.value.fee; - // TODO: shouldn't wownero have an address I can grab - if (tx.value.direction == TransactionDirection.incoming) { - final addressInfo = tx.value.additionalInfo; - - midSortedTx["address"] = walletBase?.getTransactionAddress( - addressInfo!['accountIndex'] as int, - addressInfo['addressIndex'] as int, - ); - } else { - midSortedTx["address"] = ""; - } - - final int txHeight = tx.value.height ?? 0; - midSortedTx["height"] = txHeight; - if (txHeight >= latestTxnBlockHeight) { - latestTxnBlockHeight = txHeight; - } - - midSortedTx["aliens"] = []; - midSortedTx["inputSize"] = 0; - midSortedTx["outputSize"] = 0; - midSortedTx["inputs"] = []; - midSortedTx["outputs"] = []; - midSortedArray.add(midSortedTx); - } - } - - // sort by date ---- - midSortedArray - .sort((a, b) => (b["timestamp"] as int) - (a["timestamp"] as int)); - Logging.instance.log(midSortedArray, level: LogLevel.Info); - - // buildDateTimeChunks - final Map result = {"dateTimeChunks": []}; - final dateArray = []; - - for (int i = 0; i < midSortedArray.length; i++) { - final txObject = midSortedArray[i]; - final date = extractDateFromTimestamp(txObject["timestamp"] as int); - final txTimeArray = [txObject["timestamp"], date]; - - if (dateArray.contains(txTimeArray[1])) { - result["dateTimeChunks"].forEach((dynamic chunk) { - if (extractDateFromTimestamp(chunk["timestamp"] as int) == - txTimeArray[1]) { - if (chunk["transactions"] == null) { - chunk["transactions"] = >[]; - } - chunk["transactions"].add(txObject); - } - }); - } else { - dateArray.add(txTimeArray[1]); - final chunk = { - "timestamp": txTimeArray[0], - "transactions": [txObject], - }; - result["dateTimeChunks"].add(chunk); - } - } - - final transactionsMap = cachedTransactions?.getAllTransactions() ?? {}; - transactionsMap - .addAll(TransactionData.fromJson(result).getAllTransactions()); - - final txModel = TransactionData.fromMap(transactionsMap); - - await DB.instance.put( - boxName: walletId, - key: 'storedTxnDataHeight', - value: latestTxnBlockHeight); - await DB.instance.put( - boxName: walletId, key: 'latest_tx_model', value: txModel); - await DB.instance.put( - boxName: walletId, - key: 'cachedTxids', - value: cachedTxids.toList(growable: false)); - - return txModel; - } - - @override - // TODO: implement unspentOutputs - Future> get unspentOutputs => throw UnimplementedError(); - - @override - bool validateAddress(String address) { - bool valid = walletBase!.validateAddress(address); - return valid; - } - - @override - String get walletId => _walletId; - late String _walletId; - @override String get walletName => _walletName; - late String _walletName; // setter for updating on rename @override set walletName(String newName) => _walletName = newName; - @override - set isFavorite(bool markFavorite) { - DB.instance.put( - boxName: walletId, key: "isFavorite", value: markFavorite); - } - - @override - bool get isFavorite { - try { - return DB.instance.get(boxName: walletId, key: "isFavorite") - as bool; - } catch (e, s) { - Logging.instance.log( - "isFavorite fetch failed (returning false by default): $e\n$s", - level: LogLevel.Error); - return false; - } - } - - @override - // TODO: implement availableBalance - Future get availableBalance async { - var bal = 0; - for (var element in walletBase!.balance!.entries) { - bal = bal + element.value.unlockedBalance; - } - String am = wowneroAmountToString(amount: bal); - - return Decimal.parse(am); - } - @override Coin get coin => _coin; @@ -1446,100 +165,13 @@ class WowneroWallet extends CoinServiceAPI { } } - // TODO: fix the double free memory crash error. @override - Future> prepareSend( - {required String address, - required int satoshiAmount, - Map? args}) async { - int amount = satoshiAmount; - String toAddress = address; - try { - final feeRate = args?["feeRate"]; - if (feeRate is FeeRateType) { - MoneroTransactionPriority feePriority; - switch (feeRate) { - case FeeRateType.fast: - feePriority = MoneroTransactionPriority.fast; - break; - case FeeRateType.average: - feePriority = MoneroTransactionPriority.regular; - break; - case FeeRateType.slow: - feePriority = MoneroTransactionPriority.slow; - break; - } - - Future? awaitPendingTransaction; - try { - // check for send all - bool isSendAll = false; - final balance = await availableBalance; - final satInDecimal = ((Decimal.fromInt(satoshiAmount) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal()); - if (satInDecimal == balance) { - isSendAll = true; - } - Logging.instance - .log("$toAddress $amount $args", level: LogLevel.Info); - String amountToSend = wowneroAmountToString(amount: amount); - Logging.instance.log("$amount $amountToSend", level: LogLevel.Info); - - wownero_output.Output output = wownero_output.Output(walletBase!); - output.address = toAddress; - output.sendAll = isSendAll; - output.setCryptoAmount(amountToSend); - - List outputs = [output]; - Object tmp = wownero.createWowneroTransactionCreationCredentials( - outputs: outputs, priority: feePriority); - - await prepareSendMutex.protect(() async { - awaitPendingTransaction = walletBase!.createTransaction(tmp); - }); - } catch (e, s) { - Logging.instance.log("Exception rethrown from prepareSend(): $e\n$s", - level: LogLevel.Warning); - } - - PendingWowneroTransaction pendingWowneroTransaction = - await (awaitPendingTransaction!) as PendingWowneroTransaction; - int realfee = Format.decimalAmountToSatoshis( - Decimal.parse(pendingWowneroTransaction.feeFormatted), coin); - //todo: check if print needed - // debugPrint("fee? $realfee"); - Map txData = { - "pendingWowneroTransaction": pendingWowneroTransaction, - "fee": realfee, - "addresss": toAddress, - "recipientAmt": satoshiAmount, - }; - - Logging.instance.log("prepare send: $txData", level: LogLevel.Info); - return txData; - } else { - throw ArgumentError("Invalid fee rate argument provided!"); - } - } catch (e, s) { - Logging.instance.log("Exception rethrown from prepare send(): $e\n$s", - level: LogLevel.Info); - - if (e.toString().contains("Incorrect unlocked balance")) { - throw Exception("Insufficient balance!"); - } else if (e is CreationTransactionException) { - throw Exception("Insufficient funds to pay for transaction fee!"); - } else { - throw Exception("Transaction failed with error code $e"); - } - } - } - - Mutex prepareSendMutex = Mutex(); - Mutex estimateFeeMutex = Mutex(); + Future get currentReceivingAddress async => + (await _currentReceivingAddress)?.value ?? + (await _generateAddressForChain(0, 0)).value; @override - Future estimateFeeFor(int satoshiAmount, int feeRate) async { + Future estimateFeeFor(Amount amount, int feeRate) async { MoneroTransactionPriority priority; FeeRateType feeRateType = FeeRateType.slow; switch (feeRate) { @@ -1571,41 +203,76 @@ class WowneroWallet extends CoinServiceAPI { try { aprox = (await prepareSend( // This address is only used for getting an approximate fee, never for sending - address: - "WW3iVcnoAY6K9zNdU4qmdvZELefx6xZz4PMpTwUifRkvMQckyadhSPYMVPJhBdYE8P9c27fg9RPmVaWNFx1cDaj61HnetqBiy", - satoshiAmount: satoshiAmount, + address: "WW3iVcnoAY6K9zNdU4qmdvZELefx6xZz4PMpTwUifRkvMQckyadhSPYMVPJhBdYE8P9c27fg9RPmVaWNFx1cDaj61HnetqBiy", + amount: amount, args: {"feeRate": feeRateType}))['fee']; - await Future.delayed(const Duration(milliseconds: 500)); + await Future.delayed(const Duration(milliseconds: 500)); } catch (e, s) { - aprox = walletBase!.calculateEstimatedFee(priority, satoshiAmount); + aprox = walletBase!.calculateEstimatedFee( + priority, + amount.raw.toInt(), + ); } } }); - print("this is the aprox fee $aprox for $satoshiAmount"); - final fee = (aprox as int); - return fee; + print("this is the aprox fee $aprox for $amount"); + + if (aprox is Amount) { + return aprox as Amount; + } else { + return Amount( + rawValue: BigInt.from(aprox as int), + fractionDigits: coin.decimals, + ); + } + } + + @override + Future exit() async { + if (!_hasCalledExit) { + walletBase?.onNewBlock = null; + walletBase?.onNewTransaction = null; + walletBase?.syncStatusChanged = null; + _hasCalledExit = true; + _autoSaveTimer?.cancel(); + await walletBase?.save(prioritySave: true); + walletBase?.close(); + } + } + + @override + Future get fees => _feeObject ??= _getFees(); + + @override + Future fullRescan( + int maxUnusedAddressGap, + int maxNumberOfIndexesToCheck, + ) async { + // clear blockchain info + await db.deleteWalletBlockchainData(walletId); + + var restoreHeight = walletBase?.walletInfo.restoreHeight; + highestPercentCached = 0; + await walletBase?.rescan(height: restoreHeight); + await refresh(); } @override Future generateNewAddress() async { try { - const String indexKey = "receivingIndex"; - // First increment the receiving index - await _incrementAddressIndexForChain(0); - final newReceivingIndex = - DB.instance.get(boxName: walletId, key: indexKey) as int; + final currentReceiving = await _currentReceivingAddress; + + final newReceivingIndex = currentReceiving!.derivationIndex + 1; // Use new index to derive a new receiving address - final newReceivingAddress = - await _generateAddressForChain(0, newReceivingIndex); + final newReceivingAddress = await _generateAddressForChain( + 0, + newReceivingIndex, + ); - // Add that new receiving address to the array of receiving addresses - await _addToAddressesArrayForChain(newReceivingAddress, 0); - - // Set the new receiving address that the service - - _currentReceivingAddress = Future(() => newReceivingAddress); + // Add that new receiving address + await db.putAddress(newReceivingAddress); return true; } catch (e, s) { @@ -1615,4 +282,1058 @@ class WowneroWallet extends CoinServiceAPI { return false; } } + + @override + bool get hasCalledExit => _hasCalledExit; + + @override + Future initializeExisting() async { + Logging.instance.log( + "initializeExisting() ${coin.prettyName} wallet $walletName...", + level: LogLevel.Info); + + if (getCachedId() == null) { + //todo: check if print needed + // debugPrint("Exception was thrown"); + throw Exception( + "Attempted to initialize an existing wallet using an unknown wallet ID!"); + } + + walletService = + wownero.createWowneroWalletService(DB.instance.moneroWalletInfoBox); + keysStorage = KeyService(_secureStorage); + + await _prefs.init(); + + String? password; + try { + password = await keysStorage?.getWalletPassword(walletName: _walletId); + } catch (e, s) { + throw Exception("Password not found $e, $s"); + } + walletBase = (await walletService?.openWallet(_walletId, password!)) + as WowneroWalletBase; + + // await _checkCurrentReceivingAddressesForTransactions(); + + Logging.instance.log( + "Opened existing ${coin.prettyName} wallet $walletName", + level: LogLevel.Info, + ); + } + + @override + Future initializeNew({int seedWordsLength = 14}) async { + await _prefs.init(); + + // this should never fail + if ((await mnemonicString) != null || (await mnemonicPassphrase) != null) { + throw Exception( + "Attempted to overwrite mnemonic on generate new wallet!"); + } + + // TODO: Wallet Service may need to be switched to Wownero + walletService = + wownero.createWowneroWalletService(DB.instance.moneroWalletInfoBox); + keysStorage = KeyService(_secureStorage); + WalletInfo walletInfo; + WalletCredentials credentials; + try { + String name = _walletId; + final dirPath = + await _pathForWalletDir(name: name, type: WalletType.wownero); + final path = await _pathForWallet(name: name, type: WalletType.wownero); + credentials = wownero.createWowneroNewWalletCredentials( + name: name, + language: "English", + seedWordsLength: seedWordsLength, + ); + + walletInfo = WalletInfo.external( + id: WalletBase.idFor(name, WalletType.wownero), + name: name, + type: WalletType.wownero, + isRecovery: false, + restoreHeight: credentials.height ?? 0, + date: DateTime.now(), + path: path, + dirPath: dirPath, + // TODO: find out what to put for address + address: '', + ); + credentials.walletInfo = walletInfo; + + _walletCreationService = WalletCreationService( + secureStorage: _secureStorage, + walletService: walletService, + keyService: keysStorage, + ); + _walletCreationService?.changeWalletType(); + // To restore from a seed + final wallet = await _walletCreationService?.create(credentials); + + final bufferedCreateHeight = (seedWordsLength == 14) + ? getSeedHeightSync(wallet?.seed.trim() as String) + : wownero.getHeightByDate( + date: DateTime.now().subtract(const Duration( + days: + 2))); // subtract a couple days to ensure we have a buffer for SWB + + await DB.instance.put( + boxName: walletId, key: "restoreHeight", value: bufferedCreateHeight); + walletInfo.restoreHeight = bufferedCreateHeight; + + await _secureStorage.write( + key: '${_walletId}_mnemonic', value: wallet?.seed.trim()); + await _secureStorage.write( + key: '${_walletId}_mnemonicPassphrase', + value: "", + ); + + walletInfo.address = wallet?.walletAddresses.address; + await DB.instance + .add(boxName: WalletInfo.boxName, value: walletInfo); + walletBase?.close(); + walletBase = wallet as WowneroWalletBase; + } catch (e, s) { + debugPrint(e.toString()); + debugPrint(s.toString()); + walletBase?.close(); + } + final node = await _getCurrentNode(); + final host = Uri.parse(node.host).host; + await walletBase?.connectToNode( + node: Node( + uri: "$host:${node.port}", + type: WalletType.wownero, + trusted: node.trusted ?? false, + ), + ); + await walletBase?.startSync(); + await Future.wait([ + updateCachedId(walletId), + updateCachedIsFavorite(false), + ]); + + // Generate and add addresses to relevant arrays + final initialReceivingAddress = await _generateAddressForChain(0, 0); + // final initialChangeAddress = await _generateAddressForChain(1, 0); + + await db.putAddress(initialReceivingAddress); + + walletBase?.close(); + + Logging.instance + .log("initializeNew for $walletName $walletId", level: LogLevel.Info); + } + + @override + bool get isConnected => _isConnected; + + @override + bool get isRefreshing => refreshMutex; + + @override + // not used in wow + Future get maxFee => throw UnimplementedError(); + + @override + Future> get mnemonic async { + final _mnemonicString = await mnemonicString; + if (_mnemonicString == null) { + return []; + } + final List data = _mnemonicString.split(' '); + return data; + } + + @override + Future get mnemonicString => + _secureStorage.read(key: '${_walletId}_mnemonic'); + + @override + Future get mnemonicPassphrase => _secureStorage.read( + key: '${_walletId}_mnemonicPassphrase', + ); + + @override + Future> prepareSend({ + required String address, + required Amount amount, + Map? args, + }) async { + try { + final feeRate = args?["feeRate"]; + if (feeRate is FeeRateType) { + MoneroTransactionPriority feePriority; + switch (feeRate) { + case FeeRateType.fast: + feePriority = MoneroTransactionPriority.fast; + break; + case FeeRateType.average: + feePriority = MoneroTransactionPriority.regular; + break; + case FeeRateType.slow: + feePriority = MoneroTransactionPriority.slow; + break; + } + + Future? awaitPendingTransaction; + try { + // check for send all + bool isSendAll = false; + final balance = await _availableBalance; + if (amount == balance) { + isSendAll = true; + } + Logging.instance.log("$address $amount $args", level: LogLevel.Info); + String amountToSend = amount.decimal.toString(); + Logging.instance.log("$amount $amountToSend", level: LogLevel.Info); + + wownero_output.Output output = wownero_output.Output(walletBase!); + output.address = address; + output.sendAll = isSendAll; + output.setCryptoAmount(amountToSend); + + List outputs = [output]; + Object tmp = wownero.createWowneroTransactionCreationCredentials( + outputs: outputs, + priority: feePriority, + ); + + await prepareSendMutex.protect(() async { + awaitPendingTransaction = walletBase!.createTransaction(tmp); + }); + } catch (e, s) { + Logging.instance.log("Exception rethrown from prepareSend(): $e\n$s", + level: LogLevel.Warning); + } + + PendingWowneroTransaction pendingWowneroTransaction = + await (awaitPendingTransaction!) as PendingWowneroTransaction; + final int realFee = Amount.fromDecimal( + Decimal.parse(pendingWowneroTransaction.feeFormatted), + fractionDigits: coin.decimals, + ).raw.toInt(); + + Map txData = { + "pendingWowneroTransaction": pendingWowneroTransaction, + "fee": realFee, + "addresss": address, + "recipientAmt": amount, + }; + + Logging.instance.log("prepare send: $txData", level: LogLevel.Info); + return txData; + } else { + throw ArgumentError("Invalid fee rate argument provided!"); + } + } catch (e, s) { + Logging.instance.log("Exception rethrown from prepare send(): $e\n$s", + level: LogLevel.Info); + + if (e.toString().contains("Incorrect unlocked balance")) { + throw Exception("Insufficient balance!"); + } else if (e is CreationTransactionException) { + throw Exception("Insufficient funds to pay for transaction fee!"); + } else { + throw Exception("Transaction failed with error code $e"); + } + } + } + + @override + Future recoverFromMnemonic({ + required String mnemonic, + String? mnemonicPassphrase, // not used at the moment + required int maxUnusedAddressGap, + required int maxNumberOfIndexesToCheck, + required int height, + }) async { + final int seedLength = mnemonic.trim().split(" ").length; + if (!(seedLength == 14 || seedLength == 25)) { + throw Exception("Invalid wownero mnemonic length found: $seedLength"); + } + + await _prefs.init(); + longMutex = true; + final start = DateTime.now(); + try { + // check to make sure we aren't overwriting a mnemonic + // this should never fail + if ((await mnemonicString) != null || + (await this.mnemonicPassphrase) != null) { + longMutex = false; + throw Exception("Attempted to overwrite mnemonic on restore!"); + } + await _secureStorage.write( + key: '${_walletId}_mnemonic', value: mnemonic.trim()); + await _secureStorage.write( + key: '${_walletId}_mnemonicPassphrase', + value: mnemonicPassphrase ?? "", + ); + + // extract seed height from 14 word seed + if (seedLength == 14) { + height = getSeedHeightSync(mnemonic.trim()); + } else { + // 25 word seed. TODO validate + if (height == 0) { + height = wownero.getHeightByDate( + date: DateTime.now().subtract(const Duration( + days: + 2))); // subtract a couple days to ensure we have a buffer for SWB\ + } + } + + await DB.instance + .put(boxName: walletId, key: "restoreHeight", value: height); + + walletService = + wownero.createWowneroWalletService(DB.instance.moneroWalletInfoBox); + keysStorage = KeyService(_secureStorage); + WalletInfo walletInfo; + WalletCredentials credentials; + String name = _walletId; + final dirPath = + await _pathForWalletDir(name: name, type: WalletType.wownero); + final path = await _pathForWallet(name: name, type: WalletType.wownero); + credentials = wownero.createWowneroRestoreWalletFromSeedCredentials( + name: name, + height: height, + mnemonic: mnemonic.trim(), + ); + try { + walletInfo = WalletInfo.external( + id: WalletBase.idFor(name, WalletType.wownero), + name: name, + type: WalletType.wownero, + isRecovery: false, + restoreHeight: credentials.height ?? 0, + date: DateTime.now(), + path: path, + dirPath: dirPath, + // TODO: find out what to put for address + address: ''); + credentials.walletInfo = walletInfo; + + _walletCreationService = WalletCreationService( + secureStorage: _secureStorage, + walletService: walletService, + keyService: keysStorage, + ); + _walletCreationService!.changeWalletType(); + // To restore from a seed + final wallet = + await _walletCreationService!.restoreFromSeed(credentials); + walletInfo.address = wallet.walletAddresses.address; + await DB.instance + .add(boxName: WalletInfo.boxName, value: walletInfo); + walletBase?.close(); + walletBase = wallet as WowneroWalletBase; + + await Future.wait([ + updateCachedId(walletId), + updateCachedIsFavorite(false), + ]); + } catch (e, s) { + //todo: come back to this + debugPrint(e.toString()); + debugPrint(s.toString()); + } + final node = await _getCurrentNode(); + final host = Uri.parse(node.host).host; + await walletBase?.connectToNode( + node: Node( + uri: "$host:${node.port}", + type: WalletType.wownero, + trusted: node.trusted ?? false, + ), + ); + await walletBase?.rescan(height: credentials.height); + walletBase?.close(); + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from recoverFromMnemonic(): $e\n$s", + level: LogLevel.Error); + longMutex = false; + rethrow; + } + longMutex = false; + + final end = DateTime.now(); + Logging.instance.log( + "$walletName Recovery time: ${end.difference(start).inMilliseconds} millis", + level: LogLevel.Info); + } + + @override + Future refresh() async { + if (refreshMutex) { + Logging.instance.log("$walletId $walletName refreshMutex denied", + level: LogLevel.Info); + return; + } else { + refreshMutex = true; + } + + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.syncing, + walletId, + coin, + ), + ); + + await _refreshTransactions(); + await _updateBalance(); + + await _checkCurrentReceivingAddressesForTransactions(); + + if (walletBase?.syncStatus is SyncedSyncStatus) { + refreshMutex = false; + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.synced, + walletId, + coin, + ), + ); + } + } + + @override + Future testNetworkConnection() async { + return await walletBase?.isConnected() ?? false; + } + + bool _isActive = false; + + @override + void Function(bool)? get onIsActiveWalletChanged => (isActive) async { + if (_isActive == isActive) { + return; + } + _isActive = isActive; + + if (isActive) { + _hasCalledExit = false; + String? password; + try { + password = + await keysStorage?.getWalletPassword(walletName: _walletId); + } catch (e, s) { + throw Exception("Password not found $e, $s"); + } + walletBase = (await walletService?.openWallet(_walletId, password!)) + as WowneroWalletBase?; + + walletBase!.onNewBlock = onNewBlock; + walletBase!.onNewTransaction = onNewTransaction; + walletBase!.syncStatusChanged = syncStatusChanged; + + if (!(await walletBase!.isConnected())) { + final node = await _getCurrentNode(); + final host = Uri.parse(node.host).host; + await walletBase?.connectToNode( + node: Node( + uri: "$host:${node.port}", + type: WalletType.wownero, + trusted: node.trusted ?? false, + ), + ); + } + await walletBase?.startSync(); + await refresh(); + _autoSaveTimer?.cancel(); + _autoSaveTimer = Timer.periodic( + const Duration(seconds: 193), + (_) async => await walletBase?.save(), + ); + } else { + await exit(); + } + }; + + Future _updateCachedBalance(int sats) async { + await DB.instance.put( + boxName: walletId, + key: "cachedWowneroBalanceSats", + value: sats, + ); + } + + int _getCachedBalance() => + DB.instance.get( + boxName: walletId, + key: "cachedWowneroBalanceSats", + ) as int? ?? + 0; + + Future _updateBalance() async { + final total = await _totalBalance; + final available = await _availableBalance; + _balance = Balance( + total: total, + spendable: available, + blockedTotal: Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ), + pendingSpendable: total - available, + ); + await updateCachedBalance(_balance!); + } + + Future get _availableBalance async { + try { + int runningBalance = 0; + for (final entry in walletBase!.balance!.entries) { + runningBalance += entry.value.unlockedBalance; + } + return Amount( + rawValue: BigInt.from(runningBalance), + fractionDigits: coin.decimals, + ); + } catch (_) { + return Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); + } + } + + Future get _totalBalance async { + try { + final balanceEntries = walletBase?.balance?.entries; + if (balanceEntries != null) { + int bal = 0; + for (var element in balanceEntries) { + bal = bal + element.value.fullBalance; + } + await _updateCachedBalance(bal); + return Amount( + rawValue: BigInt.from(bal), + fractionDigits: coin.decimals, + ); + } else { + final transactions = walletBase!.transactionHistory!.transactions; + int transactionBalance = 0; + for (var tx in transactions!.entries) { + if (tx.value.direction == TransactionDirection.incoming) { + transactionBalance += tx.value.amount!; + } else { + transactionBalance += -tx.value.amount! - tx.value.fee!; + } + } + + await _updateCachedBalance(transactionBalance); + return Amount( + rawValue: BigInt.from(transactionBalance), + fractionDigits: coin.decimals, + ); + } + } catch (_) { + return Amount( + rawValue: BigInt.from(_getCachedBalance()), + fractionDigits: coin.decimals, + ); + } + } + + @override + Future updateNode(bool shouldRefresh) async { + final node = await _getCurrentNode(); + + final host = Uri.parse(node.host).host; + await walletBase?.connectToNode( + node: Node( + uri: "$host:${node.port}", + type: WalletType.wownero, + trusted: node.trusted ?? false, + ), + ); + + // TODO: is this sync call needed? Do we need to notify ui here? + await walletBase?.startSync(); + + if (shouldRefresh) { + await refresh(); + } + } + + @override + Future updateSentCachedTxData(Map txData) async { + // not used for xmr + return; + } + + @override + bool validateAddress(String address) => walletBase!.validateAddress(address); + + @override + String get walletId => _walletId; + + Future _generateAddressForChain( + int chain, + int index, + ) async { + // + String address = walletBase!.getTransactionAddress(chain, index); + + return isar_models.Address( + walletId: walletId, + derivationIndex: index, + derivationPath: null, + value: address, + publicKey: [], + type: isar_models.AddressType.cryptonote, + subType: chain == 0 + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.change, + ); + } + + Future _getFees() async { + // TODO: not use random hard coded values here + return FeeObject( + numberOfBlocksFast: 10, + numberOfBlocksAverage: 15, + numberOfBlocksSlow: 20, + fast: MoneroTransactionPriority.fast.raw!, + medium: MoneroTransactionPriority.regular.raw!, + slow: MoneroTransactionPriority.slow.raw!, + ); + } + + Future _refreshTransactions() async { + await walletBase!.updateTransactions(); + final transactions = walletBase?.transactionHistory!.transactions; + + // final cachedTransactions = + // DB.instance.get(boxName: walletId, key: 'latest_tx_model') + // as TransactionData?; + // int latestTxnBlockHeight = + // DB.instance.get(boxName: walletId, key: "storedTxnDataHeight") + // as int? ?? + // 0; + // + // final txidsList = DB.instance + // .get(boxName: walletId, key: "cachedTxids") as List? ?? + // []; + // + // final Set cachedTxids = Set.from(txidsList); + + // TODO: filter to skip cached + confirmed txn processing in next step + // final unconfirmedCachedTransactions = + // cachedTransactions?.getAllTransactions() ?? {}; + // unconfirmedCachedTransactions + // .removeWhere((key, value) => value.confirmedStatus); + // + // if (cachedTransactions != null) { + // for (final tx in allTxHashes.toList(growable: false)) { + // final txHeight = tx["height"] as int; + // if (txHeight > 0 && + // txHeight < latestTxnBlockHeight - MINIMUM_CONFIRMATIONS) { + // if (unconfirmedCachedTransactions[tx["tx_hash"] as String] == null) { + // allTxHashes.remove(tx); + // } + // } + // } + // } + + final List> txnsData = + []; + + if (transactions != null) { + for (var tx in transactions.entries) { + // cachedTxids.add(tx.value.id); + // Logging.instance.log( + // "${tx.value.accountIndex} ${tx.value.addressIndex} ${tx.value.amount} ${tx.value.date} " + // "${tx.value.direction} ${tx.value.fee} ${tx.value.height} ${tx.value.id} ${tx.value.isPending} ${tx.value.key} " + // "${tx.value.recipientAddress}, ${tx.value.additionalInfo} con:${tx.value.confirmations}" + // " ${tx.value.keyIndex}", + // level: LogLevel.Info); + // String am = wowneroAmountToString(amount: tx.value.amount!); + // final worthNow = (currentPrice * Decimal.parse(am)).toStringAsFixed(2); + // Map midSortedTx = {}; + // // // create final tx map + // midSortedTx["txid"] = tx.value.id; + // midSortedTx["confirmed_status"] = !tx.value.isPending && + // tx.value.confirmations != null && + // tx.value.confirmations! >= MINIMUM_CONFIRMATIONS; + // midSortedTx["confirmations"] = tx.value.confirmations ?? 0; + // midSortedTx["timestamp"] = + // (tx.value.date.millisecondsSinceEpoch ~/ 1000); + // midSortedTx["txType"] = + // tx.value.direction == TransactionDirection.incoming + // ? "Received" + // : "Sent"; + // midSortedTx["amount"] = tx.value.amount; + // midSortedTx["worthNow"] = worthNow; + // midSortedTx["worthAtBlockTimestamp"] = worthNow; + // midSortedTx["fees"] = tx.value.fee; + // if (tx.value.direction == TransactionDirection.incoming) { + // final addressInfo = tx.value.additionalInfo; + // + // midSortedTx["address"] = walletBase?.getTransactionAddress( + // addressInfo!['accountIndex'] as int, + // addressInfo['addressIndex'] as int, + // ); + // } else { + // midSortedTx["address"] = ""; + // } + // + // final int txHeight = tx.value.height ?? 0; + // midSortedTx["height"] = txHeight; + // // if (txHeight >= latestTxnBlockHeight) { + // // latestTxnBlockHeight = txHeight; + // // } + // + // midSortedTx["aliens"] = []; + // midSortedTx["inputSize"] = 0; + // midSortedTx["outputSize"] = 0; + // midSortedTx["inputs"] = []; + // midSortedTx["outputs"] = []; + // midSortedArray.add(midSortedTx); + + isar_models.Address? address; + isar_models.TransactionType type; + if (tx.value.direction == TransactionDirection.incoming) { + final addressInfo = tx.value.additionalInfo; + + final addressString = walletBase?.getTransactionAddress( + addressInfo!['accountIndex'] as int, + addressInfo['addressIndex'] as int, + ); + + if (addressString != null) { + address = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(addressString) + .findFirst(); + } + + type = isar_models.TransactionType.incoming; + } else { + // txn.address = ""; + type = isar_models.TransactionType.outgoing; + } + + final txn = isar_models.Transaction( + walletId: walletId, + txid: tx.value.id, + timestamp: (tx.value.date.millisecondsSinceEpoch ~/ 1000), + type: type, + subType: isar_models.TransactionSubType.none, + amount: tx.value.amount ?? 0, + amountString: Amount( + rawValue: BigInt.from(tx.value.amount ?? 0), + fractionDigits: coin.decimals, + ).toJsonString(), + fee: tx.value.fee ?? 0, + height: tx.value.height, + isCancelled: false, + isLelantus: false, + slateId: null, + otherData: null, + nonce: null, + inputs: [], + outputs: [], + ); + + txnsData.add(Tuple2(txn, address)); + } + } + + await db.addNewTransactionData(txnsData, walletId); + + // quick hack to notify manager to call notifyListeners if + // transactions changed + if (txnsData.isNotEmpty) { + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "Transactions updated/added for: $walletId $walletName ", + walletId, + ), + ); + } + } + + Future _pathForWalletDir({ + required String name, + required WalletType type, + }) async { + Directory root = await StackFileSystem.applicationRootDirectory(); + + final prefix = walletTypeToString(type).toLowerCase(); + final walletsDir = Directory('${root.path}/wallets'); + final walletDire = Directory('${walletsDir.path}/$prefix/$name'); + + if (!walletDire.existsSync()) { + walletDire.createSync(recursive: true); + } + + return walletDire.path; + } + + Future _pathForWallet({ + required String name, + required WalletType type, + }) async => + await _pathForWalletDir(name: name, type: type) + .then((path) => '$path/$name'); + + Future _getCurrentNode() async { + return NodeService(secureStorageInterface: _secureStorage) + .getPrimaryNodeFor(coin: coin) ?? + DefaultNodes.getNodeFor(coin); + } + + void onNewBlock({required int height, required int blocksLeft}) { + // + print("============================="); + print("New Wownero Block! :: $walletName"); + print("============================="); + updateCachedChainHeight(height); + _refreshTxDataHelper(); + } + + bool _txRefreshLock = false; + int _lastCheckedHeight = -1; + int _txCount = 0; + + Future _refreshTxDataHelper() async { + if (_txRefreshLock) return; + _txRefreshLock = true; + + final syncStatus = walletBase?.syncStatus; + + if (syncStatus != null && syncStatus is SyncingSyncStatus) { + final int blocksLeft = syncStatus.blocksLeft; + final tenKChange = blocksLeft ~/ 10000; + + // only refresh transactions periodically during a sync + if (_lastCheckedHeight == -1 || tenKChange < _lastCheckedHeight) { + _lastCheckedHeight = tenKChange; + await _refreshTxData(); + } + } else { + await _refreshTxData(); + } + + _txRefreshLock = false; + } + + Future _refreshTxData() async { + await _refreshTransactions(); + final count = await db.getTransactions(walletId).count(); + + if (count > _txCount) { + _txCount = count; + await _updateBalance(); + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "New transaction data found in $walletId $walletName!", + walletId, + ), + ); + } + } + + void onNewTransaction() { + // + print("============================="); + print("New Wownero Transaction! :: $walletName"); + print("============================="); + + // call this here? + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "New data found in $walletId $walletName in background!", + walletId, + ), + ); + } + + void syncStatusChanged() async { + final syncStatus = walletBase?.syncStatus; + if (syncStatus != null) { + if (syncStatus.progress() == 1) { + refreshMutex = false; + } + + WalletSyncStatus? status; + _isConnected = true; + + if (syncStatus is SyncingSyncStatus) { + final int blocksLeft = syncStatus.blocksLeft; + + // ensure at least 1 to prevent math errors + final int height = max(1, syncStatus.height); + + final nodeHeight = height + blocksLeft; + + final percent = height / nodeHeight; + + final highest = max(highestPercentCached, percent); + + // update cached + if (highestPercentCached < percent) { + highestPercentCached = percent; + } + await updateCachedChainHeight(height); + + GlobalEventBus.instance.fire( + RefreshPercentChangedEvent( + highest, + walletId, + ), + ); + GlobalEventBus.instance.fire( + BlocksRemainingEvent( + blocksLeft, + walletId, + ), + ); + } else if (syncStatus is SyncedSyncStatus) { + status = WalletSyncStatus.synced; + } else if (syncStatus is NotConnectedSyncStatus) { + status = WalletSyncStatus.unableToSync; + _isConnected = false; + } else if (syncStatus is StartingSyncStatus) { + status = WalletSyncStatus.syncing; + GlobalEventBus.instance.fire( + RefreshPercentChangedEvent( + highestPercentCached, + walletId, + ), + ); + } else if (syncStatus is FailedSyncStatus) { + status = WalletSyncStatus.unableToSync; + _isConnected = false; + } else if (syncStatus is ConnectingSyncStatus) { + status = WalletSyncStatus.syncing; + GlobalEventBus.instance.fire( + RefreshPercentChangedEvent( + highestPercentCached, + walletId, + ), + ); + } else if (syncStatus is ConnectedSyncStatus) { + status = WalletSyncStatus.syncing; + GlobalEventBus.instance.fire( + RefreshPercentChangedEvent( + highestPercentCached, + walletId, + ), + ); + } else if (syncStatus is LostConnectionSyncStatus) { + status = WalletSyncStatus.unableToSync; + _isConnected = false; + } + + if (status != null) { + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + status, + walletId, + coin, + ), + ); + } + } + } + + Future _checkCurrentReceivingAddressesForTransactions() async { + try { + await _checkReceivingAddressForTransactions(); + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _checkCurrentReceivingAddressesForTransactions(): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + Future _checkReceivingAddressForTransactions() async { + try { + int highestIndex = -1; + for (var element + in walletBase!.transactionHistory!.transactions!.entries) { + if (element.value.direction == TransactionDirection.incoming) { + int curAddressIndex = + element.value.additionalInfo!['addressIndex'] as int; + if (curAddressIndex > highestIndex) { + highestIndex = curAddressIndex; + } + } + } + + // Check the new receiving index + final currentReceiving = await _currentReceivingAddress; + final curIndex = currentReceiving?.derivationIndex ?? -1; + + if (highestIndex >= curIndex) { + // First increment the receiving index + final newReceivingIndex = curIndex + 1; + + // Use new index to derive a new receiving address + final newReceivingAddress = + await _generateAddressForChain(0, newReceivingIndex); + + final existing = await db + .getAddresses(walletId) + .filter() + .valueEqualTo(newReceivingAddress.value) + .findFirst(); + if (existing == null) { + // Add that new change address + await db.putAddress(newReceivingAddress); + } else { + // we need to update the address + await db.updateAddress(existing, newReceivingAddress); + } + // keep checking until address with no tx history is set as current + await _checkReceivingAddressForTransactions(); + } + } on SocketException catch (se, s) { + Logging.instance.log( + "SocketException caught in _checkReceivingAddressForTransactions(): $se\n$s", + level: LogLevel.Error); + return; + } catch (e, s) { + Logging.instance.log( + "Exception rethrown from _checkReceivingAddressForTransactions(): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + double get highestPercentCached => + DB.instance.get(boxName: walletId, key: "highestPercentCached") + as double? ?? + 0; + + set highestPercentCached(double value) => DB.instance.put( + boxName: walletId, + key: "highestPercentCached", + value: value, + ); + + @override + int get storedChainHeight => getCachedChainHeight(); + + @override + Balance get balance => _balance ??= getCachedBalance(); + Balance? _balance; + + @override + Future> get transactions => + db.getTransactions(walletId).sortByTimestampDesc().findAll(); + + @override + // TODO: implement utxos + Future> get utxos => throw UnimplementedError(); } diff --git a/lib/services/debug_service.dart b/lib/services/debug_service.dart index 6557fc96e..dadb76990 100644 --- a/lib/services/debug_service.dart +++ b/lib/services/debug_service.dart @@ -15,8 +15,6 @@ class DebugService extends ChangeNotifier { late final Isar isar; // late final Stream logsChanged; - final int numberOfRecentLogsToLoad = 500; - // bool _shouldPause = false; // // void togglePauseUiUpdates() { @@ -36,44 +34,49 @@ class DebugService extends ChangeNotifier { // }); } - List _recentLogs = []; - List get recentLogs => _recentLogs; + List get recentLogs => isar.logs.where().limit(200).findAllSync(); - Future updateRecentLogs() async { - int totalCount = await isar.logs.count(); - int offset = totalCount - numberOfRecentLogsToLoad; - if (offset < 0) { - offset = 0; - } + // Future updateRecentLogs() async { + // int totalCount = await isar.logs.count(); + // int offset = totalCount - numberOfRecentLogsToLoad; + // if (offset < 0) { + // offset = 0; + // } + // + // _recentLogs = (await isar.logs + // .where() + // .anyTimestampInMillisUTC() + // .offset(offset) + // .limit(numberOfRecentLogsToLoad) + // .findAll()); + // notifyListeners(); + // } - _recentLogs = (await isar.logs - .where() - .anyTimestampInMillisUTC() - .offset(offset) - .limit(numberOfRecentLogsToLoad) - .findAll()); - notifyListeners(); - } - - Future deleteAllMessages() async { + Future deleteAllLogs() async { try { await isar.writeTxn(() async => await isar.logs.clear()); notifyListeners(); - } catch (e, s) { - //todo: come back to this - debugPrint("$e, $s"); + return true; + } catch (_) { + return false; } } - Future purgeInfoLogs() async { - final now = DateTime.now(); + Future deleteLogsOlderThan({ + Duration timeframe = const Duration(days: 30), + }) async { + final cutoffDate = DateTime.now().subtract(timeframe).toUtc(); await isar.writeTxn(() async { - await isar.logs.filter().logLevelEqualTo(LogLevel.Info).deleteAll(); + await isar.logs + .where() + .timestampInMillisUTCLessThan(cutoffDate.millisecondsSinceEpoch) + .deleteAll(); }); Logging.instance.log( - "Info logs purged in ${DateTime.now().difference(now).inMilliseconds} milliseconds", - level: LogLevel.Info); + "Logs older than $cutoffDate cleared!", + level: LogLevel.Info, + ); } /// returns the filename of the saved logs file diff --git a/lib/services/ethereum/cached_eth_token_balance.dart b/lib/services/ethereum/cached_eth_token_balance.dart new file mode 100644 index 000000000..d477d0460 --- /dev/null +++ b/lib/services/ethereum/cached_eth_token_balance.dart @@ -0,0 +1,44 @@ +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; +import 'package:stackwallet/services/ethereum/ethereum_api.dart'; +import 'package:stackwallet/services/mixins/eth_token_cache.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/logger.dart'; + +class CachedEthTokenBalance with EthTokenCache { + final String walletId; + final EthContract token; + + CachedEthTokenBalance(this.walletId, this.token) { + initCache(walletId, token); + } + + Future fetchAndUpdateCachedBalance(String address) async { + final response = await EthereumAPI.getWalletTokenBalance( + address: address, + contractAddress: token.address, + ); + + if (response.value != null) { + await updateCachedBalance( + Balance( + total: response.value!, + spendable: response.value!, + blockedTotal: Amount( + rawValue: BigInt.zero, + fractionDigits: token.decimals, + ), + pendingSpendable: Amount( + rawValue: BigInt.zero, + fractionDigits: token.decimals, + ), + ), + ); + } else { + Logging.instance.log( + "CachedEthTokenBalance.fetchAndUpdateCachedBalance failed: ${response.exception}", + level: LogLevel.Warning, + ); + } + } +} diff --git a/lib/services/ethereum/ethereum_api.dart b/lib/services/ethereum/ethereum_api.dart new file mode 100644 index 000000000..fcc5de192 --- /dev/null +++ b/lib/services/ethereum/ethereum_api.dart @@ -0,0 +1,705 @@ +import 'dart:convert'; + +import 'package:decimal/decimal.dart'; +import 'package:http/http.dart'; +import 'package:stackwallet/dto/ethereum/eth_token_tx_dto.dart'; +import 'package:stackwallet/dto/ethereum/eth_token_tx_extra_dto.dart'; +import 'package:stackwallet/dto/ethereum/eth_tx_dto.dart'; +import 'package:stackwallet/dto/ethereum/pending_eth_tx_dto.dart'; +import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; +import 'package:stackwallet/models/paymint/fee_object_model.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/default_nodes.dart'; +import 'package:stackwallet/utilities/eth_commons.dart'; +import 'package:stackwallet/utilities/extensions/extensions.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:tuple/tuple.dart'; + +class EthApiException with Exception { + EthApiException(this.message); + + final String message; + + @override + String toString() => "$runtimeType: $message"; +} + +class EthereumResponse { + EthereumResponse(this.value, this.exception); + + final T? value; + final EthApiException? exception; + + @override + toString() => "EthereumResponse: { value: $value, exception: $exception }"; +} + +abstract class EthereumAPI { + static String get stackBaseServer => DefaultNodes.ethereum.host; + + static Future>> getEthTransactions( + String address) async { + try { + final response = await get( + Uri.parse( + "$stackBaseServer/export?addrs=$address", + ), + ); + + if (response.statusCode == 200) { + if (response.body.isNotEmpty) { + final json = jsonDecode(response.body) as Map; + final list = json["data"] as List?; + + final List txns = []; + for (final map in list!) { + final txn = EthTxDTO.fromMap(Map.from(map as Map)); + + if (txn.hasToken == 0) { + txns.add(txn); + } + } + return EthereumResponse( + txns, + null, + ); + } else { + throw EthApiException( + "getEthTransactions($address) response is empty but status code is " + "${response.statusCode}", + ); + } + } else { + throw EthApiException( + "getEthTransactions($address) failed with status code: " + "${response.statusCode}", + ); + } + } on EthApiException catch (e) { + return EthereumResponse( + null, + e, + ); + } catch (e, s) { + Logging.instance.log( + "getEthTransactions($address): $e\n$s", + level: LogLevel.Error, + ); + return EthereumResponse( + null, + EthApiException(e.toString()), + ); + } + } + + static Future> getEthTransactionByHash( + String txid) async { + try { + final response = await post( + Uri.parse( + "$stackBaseServer/v1/mainnet", + ), + headers: {'Content-Type': 'application/json'}, + body: json.encode({ + "jsonrpc": "2.0", + "method": "eth_getTransactionByHash", + "params": [ + txid, + ], + "id": DateTime.now().millisecondsSinceEpoch, + }), + ); + + if (response.statusCode == 200) { + if (response.body.isNotEmpty) { + try { + final json = jsonDecode(response.body) as Map; + final result = json["result"] as Map; + return EthereumResponse( + PendingEthTxDto.fromMap(Map.from(result)), + null, + ); + } catch (_) { + throw EthApiException( + "getEthTransactionByHash($txid) failed with response: " + "${response.body}", + ); + } + } else { + throw EthApiException( + "getEthTransactionByHash($txid) response is empty but status code is " + "${response.statusCode}", + ); + } + } else { + throw EthApiException( + "getEthTransactionByHash($txid) failed with status code: " + "${response.statusCode}", + ); + } + } on EthApiException catch (e) { + return EthereumResponse( + null, + e, + ); + } catch (e, s) { + Logging.instance.log( + "getEthTransactionByHash($txid): $e\n$s", + level: LogLevel.Error, + ); + return EthereumResponse( + null, + EthApiException(e.toString()), + ); + } + } + + static Future>>> + getEthTransactionNonces( + List txns, + ) async { + try { + final response = await get( + Uri.parse( + "$stackBaseServer/transactions?transactions=${txns.map((e) => e.hash).join(" ")}&raw=true", + ), + ); + + if (response.statusCode == 200) { + if (response.body.isNotEmpty) { + final json = jsonDecode(response.body) as Map; + final list = List>.from(json["data"] as List); + + final List> result = []; + + for (final dto in txns) { + final data = + list.firstWhere((e) => e["hash"] == dto.hash, orElse: () => {}); + + final nonce = (data["nonce"] as String?)?.toBigIntFromHex.toInt(); + result.add(Tuple2(dto, nonce)); + } + return EthereumResponse( + result, + null, + ); + } else { + throw EthApiException( + "getEthTransactionNonces($txns) response is empty but status code is " + "${response.statusCode}", + ); + } + } else { + throw EthApiException( + "getEthTransactionNonces($txns) failed with status code: " + "${response.statusCode}", + ); + } + } on EthApiException catch (e) { + return EthereumResponse( + null, + e, + ); + } catch (e, s) { + Logging.instance.log( + "getEthTransactionNonces($txns): $e\n$s", + level: LogLevel.Error, + ); + return EthereumResponse( + null, + EthApiException(e.toString()), + ); + } + } + + static Future>> + getEthTokenTransactionsByTxids(List txids) async { + try { + final response = await get( + Uri.parse( + "$stackBaseServer/transactions?transactions=${txids.join(" ")}", + ), + ); + + if (response.statusCode == 200) { + if (response.body.isNotEmpty) { + final json = jsonDecode(response.body) as Map; + final list = json["data"] as List?; + + final List txns = []; + for (final map in list!) { + final txn = EthTokenTxExtraDTO.fromMap( + Map.from(map as Map), + ); + + txns.add(txn); + } + return EthereumResponse( + txns, + null, + ); + } else { + throw EthApiException( + "getEthTransaction($txids) response is empty but status code is " + "${response.statusCode}", + ); + } + } else { + throw EthApiException( + "getEthTransaction($txids) failed with status code: " + "${response.statusCode}", + ); + } + } on EthApiException catch (e) { + return EthereumResponse( + null, + e, + ); + } catch (e, s) { + Logging.instance.log( + "getEthTransaction($txids): $e\n$s", + level: LogLevel.Error, + ); + return EthereumResponse( + null, + EthApiException(e.toString()), + ); + } + } + + static Future>> getTokenTransactions({ + required String address, + required String tokenContractAddress, + }) async { + try { + final response = await get( + Uri.parse( + "$stackBaseServer/export?addrs=$address&emitter=$tokenContractAddress&logs=true", + ), + ); + + if (response.statusCode == 200) { + if (response.body.isNotEmpty) { + final json = jsonDecode(response.body) as Map; + final list = json["data"] as List?; + + final List txns = []; + for (final map in list!) { + final txn = + EthTokenTxDto.fromMap(Map.from(map as Map)); + + txns.add(txn); + } + return EthereumResponse( + txns, + null, + ); + } else { + throw EthApiException( + "getTokenTransactions($address, $tokenContractAddress) response is empty but status code is " + "${response.statusCode}", + ); + } + } else { + throw EthApiException( + "getTokenTransactions($address, $tokenContractAddress) failed with status code: " + "${response.statusCode}", + ); + } + } on EthApiException catch (e) { + return EthereumResponse( + null, + e, + ); + } catch (e, s) { + Logging.instance.log( + "getTokenTransactions($address, $tokenContractAddress): $e\n$s", + level: LogLevel.Error, + ); + return EthereumResponse( + null, + EthApiException(e.toString()), + ); + } + } + +// ONLY FETCHES WALLET TOKENS WITH A NON ZERO BALANCE + // static Future>> getWalletTokens({ + // required String address, + // }) async { + // try { + // final uri = Uri.parse( + // "$blockExplorer?module=account&action=tokenlist&address=$address", + // ); + // final response = await get(uri); + // + // if (response.statusCode == 200) { + // final json = jsonDecode(response.body); + // if (json["message"] == "OK") { + // final result = + // List>.from(json["result"] as List); + // final List tokens = []; + // for (final map in result) { + // if (map["type"] == "ERC-20") { + // tokens.add( + // Erc20Token( + // balance: int.parse(map["balance"] as String), + // contractAddress: map["contractAddress"] as String, + // decimals: int.parse(map["decimals"] as String), + // name: map["name"] as String, + // symbol: map["symbol"] as String, + // ), + // ); + // } else if (map["type"] == "ERC-721") { + // tokens.add( + // Erc721Token( + // balance: int.parse(map["balance"] as String), + // contractAddress: map["contractAddress"] as String, + // decimals: int.parse(map["decimals"] as String), + // name: map["name"] as String, + // symbol: map["symbol"] as String, + // ), + // ); + // } else { + // throw EthApiException( + // "Unsupported token type found: ${map["type"]}"); + // } + // } + // + // return EthereumResponse( + // tokens, + // null, + // ); + // } else { + // throw EthApiException(json["message"] as String); + // } + // } else { + // throw EthApiException( + // "getWalletTokens($address) failed with status code: " + // "${response.statusCode}", + // ); + // } + // } on EthApiException catch (e) { + // return EthereumResponse( + // null, + // e, + // ); + // } catch (e, s) { + // Logging.instance.log( + // "getWalletTokens(): $e\n$s", + // level: LogLevel.Error, + // ); + // return EthereumResponse( + // null, + // EthApiException(e.toString()), + // ); + // } + // } + + static Future> getWalletTokenBalance({ + required String address, + required String contractAddress, + }) async { + try { + final uri = Uri.parse( + "$stackBaseServer/tokens?addrs=$contractAddress $address", + ); + final response = await get(uri); + + if (response.statusCode == 200) { + final json = jsonDecode(response.body); + if (json["data"] is List) { + final map = json["data"].first as Map; + + final balance = + Decimal.tryParse(map["balance"].toString()) ?? Decimal.zero; + + return EthereumResponse( + Amount.fromDecimal(balance, fractionDigits: map["decimals"] as int), + null, + ); + } else { + throw EthApiException(json["message"] as String); + } + } else { + throw EthApiException( + "getWalletTokenBalance($address) failed with status code: " + "${response.statusCode}", + ); + } + } on EthApiException catch (e) { + return EthereumResponse( + null, + e, + ); + } catch (e, s) { + Logging.instance.log( + "getWalletTokenBalance(): $e\n$s", + level: LogLevel.Error, + ); + return EthereumResponse( + null, + EthApiException(e.toString()), + ); + } + } + + static Future> getAddressNonce({ + required String address, + }) async { + try { + final uri = Uri.parse( + "$stackBaseServer/state?addrs=$address&parts=all", + ); + final response = await get(uri); + + if (response.statusCode == 200) { + final json = jsonDecode(response.body); + if (json["data"] is List) { + final map = json["data"].first as Map; + + final nonce = map["nonce"] as int; + + return EthereumResponse( + nonce, + null, + ); + } else { + throw EthApiException(json["message"] as String); + } + } else { + throw EthApiException( + "getAddressNonce($address) failed with status code: " + "${response.statusCode}", + ); + } + } on EthApiException catch (e) { + return EthereumResponse( + null, + e, + ); + } catch (e, s) { + Logging.instance.log( + "getAddressNonce(): $e\n$s", + level: LogLevel.Error, + ); + return EthereumResponse( + null, + EthApiException(e.toString()), + ); + } + } + + static Future> getGasOracle() async { + try { + final response = await get( + Uri.parse( + "$stackBaseServer/gas-prices", + ), + ); + + if (response.statusCode == 200) { + final json = jsonDecode(response.body) as Map; + if (json["success"] == true) { + try { + return EthereumResponse( + GasTracker.fromJson( + Map.from(json["result"]["result"] as Map), + ), + null, + ); + } catch (_) { + throw EthApiException( + "getGasOracle() failed with response: " + "${response.body}", + ); + } + } else { + throw EthApiException( + "getGasOracle() failed with response: " + "${response.body}", + ); + } + } else { + throw EthApiException( + "getGasOracle() failed with status code: " + "${response.statusCode}", + ); + } + } on EthApiException catch (e) { + return EthereumResponse( + null, + e, + ); + } catch (e, s) { + Logging.instance.log( + "getGasOracle(): $e\n$s", + level: LogLevel.Error, + ); + return EthereumResponse( + null, + EthApiException(e.toString()), + ); + } + } + + static Future getFees() async { + final fees = (await getGasOracle()).value!; + final feesFast = fees.fast.shift(9).toBigInt(); + final feesStandard = fees.average.shift(9).toBigInt(); + final feesSlow = fees.slow.shift(9).toBigInt(); + + return FeeObject( + numberOfBlocksFast: fees.numberOfBlocksFast, + numberOfBlocksAverage: fees.numberOfBlocksAverage, + numberOfBlocksSlow: fees.numberOfBlocksSlow, + fast: feesFast.toInt(), + medium: feesStandard.toInt(), + slow: feesSlow.toInt()); + } + + static Future> getTokenContractInfoByAddress( + String contractAddress) async { + try { + final response = await get( + Uri.parse( + "$stackBaseServer/tokens?addrs=$contractAddress&parts=all", + ), + ); + + if (response.statusCode == 200) { + final json = jsonDecode(response.body) as Map; + if (json["data"] is List) { + final map = Map.from(json["data"].first as Map); + EthContract? token; + if (map["isErc20"] == true) { + token = EthContract( + address: map["address"] as String, + decimals: map["decimals"] as int, + name: map["name"] as String, + symbol: map["symbol"] as String, + type: EthContractType.erc20, + ); + } else if (map["isErc721"] == true) { + token = EthContract( + address: map["address"] as String, + decimals: map["decimals"] as int, + name: map["name"] as String, + symbol: map["symbol"] as String, + type: EthContractType.erc721, + ); + } else { + throw EthApiException( + "Unsupported token type found: ${map["type"]}"); + } + + return EthereumResponse( + token, + null, + ); + } else { + throw EthApiException(response.body); + } + } else { + throw EthApiException( + "getTokenByContractAddress($contractAddress) failed with status code: " + "${response.statusCode}", + ); + } + } on EthApiException catch (e) { + return EthereumResponse( + null, + e, + ); + } catch (e, s) { + Logging.instance.log( + "getTokenByContractAddress(): $e\n$s", + level: LogLevel.Error, + ); + return EthereumResponse( + null, + EthApiException(e.toString()), + ); + } + } + + static Future> getTokenAbi({ + required String name, + required String contractAddress, + }) async { + try { + final response = await get( + Uri.parse( + "$stackBaseServer/abis?addrs=$contractAddress&verbose=true", + ), + ); + + if (response.statusCode == 200) { + final json = jsonDecode(response.body)["data"] as List; + + return EthereumResponse( + jsonEncode(json), + null, + ); + } else { + throw EthApiException( + "getTokenAbi($name, $contractAddress) failed with status code: " + "${response.statusCode}", + ); + } + } on EthApiException catch (e) { + return EthereumResponse( + null, + e, + ); + } catch (e, s) { + Logging.instance.log( + "getTokenAbi($name, $contractAddress): $e\n$s", + level: LogLevel.Error, + ); + return EthereumResponse( + null, + EthApiException(e.toString()), + ); + } + } + + /// Fetch the underlying contract address that a proxy contract points to + static Future> getProxyTokenImplementationAddress( + String contractAddress, + ) async { + try { + final response = await get(Uri.parse( + "$stackBaseServer/state?addrs=$contractAddress&parts=proxy")); + if (response.statusCode == 200) { + final json = jsonDecode(response.body); + final list = json["data"] as List; + final map = Map.from(list.first as Map); + + return EthereumResponse( + map["proxy"] as String, + null, + ); + } else { + throw EthApiException( + "getProxyTokenImplementationAddress($contractAddress) failed with" + " status code: ${response.statusCode}", + ); + } + } on EthApiException catch (e) { + return EthereumResponse( + null, + e, + ); + } catch (e, s) { + Logging.instance.log( + "getProxyTokenImplementationAddress($contractAddress) : $e\n$s", + level: LogLevel.Error, + ); + return EthereumResponse( + null, + EthApiException(e.toString()), + ); + } + } +} diff --git a/lib/services/ethereum/ethereum_token_service.dart b/lib/services/ethereum/ethereum_token_service.dart new file mode 100644 index 000000000..c7007c905 --- /dev/null +++ b/lib/services/ethereum/ethereum_token_service.dart @@ -0,0 +1,575 @@ +import 'dart:async'; + +import 'package:ethereum_addresses/ethereum_addresses.dart'; +import 'package:flutter/widgets.dart'; +import 'package:http/http.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/dto/ethereum/eth_token_tx_dto.dart'; +import 'package:stackwallet/dto/ethereum/eth_token_tx_extra_dto.dart'; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/models/node_model.dart'; +import 'package:stackwallet/models/paymint/fee_object_model.dart'; +import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart'; +import 'package:stackwallet/services/ethereum/ethereum_api.dart'; +import 'package:stackwallet/services/event_bus/events/global/updated_in_background_event.dart'; +import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart'; +import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +import 'package:stackwallet/services/mixins/eth_token_cache.dart'; +import 'package:stackwallet/services/node_service.dart'; +import 'package:stackwallet/services/transaction_notification_tracker.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/default_nodes.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/fee_rate_type_enum.dart'; +import 'package:stackwallet/utilities/eth_commons.dart'; +import 'package:stackwallet/utilities/extensions/extensions.dart'; +import 'package:stackwallet/utilities/extensions/impl/contract_abi.dart'; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:tuple/tuple.dart'; +import 'package:web3dart/web3dart.dart' as web3dart; + +class EthTokenWallet extends ChangeNotifier with EthTokenCache { + final EthereumWallet ethWallet; + final TransactionNotificationTracker tracker; + final SecureStorageInterface _secureStore; + + // late web3dart.EthereumAddress _contractAddress; + late web3dart.EthPrivateKey _credentials; + late web3dart.DeployedContract _deployedContract; + late web3dart.ContractFunction _sendFunction; + late web3dart.Web3Client _client; + + static const _gasLimit = 200000; + + EthTokenWallet({ + required EthContract token, + required this.ethWallet, + required SecureStorageInterface secureStore, + required this.tracker, + }) : _secureStore = secureStore, + _tokenContract = token { + // _contractAddress = web3dart.EthereumAddress.fromHex(token.address); + initCache(ethWallet.walletId, token); + } + + EthContract get tokenContract => _tokenContract; + EthContract _tokenContract; + + Balance get balance => _balance ??= getCachedBalance(); + Balance? _balance; + + Coin get coin => Coin.ethereum; + + Future> prepareSend({ + required String address, + required Amount amount, + Map? args, + }) async { + final feeRateType = args?["feeRate"]; + int fee = 0; + final feeObject = await fees; + switch (feeRateType) { + case FeeRateType.fast: + fee = feeObject.fast; + break; + case FeeRateType.average: + fee = feeObject.medium; + break; + case FeeRateType.slow: + fee = feeObject.slow; + break; + } + + final feeEstimate = estimateFeeFor(fee); + + final client = await getEthClient(); + + final myAddress = await currentReceivingAddress; + final myWeb3Address = web3dart.EthereumAddress.fromHex(myAddress); + + final nonce = args?["nonce"] as int? ?? + await client.getTransactionCount(myWeb3Address, + atBlock: const web3dart.BlockNum.pending()); + + final tx = web3dart.Transaction.callContract( + contract: _deployedContract, + function: _sendFunction, + parameters: [web3dart.EthereumAddress.fromHex(address), amount.raw], + maxGas: _gasLimit, + gasPrice: web3dart.EtherAmount.fromUnitAndValue( + web3dart.EtherUnit.wei, + fee, + ), + nonce: nonce, + ); + + Map txData = { + "fee": feeEstimate, + "feeInWei": fee, + "address": address, + "recipientAmt": amount, + "ethTx": tx, + "chainId": (await client.getChainId()).toInt(), + "nonce": tx.nonce, + }; + + return txData; + } + + Future confirmSend({required Map txData}) async { + try { + final txid = await _client.sendTransaction( + _credentials, + txData["ethTx"] as web3dart.Transaction, + chainId: txData["chainId"] as int, + ); + + try { + txData["txid"] = txid; + await updateSentCachedTxData(txData); + } catch (e, s) { + // do not rethrow as that would get handled as a send failure further up + // also this is not critical code and transaction should show up on \ + // refresh regardless + Logging.instance.log("$e\n$s", level: LogLevel.Warning); + } + + notifyListeners(); + return txid; + } catch (e) { + // rethrow to pass error in alert + rethrow; + } + } + + Future updateSentCachedTxData(Map txData) async { + final txid = txData["txid"] as String; + final addressString = checksumEthereumAddress(txData["address"] as String); + final response = await EthereumAPI.getEthTransactionByHash(txid); + + final transaction = Transaction( + walletId: ethWallet.walletId, + txid: txid, + timestamp: DateTime.now().millisecondsSinceEpoch ~/ 1000, + type: TransactionType.outgoing, + subType: TransactionSubType.ethToken, + // precision may be lost here hence the following amountString + amount: (txData["recipientAmt"] as Amount).raw.toInt(), + amountString: (txData["recipientAmt"] as Amount).toJsonString(), + fee: (txData["fee"] as Amount).raw.toInt(), + height: null, + isCancelled: false, + isLelantus: false, + otherData: tokenContract.address, + slateId: null, + nonce: (txData["nonce"] as int?) ?? + response.value?.nonce.toBigIntFromHex.toInt(), + inputs: [], + outputs: [], + ); + + Address? address = await ethWallet.db.getAddress( + ethWallet.walletId, + addressString, + ); + + address ??= Address( + walletId: ethWallet.walletId, + value: addressString, + publicKey: [], + derivationIndex: -1, + derivationPath: null, + type: AddressType.ethereum, + subType: AddressSubType.nonWallet, + ); + + await ethWallet.db.addNewTransactionData( + [ + Tuple2(transaction, address), + ], + ethWallet.walletId, + ); + } + + Future get currentReceivingAddress async { + final address = await _currentReceivingAddress; + return checksumEthereumAddress( + address?.value ?? _credentials.address.toString()); + } + + Future get _currentReceivingAddress => ethWallet.db + .getAddresses(ethWallet.walletId) + .filter() + .typeEqualTo(AddressType.ethereum) + .subTypeEqualTo(AddressSubType.receiving) + .sortByDerivationIndexDesc() + .findFirst(); + + Amount estimateFeeFor(int feeRate) { + return estimateFee(feeRate, _gasLimit, coin.decimals); + } + + Future get fees => EthereumAPI.getFees(); + + Future _updateTokenABI({ + required EthContract forContract, + required String usingContractAddress, + }) async { + final abiResponse = await EthereumAPI.getTokenAbi( + name: forContract.name, + contractAddress: usingContractAddress, + ); + // Fetch token ABI so we can call token functions + if (abiResponse.value != null) { + final updatedToken = forContract.copyWith(abi: abiResponse.value!); + // Store updated contract + final id = await MainDB.instance.putEthContract(updatedToken); + return updatedToken..id = id; + } else { + throw abiResponse.exception!; + } + } + + Future initialize() async { + final contractAddress = + web3dart.EthereumAddress.fromHex(tokenContract.address); + + if (tokenContract.abi == null) { + _tokenContract = await _updateTokenABI( + forContract: tokenContract, + usingContractAddress: contractAddress.hex, + ); + } + + String? mnemonicString = await ethWallet.mnemonicString; + + //Get private key for given mnemonic + String privateKey = getPrivateKey( + mnemonicString!, + (await ethWallet.mnemonicPassphrase) ?? "", + ); + _credentials = web3dart.EthPrivateKey.fromHex(privateKey); + + try { + _deployedContract = web3dart.DeployedContract( + ContractAbiExtensions.fromJsonList( + jsonList: tokenContract.abi!, + name: tokenContract.name, + ), + contractAddress, + ); + } catch (_) { + rethrow; + } + + try { + _sendFunction = _deployedContract.function('transfer'); + } catch (_) { + //==================================================================== + // final list = List>.from( + // jsonDecode(tokenContract.abi!) as List); + // final functionNames = list.map((e) => e["name"] as String); + // + // if (!functionNames.contains("balanceOf")) { + // list.add( + // { + // "encoding": "0x70a08231", + // "inputs": [ + // {"name": "account", "type": "address"} + // ], + // "name": "balanceOf", + // "outputs": [ + // {"name": "val_0", "type": "uint256"} + // ], + // "signature": "balanceOf(address)", + // "type": "function" + // }, + // ); + // } + // + // if (!functionNames.contains("transfer")) { + // list.add( + // { + // "encoding": "0xa9059cbb", + // "inputs": [ + // {"name": "dst", "type": "address"}, + // {"name": "rawAmount", "type": "uint256"} + // ], + // "name": "transfer", + // "outputs": [ + // {"name": "val_0", "type": "bool"} + // ], + // "signature": "transfer(address,uint256)", + // "type": "function" + // }, + // ); + // } + //-------------------------------------------------------------------- + //==================================================================== + + // function not found so likely a proxy so we need to fetch the impl + //==================================================================== + // final updatedToken = tokenContract.copyWith(abi: jsonEncode(list)); + // // Store updated contract + // final id = await MainDB.instance.putEthContract(updatedToken); + // _tokenContract = updatedToken..id = id; + //-------------------------------------------------------------------- + final contractAddressResponse = + await EthereumAPI.getProxyTokenImplementationAddress( + contractAddress.hex); + + if (contractAddressResponse.value != null) { + _tokenContract = await _updateTokenABI( + forContract: tokenContract, + usingContractAddress: contractAddressResponse.value!, + ); + } else { + throw contractAddressResponse.exception!; + } + //==================================================================== + } + + try { + _deployedContract = web3dart.DeployedContract( + ContractAbiExtensions.fromJsonList( + jsonList: tokenContract.abi!, + name: tokenContract.name, + ), + contractAddress, + ); + } catch (_) { + rethrow; + } + + _sendFunction = _deployedContract.function('transfer'); + + _client = await getEthClient(); + + unawaited(refresh()); + } + + bool get isRefreshing => _refreshLock; + + bool _refreshLock = false; + + Future refresh() async { + if (!_refreshLock) { + _refreshLock = true; + try { + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.syncing, + ethWallet.walletId + tokenContract.address, + coin, + ), + ); + + await refreshCachedBalance(); + await _refreshTransactions(); + } catch (e, s) { + Logging.instance.log( + "Caught exception in ${tokenContract.name} ${ethWallet.walletName} ${ethWallet.walletId} refresh(): $e\n$s", + level: LogLevel.Warning, + ); + } finally { + _refreshLock = false; + GlobalEventBus.instance.fire( + WalletSyncStatusChangedEvent( + WalletSyncStatus.synced, + ethWallet.walletId + tokenContract.address, + coin, + ), + ); + notifyListeners(); + } + } + } + + Future refreshCachedBalance() async { + final response = await EthereumAPI.getWalletTokenBalance( + address: _credentials.address.hex, + contractAddress: tokenContract.address, + ); + + if (response.value != null) { + await updateCachedBalance( + Balance( + total: response.value!, + spendable: response.value!, + blockedTotal: Amount( + rawValue: BigInt.zero, + fractionDigits: tokenContract.decimals, + ), + pendingSpendable: Amount( + rawValue: BigInt.zero, + fractionDigits: tokenContract.decimals, + ), + ), + ); + notifyListeners(); + } else { + Logging.instance.log( + "CachedEthTokenBalance.fetchAndUpdateCachedBalance failed: ${response.exception}", + level: LogLevel.Warning, + ); + } + } + + Future> get transactions => ethWallet.db + .getTransactions(ethWallet.walletId) + .filter() + .otherDataEqualTo(tokenContract.address) + .sortByTimestampDesc() + .findAll(); + + String _addressFromTopic(String topic) => + checksumEthereumAddress("0x${topic.substring(topic.length - 40)}"); + + Future _refreshTransactions() async { + String addressString = + checksumEthereumAddress(await currentReceivingAddress); + + final response = await EthereumAPI.getTokenTransactions( + address: addressString, + tokenContractAddress: tokenContract.address, + ); + + if (response.value == null) { + throw response.exception ?? + Exception("Failed to fetch token transaction data"); + } + + // no need to continue if no transactions found + if (response.value!.isEmpty) { + return; + } + + final response2 = await EthereumAPI.getEthTokenTransactionsByTxids( + response.value!.map((e) => e.transactionHash).toList(), + ); + + if (response2.value == null) { + throw response2.exception ?? + Exception("Failed to fetch token transactions"); + } + final List> data = []; + for (final tokenDto in response.value!) { + data.add( + Tuple2( + tokenDto, + response2.value!.firstWhere( + (e) => e.hash == tokenDto.transactionHash, + ), + ), + ); + } + + final List> txnsData = []; + + for (final tuple in data) { + // ignore all non Transfer events (for now) + if (tuple.item1.topics[0] == kTransferEventSignature) { + final Amount amount; + String fromAddress, toAddress; + amount = Amount( + rawValue: tuple.item1.data.toBigIntFromHex, + fractionDigits: tokenContract.decimals, + ); + + fromAddress = _addressFromTopic( + tuple.item1.topics[1], + ); + toAddress = _addressFromTopic( + tuple.item1.topics[2], + ); + + bool isIncoming; + bool isSentToSelf = false; + if (fromAddress == addressString) { + isIncoming = false; + if (toAddress == addressString) { + isSentToSelf = true; + } + } else if (toAddress == addressString) { + isIncoming = true; + } else { + throw Exception("Unknown token transaction found for " + "${ethWallet.walletName} ${ethWallet.walletId}: " + "${tuple.item1.toString()}"); + } + + final txn = Transaction( + walletId: ethWallet.walletId, + txid: tuple.item1.transactionHash, + timestamp: tuple.item2.timestamp, + type: + isIncoming ? TransactionType.incoming : TransactionType.outgoing, + subType: TransactionSubType.ethToken, + amount: amount.raw.toInt(), + amountString: amount.toJsonString(), + fee: (tuple.item2.gasUsed.raw * tuple.item2.gasPrice.raw).toInt(), + height: tuple.item1.blockNumber, + isCancelled: false, + isLelantus: false, + slateId: null, + nonce: tuple.item2.nonce, + otherData: tuple.item1.address, + inputs: [], + outputs: [], + ); + + Address? transactionAddress = await ethWallet.db + .getAddresses(ethWallet.walletId) + .filter() + .valueEqualTo(toAddress) + .findFirst(); + + transactionAddress ??= Address( + walletId: ethWallet.walletId, + value: toAddress, + publicKey: [], + derivationIndex: isSentToSelf ? 0 : -1, + derivationPath: isSentToSelf + ? (DerivationPath()..value = "$hdPathEthereum/0") + : null, + type: AddressType.ethereum, + subType: isSentToSelf + ? AddressSubType.receiving + : AddressSubType.nonWallet, + ); + + txnsData.add(Tuple2(txn, transactionAddress)); + } + } + await ethWallet.db.addNewTransactionData(txnsData, ethWallet.walletId); + + // quick hack to notify manager to call notifyListeners if + // transactions changed + if (txnsData.isNotEmpty) { + GlobalEventBus.instance.fire( + UpdatedInBackgroundEvent( + "${tokenContract.name} transactions updated/added for: ${ethWallet.walletId} ${ethWallet.walletName}", + ethWallet.walletId, + ), + ); + } + } + + bool validateAddress(String address) { + return isValidEthereumAddress(address); + } + + NodeModel getCurrentNode() { + return NodeService(secureStorageInterface: _secureStore) + .getPrimaryNodeFor(coin: coin) ?? + DefaultNodes.getNodeFor(coin); + } + + Future getEthClient() async { + final node = getCurrentNode(); + return web3dart.Web3Client(node.host, Client()); + } +} diff --git a/lib/services/event_bus/events/global/balance_refreshed_event.dart b/lib/services/event_bus/events/global/balance_refreshed_event.dart new file mode 100644 index 000000000..cc6e6efc3 --- /dev/null +++ b/lib/services/event_bus/events/global/balance_refreshed_event.dart @@ -0,0 +1,12 @@ +import 'package:stackwallet/utilities/logger.dart'; + +class BalanceRefreshedEvent { + final String walletId; + + BalanceRefreshedEvent(this.walletId) { + Logging.instance.log( + "BalanceRefreshedEvent fired on $walletId", + level: LogLevel.Info, + ); + } +} diff --git a/lib/services/exchange/change_now/change_now_api.dart b/lib/services/exchange/change_now/change_now_api.dart index d957eaf1a..e14b2f128 100644 --- a/lib/services/exchange/change_now/change_now_api.dart +++ b/lib/services/exchange/change_now/change_now_api.dart @@ -3,18 +3,23 @@ import 'dart:convert'; import 'package:decimal/decimal.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; +import 'package:stackwallet/exceptions/exchange/exchange_exception.dart'; +import 'package:stackwallet/exceptions/exchange/pair_unavailable_exception.dart'; +import 'package:stackwallet/exceptions/exchange/unsupported_currency_exception.dart'; import 'package:stackwallet/external_api_keys.dart'; import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart'; import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'; -import 'package:stackwallet/models/exchange/response_objects/currency.dart'; import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; -import 'package:stackwallet/models/exchange/response_objects/pair.dart'; import 'package:stackwallet/models/exchange/response_objects/range.dart'; +import 'package:stackwallet/models/isar/exchange_cache/currency.dart'; +import 'package:stackwallet/models/isar/exchange_cache/pair.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; import 'package:stackwallet/utilities/logger.dart'; +import 'package:tuple/tuple.dart'; class ChangeNowAPI { static const String scheme = "https"; @@ -45,9 +50,16 @@ class ChangeNowAPI { headers: {'Content-Type': 'application/json'}, ); - final parsed = jsonDecode(response.body); + try { + final parsed = jsonDecode(response.body); - return parsed; + return parsed; + } on FormatException catch (e) { + return { + "error": "Dart format exception", + "message": response.body, + }; + } } catch (e, s) { Logging.instance .log("_makeRequest($uri) threw: $e\n$s", level: LogLevel.Error); @@ -88,12 +100,18 @@ class ChangeNowAPI { body: jsonEncode(body), ); - final parsed = jsonDecode(response.body); + try { + final parsed = jsonDecode(response.body); - return parsed; + return parsed; + } catch (_) { + Logging.instance.log("ChangeNOW api failed to parse: ${response.body}", + level: LogLevel.Error); + rethrow; + } } catch (e, s) { Logging.instance - .log("_makeRequest($uri) threw: $e\n$s", level: LogLevel.Error); + .log("_makePostRequest($uri) threw: $e\n$s", level: LogLevel.Error); rethrow; } } @@ -126,7 +144,9 @@ class ChangeNowAPI { try { final result = await compute( - _parseAvailableCurrenciesJson, jsonArray as List); + _parseAvailableCurrenciesJson, + Tuple2(jsonArray as List, fixedRate == true), + ); return result; } catch (e, s) { Logging.instance.log("getAvailableCurrencies exception: $e\n$s", @@ -151,14 +171,106 @@ class ChangeNowAPI { } ExchangeResponse> _parseAvailableCurrenciesJson( - List jsonArray) { + Tuple2, bool> args, + ) { try { List currencies = []; - for (final json in jsonArray) { + for (final json in args.item1) { try { - currencies - .add(Currency.fromJson(Map.from(json as Map))); + final map = Map.from(json as Map); + currencies.add( + Currency.fromJson( + map, + rateType: (map["supportsFixedRate"] as bool) + ? SupportedRateType.both + : SupportedRateType.estimated, + exchangeName: ChangeNowExchange.exchangeName, + ), + ); + } catch (_) { + return ExchangeResponse( + exception: ExchangeException("Failed to serialize $json", + ExchangeExceptionType.serializeResponseError)); + } + } + + return ExchangeResponse(value: currencies); + } catch (_) { + rethrow; + } + } + + Future>> getCurrenciesV2( + // { + // bool? fixedRate, + // bool? active, + // } + ) async { + Map? params; + + // if (active != null || fixedRate != null) { + // params = {}; + // if (fixedRate != null) { + // params.addAll({"fixedRate": fixedRate.toString()}); + // } + // if (active != null) { + // params.addAll({"active": active.toString()}); + // } + // } + + final uri = _buildUriV2("/exchange/currencies", params); + + try { + // json array is expected here + final jsonArray = await _makeGetRequest(uri); + + try { + final result = await compute( + _parseV2CurrenciesJson, + jsonArray as List, + ); + return result; + } catch (e, s) { + Logging.instance.log("getAvailableCurrencies exception: $e\n$s", + level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + "Error: $jsonArray", + ExchangeExceptionType.serializeResponseError, + ), + ); + } + } catch (e, s) { + Logging.instance.log("getAvailableCurrencies exception: $e\n$s", + level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + ExchangeResponse> _parseV2CurrenciesJson( + List args, + ) { + try { + List currencies = []; + + for (final json in args) { + try { + final map = Map.from(json as Map); + currencies.add( + Currency.fromJson( + map, + rateType: (map["supportsFixedRate"] as bool) + ? SupportedRateType.both + : SupportedRateType.estimated, + exchangeName: ChangeNowExchange.exchangeName, + ), + ); } catch (_) { return ExchangeResponse( exception: ExchangeException("Failed to serialize $json", @@ -192,14 +304,35 @@ class ChangeNowAPI { try { // json array is expected here - final jsonArray = (await _makeGetRequest(uri)) as List; + + final response = await _makeGetRequest(uri); + + if (response is Map && response["error"] != null) { + return ExchangeResponse( + exception: UnsupportedCurrencyException( + response["message"] as String? ?? response["error"].toString(), + ExchangeExceptionType.generic, + ticker, + ), + ); + } + + final jsonArray = response as List; List currencies = []; try { for (final json in jsonArray) { try { - currencies - .add(Currency.fromJson(Map.from(json as Map))); + final map = Map.from(json as Map); + currencies.add( + Currency.fromJson( + map, + rateType: (map["supportsFixedRate"] as bool) + ? SupportedRateType.both + : SupportedRateType.estimated, + exchangeName: ChangeNowExchange.exchangeName, + ), + ); } catch (_) { return ExchangeResponse( exception: ExchangeException( @@ -328,8 +461,27 @@ class ChangeNowAPI { final json = await _makeGetRequest(uri); try { - final value = EstimatedExchangeAmount.fromJson( - Map.from(json as Map)); + final map = Map.from(json as Map); + + if (map["error"] != null) { + if (map["error"] == "pair_is_inactive") { + return ExchangeResponse( + exception: PairUnavailableException( + map["message"] as String, + ExchangeExceptionType.generic, + ), + ); + } else { + return ExchangeResponse( + exception: ExchangeException( + map["message"] as String, + ExchangeExceptionType.generic, + ), + ); + } + } + + final value = EstimatedExchangeAmount.fromJson(map); return ExchangeResponse( value: Estimate( estimatedAmount: value.estimatedAmount, @@ -337,6 +489,7 @@ class ChangeNowAPI { reversed: false, rateId: value.rateId, warningMessage: value.warningMessage, + exchangeProvider: ChangeNowExchange.exchangeName, ), ); } catch (_) { @@ -392,8 +545,27 @@ class ChangeNowAPI { final json = await _makeGetRequest(uri); try { - final value = EstimatedExchangeAmount.fromJson( - Map.from(json as Map)); + final map = Map.from(json as Map); + + if (map["error"] != null) { + if (map["error"] == "not_valid_fixed_rate_pair") { + return ExchangeResponse( + exception: PairUnavailableException( + map["message"] as String? ?? "Unsupported fixed rate pair", + ExchangeExceptionType.generic, + ), + ); + } else { + return ExchangeResponse( + exception: ExchangeException( + map["message"] as String? ?? map["error"].toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + final value = EstimatedExchangeAmount.fromJson(map); return ExchangeResponse( value: Estimate( estimatedAmount: value.estimatedAmount, @@ -401,6 +573,7 @@ class ChangeNowAPI { reversed: reversed, rateId: value.rateId, warningMessage: value.warningMessage, + exchangeProvider: ChangeNowExchange.exchangeName, ), ); } catch (_) { @@ -822,12 +995,10 @@ class ChangeNowAPI { final List stringPair = (json as String).split("_"); pairs.add( Pair( + exchangeName: ChangeNowExchange.exchangeName, from: stringPair[0], to: stringPair[1], - fromNetwork: "", - toNetwork: "", - fixedRate: false, - floatingRate: true, + rateType: SupportedRateType.estimated, ), ); } catch (_) { diff --git a/lib/services/exchange/change_now/change_now_exchange.dart b/lib/services/exchange/change_now/change_now_exchange.dart index 4042863d1..a6037b511 100644 --- a/lib/services/exchange/change_now/change_now_exchange.dart +++ b/lib/services/exchange/change_now/change_now_exchange.dart @@ -1,16 +1,21 @@ import 'package:decimal/decimal.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; -import 'package:stackwallet/models/exchange/response_objects/currency.dart'; import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; -import 'package:stackwallet/models/exchange/response_objects/pair.dart'; import 'package:stackwallet/models/exchange/response_objects/range.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; +import 'package:stackwallet/models/isar/exchange_cache/currency.dart'; +import 'package:stackwallet/models/isar/exchange_cache/pair.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; import 'package:stackwallet/services/exchange/exchange.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; import 'package:uuid/uuid.dart'; class ChangeNowExchange extends Exchange { + ChangeNowExchange._(); + + static ChangeNowExchange? _instance; + static ChangeNowExchange get instance => _instance ??= ChangeNowExchange._(); + static const exchangeName = "ChangeNOW"; @override @@ -26,7 +31,7 @@ class ChangeNowExchange extends Exchange { String? extraId, required String addressRefund, required String refundExtraId, - String? rateId, + Estimate? estimate, required bool reversed, }) async { late final ExchangeResponse response; @@ -36,7 +41,7 @@ class ChangeNowExchange extends Exchange { toTicker: to, receivingAddress: addressTo, amount: amount, - rateId: rateId!, + rateId: estimate!.rateId!, extraId: extraId ?? "", refundAddress: addressRefund, refundExtraId: refundExtraId, @@ -77,20 +82,53 @@ class ChangeNowExchange extends Exchange { Future>> getAllCurrencies( bool fixedRate, ) async { - return await ChangeNowAPI.instance.getAvailableCurrencies( - fixedRate: fixedRate ? true : null, - active: true, + return await ChangeNowAPI.instance.getCurrenciesV2(); + // return await ChangeNowAPI.instance.getAvailableCurrencies( + // fixedRate: fixedRate ? true : null, + // active: true, + // ); + } + + @override + Future>> getPairedCurrencies( + String forCurrency, + bool fixedRate, + ) async { + return await ChangeNowAPI.instance.getPairedCurrencies( + ticker: forCurrency, + fixedRate: fixedRate, ); } @override Future>> getAllPairs(bool fixedRate) async { - // TODO: implement getAllPairs - throw UnimplementedError(); + if (fixedRate) { + final markets = + await ChangeNowAPI.instance.getAvailableFixedRateMarkets(); + + if (markets.value == null) { + return ExchangeResponse(exception: markets.exception); + } + + final List pairs = []; + for (final market in markets.value!) { + pairs.add( + Pair( + exchangeName: ChangeNowExchange.exchangeName, + from: market.from, + to: market.to, + rateType: SupportedRateType.fixed, + ), + ); + } + return ExchangeResponse(value: pairs); + } else { + return await ChangeNowAPI.instance.getAvailableFloatingRatePairs(); + } } @override - Future> getEstimate( + Future>> getEstimates( String from, String to, Decimal amount, @@ -113,7 +151,10 @@ class ChangeNowExchange extends Exchange { fromAmount: amount, ); } - return response; + return ExchangeResponse( + value: response.value == null ? null : [response.value!], + exception: response.exception, + ); } @override diff --git a/lib/services/exchange/exchange.dart b/lib/services/exchange/exchange.dart index 6437f9065..1db451457 100644 --- a/lib/services/exchange/exchange.dart +++ b/lib/services/exchange/exchange.dart @@ -1,21 +1,34 @@ import 'package:decimal/decimal.dart'; -import 'package:stackwallet/models/exchange/response_objects/currency.dart'; import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; -import 'package:stackwallet/models/exchange/response_objects/pair.dart'; import 'package:stackwallet/models/exchange/response_objects/range.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; +import 'package:stackwallet/models/isar/exchange_cache/currency.dart'; +import 'package:stackwallet/models/isar/exchange_cache/pair.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; +import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart'; import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; +import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart'; abstract class Exchange { + static Exchange get defaultExchange => ChangeNowExchange.instance; + static Exchange fromName(String name) { switch (name) { case ChangeNowExchange.exchangeName: - return ChangeNowExchange(); + return ChangeNowExchange.instance; case SimpleSwapExchange.exchangeName: - return SimpleSwapExchange(); + return SimpleSwapExchange.instance; + case MajesticBankExchange.exchangeName: + return MajesticBankExchange.instance; + case TrocadorExchange.exchangeName: + return TrocadorExchange.instance; default: + final split = name.split(" "); + if (split.length >= 2) { + // silly way to check for 'Trocador ($providerName)' + return fromName(split.first); + } throw ArgumentError("Unknown exchange name"); } } @@ -24,6 +37,11 @@ abstract class Exchange { Future>> getAllCurrencies(bool fixedRate); + Future>> getPairedCurrencies( + String forCurrency, + bool fixedRate, + ); + Future>> getPairsFor( String currency, bool fixedRate, @@ -42,7 +60,7 @@ abstract class Exchange { bool fixedRate, ); - Future> getEstimate( + Future>> getEstimates( String from, String to, Decimal amount, @@ -59,7 +77,7 @@ abstract class Exchange { String? extraId, required String addressRefund, required String refundExtraId, - String? rateId, + Estimate? estimate, required bool reversed, }); } diff --git a/lib/services/exchange/exchange_data_loading_service.dart b/lib/services/exchange/exchange_data_loading_service.dart index d06f8b726..c0aa2e2b0 100644 --- a/lib/services/exchange/exchange_data_loading_service.dart +++ b/lib/services/exchange/exchange_data_loading_service.dart @@ -1,202 +1,359 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; -import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:flutter/foundation.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/hive/db.dart'; +import 'package:stackwallet/models/exchange/active_pair.dart'; +import 'package:stackwallet/models/exchange/aggregate_currency.dart'; +import 'package:stackwallet/models/isar/exchange_cache/currency.dart'; +import 'package:stackwallet/models/isar/exchange_cache/pair.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; +import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart'; +import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart'; +import 'package:stackwallet/utilities/enums/exchange_rate_type_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/stack_file_system.dart'; +import 'package:tuple/tuple.dart'; class ExchangeDataLoadingService { - Future loadAll(WidgetRef ref, {Coin? coin}) async { - try { - await Future.wait([ - _loadFixedRateMarkets(ref, coin: coin), - _loadChangeNowStandardCurrencies(ref, coin: coin), - loadSimpleswapFixedRateCurrencies(ref), - loadSimpleswapFloatingRateCurrencies(ref), - ]); - } catch (e, s) { - Logging.instance.log("ExchangeDataLoadingService.loadAll failed: $e\n$s", - level: LogLevel.Error); - } + ExchangeDataLoadingService._(); + static final ExchangeDataLoadingService _instance = + ExchangeDataLoadingService._(); + static ExchangeDataLoadingService get instance => _instance; + + Isar? _isar; + Isar get isar => _isar!; + + VoidCallback? onLoadingError; + VoidCallback? onLoadingComplete; + + static const int cacheVersion = 1; + + static int get currentCacheVersion => + DB.instance.get( + boxName: DB.boxNameDBInfo, + key: "exchange_data_cache_version") as int? ?? + 0; + + Future _updateCurrentCacheVersion(int version) async { + await DB.instance.put( + boxName: DB.boxNameDBInfo, + key: "exchange_data_cache_version", + value: version, + ); } - Future _loadFixedRateMarkets(WidgetRef ref, {Coin? coin}) async { - if (ref.read(changeNowFixedInitialLoadStatusStateProvider.state).state == - ChangeNowLoadStatus.loading) { - // already in progress so just - return; - } + Future initDB() async { + if (_isar != null) return; + await _isar?.close(); + _isar = await Isar.open( + [ + CurrencySchema, + // PairSchema, + ], + directory: (await StackFileSystem.applicationIsarDirectory()).path, + // inspector: kDebugMode, + inspector: false, + name: "exchange_cache", + maxSizeMiB: 64, + ); + } - ref.read(changeNowFixedInitialLoadStatusStateProvider.state).state = - ChangeNowLoadStatus.loading; + Future setCurrenciesIfEmpty( + ActivePair? pair, + ExchangeRateType rateType, + ) async { + if (pair?.send == null && pair?.receive == null) { + if (await isar.currencies.count() > 0) { + pair?.setSend( + await getAggregateCurrency( + "BTC", + rateType, + null, + ), + notifyListeners: false, + ); - final response3 = - await ChangeNowAPI.instance.getAvailableFixedRateMarkets(); - if (response3.value != null) { - ref - .read(availableChangeNowCurrenciesProvider) - .updateMarkets(response3.value!); - - if (ref.read(exchangeFormStateProvider).market == null) { - String fromTicker = "btc"; - String toTicker = "xmr"; - - if (coin != null) { - fromTicker = coin.ticker.toLowerCase(); - } - - final matchingMarkets = response3.value! - .where((e) => e.to == toTicker && e.from == fromTicker); - if (matchingMarkets.isNotEmpty) { - await ref - .read(exchangeFormStateProvider) - .updateMarket(matchingMarkets.first, true); - } + pair?.setReceive( + await getAggregateCurrency( + "XMR", + rateType, + null, + ), + notifyListeners: false, + ); } - } else { - Logging.instance.log( - "Failed to load changeNOW fixed rate markets: ${response3.exception?.errorMessage}", - level: LogLevel.Error); - - ref.read(changeNowFixedInitialLoadStatusStateProvider.state).state = - ChangeNowLoadStatus.failed; - return; } - - ref.read(changeNowFixedInitialLoadStatusStateProvider.state).state = - ChangeNowLoadStatus.success; } - Future _loadChangeNowStandardCurrencies( - WidgetRef ref, { - Coin? coin, - }) async { - if (ref - .read(changeNowEstimatedInitialLoadStatusStateProvider.state) - .state == - ChangeNowLoadStatus.loading) { - // already in progress so just - return; - } + Future getAggregateCurrency( + String ticker, + ExchangeRateType rateType, + String? contract, + ) async { + final currencies = await ExchangeDataLoadingService.instance.isar.currencies + .filter() + .group((q) => rateType == ExchangeRateType.fixed + ? q + .rateTypeEqualTo(SupportedRateType.both) + .or() + .rateTypeEqualTo(SupportedRateType.fixed) + : q + .rateTypeEqualTo(SupportedRateType.both) + .or() + .rateTypeEqualTo(SupportedRateType.estimated)) + .and() + .tickerEqualTo( + ticker, + caseSensitive: false, + ) + .and() + .tokenContractEqualTo(contract) + .findAll(); - ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state = - ChangeNowLoadStatus.loading; + final items = currencies + .map((e) => Tuple2(e.exchangeName, e)) + .toList(growable: false); - final response = await ChangeNowAPI.instance.getAvailableCurrencies(); - final response2 = - await ChangeNowAPI.instance.getAvailableFloatingRatePairs(); - if (response.value != null) { - ref - .read(availableChangeNowCurrenciesProvider) - .updateCurrencies(response.value!); + return items.isNotEmpty + ? AggregateCurrency(exchangeCurrencyPairs: items) + : null; + } - if (response2.value != null) { - ref - .read(availableChangeNowCurrenciesProvider) - .updateFloatingPairs(response2.value!); + bool get isLoading => _locked; - String fromTicker = "btc"; - String toTicker = "xmr"; + bool _locked = false; - if (coin != null) { - fromTicker = coin.ticker.toLowerCase(); - } + Future loadAll() async { + if (!_locked) { + _locked = true; + Logging.instance.log( + "ExchangeDataLoadingService.loadAll starting...", + level: LogLevel.Info, + ); + final start = DateTime.now(); + try { + await Future.wait([ + _loadChangeNowCurrencies(), + // _loadChangeNowFixedRatePairs(), + // _loadChangeNowEstimatedRatePairs(), + // loadSimpleswapFixedRateCurrencies(ref), + // loadSimpleswapFloatingRateCurrencies(ref), + loadMajesticBankCurrencies(), + loadTrocadorCurrencies(), + ]); + + // quicker to load available currencies on the fly for a specific base currency + // await _loadChangeNowFixedRatePairs(); + // await _loadChangeNowEstimatedRatePairs(); - if (response.value!.length > 1) { - if (ref.read(exchangeFormStateProvider).from == null) { - if (response.value! - .where((e) => e.ticker == fromTicker) - .isNotEmpty) { - await ref.read(exchangeFormStateProvider).updateFrom( - response.value!.firstWhere((e) => e.ticker == fromTicker), - false); - } - } - if (ref.read(exchangeFormStateProvider).to == null) { - if (response.value!.where((e) => e.ticker == toTicker).isNotEmpty) { - await ref.read(exchangeFormStateProvider).updateTo( - response.value!.firstWhere((e) => e.ticker == toTicker), - false); - } - } - } - } else { Logging.instance.log( - "Failed to load changeNOW available floating rate pairs: ${response2.exception?.errorMessage}", - level: LogLevel.Error); - ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state = - ChangeNowLoadStatus.failed; - return; + "ExchangeDataLoadingService.loadAll finished in ${DateTime.now().difference(start).inSeconds} seconds", + level: LogLevel.Info, + ); + onLoadingComplete?.call(); + await _updateCurrentCacheVersion(cacheVersion); + } catch (e, s) { + Logging.instance.log( + "ExchangeDataLoadingService.loadAll failed after ${DateTime.now().difference(start).inSeconds} seconds: $e\n$s", + level: LogLevel.Error, + ); + onLoadingError?.call(); } - } else { - Logging.instance.log( - "Failed to load changeNOW currencies: ${response.exception?.errorMessage}", - level: LogLevel.Error); - await Future.delayed(const Duration(seconds: 3)); - ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state = - ChangeNowLoadStatus.failed; - return; + _locked = false; } - - ref.read(changeNowEstimatedInitialLoadStatusStateProvider.state).state = - ChangeNowLoadStatus.success; } - Future loadSimpleswapFloatingRateCurrencies(WidgetRef ref) async { - final exchange = SimpleSwapExchange(); + Future _loadChangeNowCurrencies() async { + final exchange = ChangeNowExchange.instance; + final responseCurrencies = await exchange.getAllCurrencies(false); + if (responseCurrencies.value != null) { + await isar.writeTxn(() async { + final idsToDelete = await isar.currencies + .where() + .exchangeNameEqualTo(ChangeNowExchange.exchangeName) + .idProperty() + .findAll(); + await isar.currencies.deleteAll(idsToDelete); + await isar.currencies.putAll(responseCurrencies.value!); + }); + } else { + Logging.instance.log( + "Failed to load changeNOW currencies: ${responseCurrencies.exception?.message}", + level: LogLevel.Error); + return; + } + } + + // Future _loadChangeNowFixedRatePairs() async { + // final exchange = ChangeNowExchange.instance; + // + // final responsePairs = await compute(exchange.getAllPairs, true); + // + // if (responsePairs.value != null) { + // await isar.writeTxn(() async { + // final idsToDelete2 = await isar.pairs + // .where() + // .exchangeNameEqualTo(ChangeNowExchange.exchangeName) + // .filter() + // .rateTypeEqualTo(SupportedRateType.fixed) + // .idProperty() + // .findAll(); + // await isar.pairs.deleteAll(idsToDelete2); + // await isar.pairs.putAll(responsePairs.value!); + // }); + // } else { + // Logging.instance.log( + // "Failed to load changeNOW available fixed rate pairs: ${responsePairs.exception?.message}", + // level: LogLevel.Error); + // return; + // } + // } + + // Future _loadChangeNowEstimatedRatePairs() async { + // final exchange = ChangeNowExchange.instance; + // + // final responsePairs = await compute(exchange.getAllPairs, false); + // + // if (responsePairs.value != null) { + // await isar.writeTxn(() async { + // final idsToDelete = await isar.pairs + // .where() + // .exchangeNameEqualTo(ChangeNowExchange.exchangeName) + // .filter() + // .rateTypeEqualTo(SupportedRateType.estimated) + // .idProperty() + // .findAll(); + // await isar.pairs.deleteAll(idsToDelete); + // await isar.pairs.putAll(responsePairs.value!); + // }); + // } else { + // Logging.instance.log( + // "Failed to load changeNOW available floating rate pairs: ${responsePairs.exception?.message}", + // level: LogLevel.Error); + // return; + // } + // } + // + // Future loadSimpleswapFloatingRateCurrencies(WidgetRef ref) async { + // final exchange = SimpleSwapExchange(); + // final responseCurrencies = await exchange.getAllCurrencies(false); + // + // if (responseCurrencies.value != null) { + // ref + // .read(availableSimpleswapCurrenciesProvider) + // .updateFloatingCurrencies(responseCurrencies.value!); + // + // final responsePairs = await exchange.getAllPairs(false); + // + // if (responsePairs.value != null) { + // ref + // .read(availableSimpleswapCurrenciesProvider) + // .updateFloatingPairs(responsePairs.value!); + // } else { + // Logging.instance.log( + // "loadSimpleswapFloatingRateCurrencies: $responsePairs", + // level: LogLevel.Warning, + // ); + // } + // } else { + // Logging.instance.log( + // "loadSimpleswapFloatingRateCurrencies: $responseCurrencies", + // level: LogLevel.Warning, + // ); + // } + // } + // + // Future loadSimpleswapFixedRateCurrencies(WidgetRef ref) async { + // final exchange = SimpleSwapExchange(); + // final responseCurrencies = await exchange.getAllCurrencies(true); + // + // if (responseCurrencies.value != null) { + // ref + // .read(availableSimpleswapCurrenciesProvider) + // .updateFixedCurrencies(responseCurrencies.value!); + // + // final responsePairs = await exchange.getAllPairs(true); + // + // if (responsePairs.value != null) { + // ref + // .read(availableSimpleswapCurrenciesProvider) + // .updateFixedPairs(responsePairs.value!); + // } else { + // Logging.instance.log( + // "loadSimpleswapFixedRateCurrencies: $responsePairs", + // level: LogLevel.Warning, + // ); + // } + // } else { + // Logging.instance.log( + // "loadSimpleswapFixedRateCurrencies: $responseCurrencies", + // level: LogLevel.Warning, + // ); + // } + // } + + Future loadMajesticBankCurrencies() async { + final exchange = MajesticBankExchange.instance; final responseCurrencies = await exchange.getAllCurrencies(false); if (responseCurrencies.value != null) { - ref - .read(availableSimpleswapCurrenciesProvider) - .updateFloatingCurrencies(responseCurrencies.value!); - - final responsePairs = await exchange.getAllPairs(false); - - if (responsePairs.value != null) { - ref - .read(availableSimpleswapCurrenciesProvider) - .updateFloatingPairs(responsePairs.value!); - } else { - Logging.instance.log( - "loadSimpleswapFloatingRateCurrencies: $responsePairs", - level: LogLevel.Warning, - ); - } + await isar.writeTxn(() async { + final idsToDelete = await isar.currencies + .where() + .exchangeNameEqualTo(MajesticBankExchange.exchangeName) + .idProperty() + .findAll(); + await isar.currencies.deleteAll(idsToDelete); + await isar.currencies.putAll(responseCurrencies.value!); + }); } else { Logging.instance.log( - "loadSimpleswapFloatingRateCurrencies: $responseCurrencies", + "loadMajesticBankCurrencies: $responseCurrencies", level: LogLevel.Warning, ); } } - Future loadSimpleswapFixedRateCurrencies(WidgetRef ref) async { - final exchange = SimpleSwapExchange(); - final responseCurrencies = await exchange.getAllCurrencies(true); + Future loadTrocadorCurrencies() async { + final exchange = TrocadorExchange.instance; + final responseCurrencies = await exchange.getAllCurrencies(false); if (responseCurrencies.value != null) { - ref - .read(availableSimpleswapCurrenciesProvider) - .updateFixedCurrencies(responseCurrencies.value!); - - final responsePairs = await exchange.getAllPairs(true); - - if (responsePairs.value != null) { - ref - .read(availableSimpleswapCurrenciesProvider) - .updateFixedPairs(responsePairs.value!); - } else { - Logging.instance.log( - "loadSimpleswapFixedRateCurrencies: $responsePairs", - level: LogLevel.Warning, - ); - } + await isar.writeTxn(() async { + final idsToDelete = await isar.currencies + .where() + .exchangeNameEqualTo(TrocadorExchange.exchangeName) + .idProperty() + .findAll(); + await isar.currencies.deleteAll(idsToDelete); + await isar.currencies.putAll(responseCurrencies.value!); + }); } else { Logging.instance.log( - "loadSimpleswapFixedRateCurrencies: $responseCurrencies", + "loadTrocadorCurrencies: $responseCurrencies", level: LogLevel.Warning, ); } } + + // Future loadMajesticBankPairs() async { + // final exchange = MajesticBankExchange.instance; + // + // final responsePairs = await exchange.getAllPairs(false); + // if (responsePairs.value != null) { + // await isar.writeTxn(() async { + // final idsToDelete2 = await isar.pairs + // .where() + // .exchangeNameEqualTo(MajesticBankExchange.exchangeName) + // .idProperty() + // .findAll(); + // await isar.pairs.deleteAll(idsToDelete2); + // await isar.pairs.putAll(responsePairs.value!); + // }); + // } else { + // Logging.instance.log( + // "loadMajesticBankCurrencies: $responsePairs", + // level: LogLevel.Warning, + // ); + // } + // } } diff --git a/lib/services/exchange/exchange_response.dart b/lib/services/exchange/exchange_response.dart index 59441fb9b..79339e5f7 100644 --- a/lib/services/exchange/exchange_response.dart +++ b/lib/services/exchange/exchange_response.dart @@ -1,15 +1,4 @@ -enum ExchangeExceptionType { generic, serializeResponseError } - -class ExchangeException implements Exception { - String errorMessage; - ExchangeExceptionType type; - ExchangeException(this.errorMessage, this.type); - - @override - String toString() { - return errorMessage; - } -} +import 'package:stackwallet/exceptions/exchange/exchange_exception.dart'; class ExchangeResponse { late final T? value; diff --git a/lib/services/exchange/majestic_bank/majestic_bank_api.dart b/lib/services/exchange/majestic_bank/majestic_bank_api.dart new file mode 100644 index 000000000..61eb886a4 --- /dev/null +++ b/lib/services/exchange/majestic_bank/majestic_bank_api.dart @@ -0,0 +1,374 @@ +import 'dart:convert'; + +import 'package:decimal/decimal.dart'; +import 'package:http/http.dart' as http; +import 'package:stackwallet/exceptions/exchange/exchange_exception.dart'; +import 'package:stackwallet/exceptions/exchange/majestic_bank/mb_exception.dart'; +import 'package:stackwallet/exceptions/exchange/pair_unavailable_exception.dart'; +import 'package:stackwallet/models/exchange/majestic_bank/mb_limit.dart'; +import 'package:stackwallet/models/exchange/majestic_bank/mb_order.dart'; +import 'package:stackwallet/models/exchange/majestic_bank/mb_order_calculation.dart'; +import 'package:stackwallet/models/exchange/majestic_bank/mb_order_status.dart'; +import 'package:stackwallet/models/exchange/majestic_bank/mb_rate.dart'; +import 'package:stackwallet/services/exchange/exchange_response.dart'; +import 'package:stackwallet/utilities/logger.dart'; + +class MajesticBankAPI { + static const String scheme = "https"; + static const String authority = "majesticbank.sc"; + static const String version = "v1"; + static const kMajesticBankRefCode = "rjWugM"; + + MajesticBankAPI._(); + + static final MajesticBankAPI _instance = MajesticBankAPI._(); + + static MajesticBankAPI get instance => _instance; + + /// set this to override using standard http client. Useful for testing + http.Client? client; + + Uri _buildUri({required String endpoint, Map? params}) { + return Uri.https(authority, "/api/$version/$endpoint", params); + } + + Future _makeGetRequest(Uri uri) async { + final client = this.client ?? http.Client(); + int code = -1; + try { + final response = await client.get( + uri, + ); + + code = response.statusCode; + + final parsed = jsonDecode(response.body); + + return parsed; + } catch (e, s) { + Logging.instance.log( + "_makeRequest($uri) HTTP:$code threw: $e\n$s", + level: LogLevel.Error, + ); + rethrow; + } + } + + Future>> getRates() async { + final uri = _buildUri( + endpoint: "rates", + ); + + try { + final jsonObject = await _makeGetRequest(uri); + + final map = Map.from(jsonObject as Map); + final List rates = []; + for (final key in map.keys) { + final currencies = key.split("-"); + if (currencies.length == 2) { + final rate = MBRate( + fromCurrency: currencies.first, + toCurrency: currencies.last, + rate: Decimal.parse(map[key].toString()), + ); + rates.add(rate); + } + } + return ExchangeResponse(value: rates); + } catch (e, s) { + Logging.instance.log("getRates exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + Future> getLimit({ + required String fromCurrency, + }) async { + final uri = _buildUri( + endpoint: "limits", + params: { + "from_currency": fromCurrency, + }, + ); + + try { + final jsonObject = await _makeGetRequest(uri); + + final map = Map.from(jsonObject as Map); + + final limit = MBLimit( + currency: fromCurrency, + min: Decimal.parse(map["min"].toString()), + max: Decimal.parse(map["max"].toString()), + ); + + return ExchangeResponse(value: limit); + } catch (e, s) { + Logging.instance + .log("getLimits exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + Future>> getLimits() async { + final uri = _buildUri( + endpoint: + "rates", // limits are included in the rates call for some reason??? + ); + + try { + final jsonObject = await _makeGetRequest(uri); + + final map = Map.from(jsonObject as Map)["limits"] as Map; + final List limits = []; + for (final key in map.keys) { + final limit = MBLimit( + currency: key as String, + min: Decimal.parse(map[key]["min"].toString()), + max: Decimal.parse(map[key]["max"].toString()), + ); + limits.add(limit); + } + + return ExchangeResponse(value: limits); + } catch (e, s) { + Logging.instance + .log("getLimits exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + /// If [reversed] then the amount is the expected receive_amount, otherwise + /// the amount is assumed to be the from_amount. + Future> calculateOrder({ + required String amount, + required bool reversed, + required String fromCurrency, + required String receiveCurrency, + }) async { + final params = { + "from_currency": fromCurrency, + "receive_currency": receiveCurrency, + }; + + if (reversed) { + params["receive_amount"] = amount; + } else { + params["from_amount"] = amount; + } + + final uri = _buildUri( + endpoint: "calculate", + params: params, + ); + + try { + final jsonObject = await _makeGetRequest(uri); + final map = Map.from(jsonObject as Map); + + if (map["error"] != null) { + final errorMessage = map["extra"] as String?; + if (errorMessage != null && + errorMessage.startsWith("Bad") && + errorMessage.endsWith("currency symbol")) { + return ExchangeResponse( + exception: PairUnavailableException( + errorMessage, + ExchangeExceptionType.generic, + ), + ); + } else { + return ExchangeResponse( + exception: ExchangeException( + errorMessage ?? "Error: ${map["error"]}", + ExchangeExceptionType.generic, + ), + ); + } + } + + final result = MBOrderCalculation( + fromCurrency: map["from_currency"] as String, + fromAmount: Decimal.parse(map["from_amount"].toString()), + receiveCurrency: map["receive_currency"] as String, + receiveAmount: Decimal.parse(map["receive_amount"].toString()), + ); + + return ExchangeResponse(value: result); + } catch (e, s) { + Logging.instance.log( + "calculateOrder $fromCurrency-$receiveCurrency exception: $e\n$s", + level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + Future> createOrder({ + required String fromAmount, + required String fromCurrency, + required String receiveCurrency, + required String receiveAddress, + }) async { + final params = { + "from_amount": fromAmount, + "from_currency": fromCurrency, + "receive_currency": receiveCurrency, + "receive_address": receiveAddress, + "referral_code": kMajesticBankRefCode, + }; + + final uri = _buildUri(endpoint: "exchange", params: params); + + try { + final now = DateTime.now(); + final jsonObject = await _makeGetRequest(uri); + final json = Map.from(jsonObject as Map); + + final order = MBOrder( + orderId: json["trx"] as String, + fromCurrency: json["from_currency"] as String, + fromAmount: Decimal.parse(json["from_amount"].toString()), + receiveCurrency: json["receive_currency"] as String, + receiveAmount: Decimal.parse(json["receive_amount"].toString()), + address: json["address"] as String, + orderType: MBOrderType.floating, + expiration: json["expiration"] as int, + createdAt: now, + ); + + return ExchangeResponse(value: order); + } catch (e, s) { + Logging.instance + .log("createOrder exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + /// Fixed rate for 10 minutes, useful for payments. + /// If [reversed] then the amount is the expected receive_amount, otherwise + /// the amount is assumed to be the from_amount. + Future> createFixedRateOrder({ + required String amount, + required String fromCurrency, + required String receiveCurrency, + required String receiveAddress, + required bool reversed, + }) async { + final params = { + "from_currency": fromCurrency, + "receive_currency": receiveCurrency, + "receive_address": receiveAddress, + "referral_code": kMajesticBankRefCode, + }; + + if (reversed) { + params["receive_amount"] = amount; + } else { + params["from_amount"] = amount; + } + + final uri = _buildUri(endpoint: "pay", params: params); + + try { + final now = DateTime.now(); + final jsonObject = await _makeGetRequest(uri); + final json = Map.from(jsonObject as Map); + + final order = MBOrder( + orderId: json["trx"] as String, + fromCurrency: json["from_currency"] as String, + fromAmount: Decimal.parse(json["from_amount"].toString()), + receiveCurrency: json["receive_currency"] as String, + receiveAmount: Decimal.parse(json["receive_amount"].toString()), + address: json["address"] as String, + orderType: MBOrderType.fixed, + expiration: json["expiration"] as int, + createdAt: now, + ); + + return ExchangeResponse(value: order); + } catch (e, s) { + Logging.instance + .log("createFixedRateOrder exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + Future> trackOrder({ + required String orderId, + }) async { + final uri = _buildUri(endpoint: "track", params: { + "trx": orderId, + }); + + try { + final jsonObject = await _makeGetRequest(uri); + final json = Map.from(jsonObject as Map); + + if (json.length == 2) { + return ExchangeResponse( + exception: MBException( + json["status"] as String, + ExchangeExceptionType.orderNotFound, + ), + ); + } + + final status = MBOrderStatus( + orderId: json["trx"] as String, + status: json["status"] as String, + fromCurrency: json["from_currency"] as String, + fromAmount: Decimal.parse(json["from_amount"].toString()), + receiveCurrency: json["receive_currency"] as String, + receiveAmount: Decimal.parse(json["receive_amount"].toString()), + address: json["address"] as String, + received: Decimal.parse(json["received"].toString()), + confirmed: Decimal.parse(json["confirmed"].toString()), + ); + + return ExchangeResponse(value: status); + } catch (e, s) { + Logging.instance.log( + "trackOrder exception when trying to parse $json: $e\n$s", + level: LogLevel.Error, + ); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } +} diff --git a/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart b/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart new file mode 100644 index 000000000..95031490c --- /dev/null +++ b/lib/services/exchange/majestic_bank/majestic_bank_exchange.dart @@ -0,0 +1,317 @@ +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/exceptions/exchange/exchange_exception.dart'; +import 'package:stackwallet/exceptions/exchange/majestic_bank/mb_exception.dart'; +import 'package:stackwallet/models/exchange/majestic_bank/mb_order.dart'; +import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; +import 'package:stackwallet/models/exchange/response_objects/range.dart'; +import 'package:stackwallet/models/exchange/response_objects/trade.dart'; +import 'package:stackwallet/models/isar/exchange_cache/currency.dart'; +import 'package:stackwallet/models/isar/exchange_cache/pair.dart'; +import 'package:stackwallet/services/exchange/exchange.dart'; +import 'package:stackwallet/services/exchange/exchange_response.dart'; +import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_api.dart'; +import 'package:uuid/uuid.dart'; + +class MajesticBankExchange extends Exchange { + MajesticBankExchange._(); + + static MajesticBankExchange? _instance; + static MajesticBankExchange get instance => + _instance ??= MajesticBankExchange._(); + + static const exchangeName = "Majestic Bank"; + + static const kMajesticBankCurrencyNames = { + "BCH": "Bitcoin Cash", + "BTC": "Bitcoin", + "DOGE": "Dogecoin", + "EPIC": "Epic Cash", + "FIRO": "Firo", + "LTC": "Litecoin", + "NMC": "Namecoin", + "PART": "Particl", + "WOW": "Wownero", + "XMR": "Monero", + }; + + @override + Future> createTrade({ + required String from, + required String to, + required bool fixedRate, + required Decimal amount, + required String addressTo, + String? extraId, + required String addressRefund, + required String refundExtraId, + Estimate? estimate, + required bool reversed, + }) async { + ExchangeResponse? response; + + if (fixedRate) { + response = await MajesticBankAPI.instance.createFixedRateOrder( + amount: amount.toString(), + fromCurrency: from, + receiveCurrency: to, + receiveAddress: addressTo, + reversed: reversed, + ); + } else { + if (reversed) { + return ExchangeResponse( + exception: MBException( + "Reversed trade not available", + ExchangeExceptionType.generic, + ), + ); + } + response = await MajesticBankAPI.instance.createOrder( + fromAmount: amount.toString(), + fromCurrency: from, + receiveCurrency: to, + receiveAddress: addressTo, + ); + } + + if (response.value != null) { + final order = response.value!; + final trade = Trade( + uuid: const Uuid().v1(), + tradeId: order.orderId, + rateType: fixedRate ? "fixed" : "floating", + direction: reversed ? "reversed" : "direct", + timestamp: order.createdAt, + updatedAt: order.createdAt, + payInCurrency: order.fromCurrency, + payInAmount: order.fromAmount.toString(), + payInAddress: order.address, + payInNetwork: "", + payInExtraId: "", + payInTxid: "", + payOutCurrency: order.receiveCurrency, + payOutAmount: order.receiveAmount.toString(), + payOutAddress: addressTo, + payOutNetwork: "", + payOutExtraId: "", + payOutTxid: "", + refundAddress: addressRefund, + refundExtraId: refundExtraId, + status: "Waiting", + exchangeName: exchangeName, + ); + + return ExchangeResponse(value: trade); + } else { + return ExchangeResponse(exception: response.exception!); + } + } + + @override + Future>> getAllCurrencies( + bool fixedRate, + ) async { + final response = await MajesticBankAPI.instance.getLimits(); + if (response.value == null) { + return ExchangeResponse(exception: response.exception); + } + + final List currencies = []; + final limits = response.value!; + + for (final limit in limits) { + final currency = Currency( + exchangeName: MajesticBankExchange.exchangeName, + ticker: limit.currency, + name: kMajesticBankCurrencyNames[limit.currency] ?? + limit.currency, // todo: add more names if MB adds more + network: "", + image: "", + isFiat: false, + rateType: SupportedRateType.both, + isAvailable: true, + isStackCoin: Currency.checkIsStackCoin(limit.currency), + tokenContract: null, + ); + currencies.add(currency); + } + + return ExchangeResponse(value: currencies); + } + + @override + Future>> getPairedCurrencies( + String forCurrency, bool fixedRate) { + // TODO: change this if the api changes to allow getting by paired currency + return getAllCurrencies(fixedRate); + } + + @override + Future>> getAllPairs(bool fixedRate) async { + final response = await MajesticBankAPI.instance.getRates(); + if (response.value == null) { + return ExchangeResponse(exception: response.exception); + } + + final List pairs = []; + final rates = response.value!; + + for (final rate in rates) { + final pair = Pair( + exchangeName: MajesticBankExchange.exchangeName, + from: rate.fromCurrency, + to: rate.toCurrency, + rateType: SupportedRateType.both, + ); + pairs.add(pair); + } + + return ExchangeResponse(value: pairs); + } + + @override + Future>> getEstimates( + String from, + String to, + Decimal amount, + bool fixedRate, + bool reversed, + ) async { + final response = await MajesticBankAPI.instance.calculateOrder( + amount: amount.toString(), + reversed: reversed, + fromCurrency: from, + receiveCurrency: to, + ); + if (response.value == null) { + return ExchangeResponse(exception: response.exception); + } + + final calc = response.value!; + final estimate = Estimate( + estimatedAmount: reversed ? calc.fromAmount : calc.receiveAmount, + fixedRate: fixedRate, + reversed: reversed, + exchangeProvider: MajesticBankExchange.exchangeName, + ); + return ExchangeResponse(value: [estimate]); + } + + @override + Future>> getPairsFor( + String currency, + bool fixedRate, + ) async { + final response = await getAllPairs(fixedRate); + if (response.value == null) { + return ExchangeResponse(exception: response.exception); + } + + final pairs = response.value!.where( + (e) => + e.from.toUpperCase() == currency.toUpperCase() || + e.to.toUpperCase() == currency.toUpperCase(), + ); + + return ExchangeResponse(value: pairs.toList()); + } + + @override + Future> getRange( + String from, + String to, + bool fixedRate, + ) async { + final response = + await MajesticBankAPI.instance.getLimit(fromCurrency: from); + if (response.value == null) { + return ExchangeResponse(exception: response.exception); + } + + final limit = response.value!; + final range = Range(min: limit.min, max: limit.max); + + return ExchangeResponse(value: range); + } + + @override + Future> getTrade(String tradeId) async { + // TODO: implement getTrade + throw UnimplementedError(); + } + + @override + Future>> getTrades() async { + // TODO: implement getTrades + throw UnimplementedError(); + } + + @override + String get name => exchangeName; + + @override + Future> updateTrade(Trade trade) async { + final response = await MajesticBankAPI.instance.trackOrder( + orderId: trade.tradeId, + ); + + if (response.value != null) { + final status = response.value!; + final updatedTrade = Trade( + uuid: trade.uuid, + tradeId: status.orderId, + rateType: trade.rateType, + direction: trade.direction, + timestamp: trade.timestamp, + updatedAt: DateTime.now(), + payInCurrency: status.fromCurrency, + payInAmount: status.fromAmount.toString(), + payInAddress: status.address, + payInNetwork: trade.payInNetwork, + payInExtraId: trade.payInExtraId, + payInTxid: trade.payInTxid, + payOutCurrency: status.receiveCurrency, + payOutAmount: status.receiveAmount.toString(), + payOutAddress: trade.payOutAddress, + payOutNetwork: trade.payOutNetwork, + payOutExtraId: trade.payOutExtraId, + payOutTxid: trade.payOutTxid, + refundAddress: trade.refundAddress, + refundExtraId: trade.refundExtraId, + status: status.status, + exchangeName: exchangeName, + ); + + return ExchangeResponse(value: updatedTrade); + } else { + if (response.exception?.type == ExchangeExceptionType.orderNotFound) { + final updatedTrade = Trade( + uuid: trade.uuid, + tradeId: trade.tradeId, + rateType: trade.rateType, + direction: trade.direction, + timestamp: trade.timestamp, + updatedAt: DateTime.now(), + payInCurrency: trade.payInCurrency, + payInAmount: trade.payInAmount, + payInAddress: trade.payInAddress, + payInNetwork: trade.payInNetwork, + payInExtraId: trade.payInExtraId, + payInTxid: trade.payInTxid, + payOutCurrency: trade.payOutCurrency, + payOutAmount: trade.payOutAmount, + payOutAddress: trade.payOutAddress, + payOutNetwork: trade.payOutNetwork, + payOutExtraId: trade.payOutExtraId, + payOutTxid: trade.payOutTxid, + refundAddress: trade.refundAddress, + refundExtraId: trade.refundExtraId, + status: "Completed", + exchangeName: exchangeName, + ); + return ExchangeResponse(value: updatedTrade); + } + return ExchangeResponse(exception: response.exception); + } + } +} diff --git a/lib/services/exchange/simpleswap/simpleswap_api.dart b/lib/services/exchange/simpleswap/simpleswap_api.dart index 3d626b2eb..8f14c222e 100644 --- a/lib/services/exchange/simpleswap/simpleswap_api.dart +++ b/lib/services/exchange/simpleswap/simpleswap_api.dart @@ -3,12 +3,13 @@ import 'dart:convert'; import 'package:decimal/decimal.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; +import 'package:stackwallet/exceptions/exchange/exchange_exception.dart'; import 'package:stackwallet/external_api_keys.dart'; import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart'; -import 'package:stackwallet/models/exchange/response_objects/pair.dart'; import 'package:stackwallet/models/exchange/response_objects/range.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/models/exchange/simpleswap/sp_currency.dart'; +import 'package:stackwallet/models/isar/exchange_cache/pair.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; import 'package:stackwallet/utilities/logger.dart'; @@ -271,12 +272,10 @@ class SimpleSwapAPI { for (final to in entry.value as List) { pairs.add( Pair( + exchangeName: SimpleSwapExchange.exchangeName, from: from, - fromNetwork: "", to: to as String, - toNetwork: "", - fixedRate: args.item2, - floatingRate: !args.item2, + rateType: SupportedRateType.estimated, ), ); } diff --git a/lib/services/exchange/simpleswap/simpleswap_exchange.dart b/lib/services/exchange/simpleswap/simpleswap_exchange.dart index 4c8d7e9aa..0bfe93a38 100644 --- a/lib/services/exchange/simpleswap/simpleswap_exchange.dart +++ b/lib/services/exchange/simpleswap/simpleswap_exchange.dart @@ -1,14 +1,20 @@ import 'package:decimal/decimal.dart'; -import 'package:stackwallet/models/exchange/response_objects/currency.dart'; import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; -import 'package:stackwallet/models/exchange/response_objects/pair.dart'; import 'package:stackwallet/models/exchange/response_objects/range.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; +import 'package:stackwallet/models/isar/exchange_cache/currency.dart'; +import 'package:stackwallet/models/isar/exchange_cache/pair.dart'; import 'package:stackwallet/services/exchange/exchange.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; import 'package:stackwallet/services/exchange/simpleswap/simpleswap_api.dart'; class SimpleSwapExchange extends Exchange { + SimpleSwapExchange._(); + + static SimpleSwapExchange? _instance; + static SimpleSwapExchange get instance => + _instance ??= SimpleSwapExchange._(); + static const exchangeName = "SimpleSwap"; @override @@ -24,7 +30,7 @@ class SimpleSwapExchange extends Exchange { String? extraId, required String addressRefund, required String refundExtraId, - String? rateId, + Estimate? estimate, required bool reversed, }) async { return await SimpleSwapAPI.instance.createNewExchange( @@ -47,18 +53,23 @@ class SimpleSwapExchange extends Exchange { await SimpleSwapAPI.instance.getAllCurrencies(fixedRate: fixedRate); if (response.value != null) { final List currencies = response.value! - .map((e) => Currency( - ticker: e.symbol, - name: e.name, - network: e.network, - image: e.image, - hasExternalId: e.hasExtraId, - externalId: e.extraId, - isFiat: false, - featured: false, - isStable: false, - supportsFixedRate: fixedRate, - )) + .map( + (e) => Currency( + exchangeName: exchangeName, + ticker: e.symbol, + name: e.name, + network: e.network, + image: e.image, + externalId: e.extraId, + isFiat: false, + rateType: fixedRate + ? SupportedRateType.both + : SupportedRateType.estimated, + isAvailable: true, + isStackCoin: Currency.checkIsStackCoin(e.symbol), + tokenContract: null, + ), + ) .toList(); return ExchangeResponse>( value: currencies, @@ -78,7 +89,7 @@ class SimpleSwapExchange extends Exchange { } @override - Future> getEstimate( + Future>> getEstimates( String from, String to, Decimal amount, @@ -98,11 +109,14 @@ class SimpleSwapExchange extends Exchange { } return ExchangeResponse( - value: Estimate( - estimatedAmount: Decimal.parse(response.value!), - fixedRate: fixedRate, - reversed: reversed, - ), + value: [ + Estimate( + estimatedAmount: Decimal.parse(response.value!), + fixedRate: fixedRate, + reversed: reversed, + exchangeProvider: SimpleSwapExchange.exchangeName, + ), + ], ); } @@ -146,4 +160,11 @@ class SimpleSwapExchange extends Exchange { // TODO: implement getTrades throw UnimplementedError(); } + + @override + Future>> getPairedCurrencies( + String forCurrency, bool fixedRate) { + // TODO: implement getPairedCurrencies + throw UnimplementedError(); + } } diff --git a/lib/services/exchange/trocador/response_objects/trocador_coin.dart b/lib/services/exchange/trocador/response_objects/trocador_coin.dart new file mode 100644 index 000000000..aea01d2f1 --- /dev/null +++ b/lib/services/exchange/trocador/response_objects/trocador_coin.dart @@ -0,0 +1,44 @@ +import 'package:decimal/decimal.dart'; + +class TrocadorCoin { + final String name; + final String ticker; + final String network; + final bool memo; + final String image; + final Decimal minimum; + final Decimal maximum; + + TrocadorCoin({ + required this.name, + required this.ticker, + required this.network, + required this.memo, + required this.image, + required this.minimum, + required this.maximum, + }); + + factory TrocadorCoin.fromMap(Map json) => TrocadorCoin( + name: json['name'] as String, + ticker: json['ticker'] as String, + network: json['network'] as String, + memo: json['memo'] as bool, + image: json['image'] as String, + minimum: Decimal.parse(json['minimum'].toString()), + maximum: Decimal.parse(json['maximum'].toString()), + ); + + @override + String toString() { + return 'TrocadorCoin( ' + 'name: $name, ' + 'ticker: $ticker, ' + 'network: $network, ' + 'memo: $memo, ' + 'image: $image, ' + 'minimum: $minimum, ' + 'maximum: $maximum ' + ')'; + } +} diff --git a/lib/services/exchange/trocador/response_objects/trocador_quote.dart b/lib/services/exchange/trocador/response_objects/trocador_quote.dart new file mode 100644 index 000000000..ada8725f5 --- /dev/null +++ b/lib/services/exchange/trocador/response_objects/trocador_quote.dart @@ -0,0 +1,47 @@ +import 'package:decimal/decimal.dart'; + +class TrocadorQuote { + final String provider; + final String kycRating; + final int insurance; + final bool fixed; + final Decimal? amountTo; + final Decimal? amountFrom; + final Decimal waste; + + TrocadorQuote({ + required this.provider, + required this.kycRating, + required this.insurance, + required this.fixed, + required this.amountTo, + required this.amountFrom, + required this.waste, + }); + + factory TrocadorQuote.fromMap(Map map) { + return TrocadorQuote( + provider: map['provider'] as String, + kycRating: map['kycrating'] as String, + insurance: map['insurance'] as int, + // wtf trocador? + fixed: map['fixed'] == "True", + amountTo: Decimal.tryParse(map['amount_to'].toString()), + amountFrom: Decimal.tryParse(map['amount_from'].toString()), + waste: Decimal.parse(map['waste'].toString()), + ); + } + + @override + String toString() { + return 'TrocadorQuote( ' + 'provider: $provider, ' + 'kycRating: $kycRating, ' + 'insurance: $insurance, ' + 'fixed: $fixed, ' + 'amountTo: $amountTo, ' + 'amountFrom: $amountFrom, ' + 'waste: $waste ' + ')'; + } +} diff --git a/lib/services/exchange/trocador/response_objects/trocador_rate.dart b/lib/services/exchange/trocador/response_objects/trocador_rate.dart new file mode 100644 index 000000000..5c3d18da2 --- /dev/null +++ b/lib/services/exchange/trocador/response_objects/trocador_rate.dart @@ -0,0 +1,84 @@ +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_quote.dart'; + +class TrocadorRate { + final String tradeId; + final DateTime date; + final String tickerFrom; + final String tickerTo; + final String coinFrom; + final String coinTo; + final String networkFrom; + final String networkTo; + final Decimal amountFrom; + final Decimal amountTo; + final String provider; + final bool fixed; + final bool payment; + final String status; + final List quotes; + + TrocadorRate({ + required this.tradeId, + required this.date, + required this.tickerFrom, + required this.tickerTo, + required this.coinFrom, + required this.coinTo, + required this.networkFrom, + required this.networkTo, + required this.amountFrom, + required this.amountTo, + required this.provider, + required this.fixed, + required this.payment, + required this.status, + required this.quotes, + }); + + factory TrocadorRate.fromMap(Map map) { + final list = + List>.from(map['quotes']['quotes'] as List); + final quotes = list.map((quote) => TrocadorQuote.fromMap(quote)).toList(); + + return TrocadorRate( + tradeId: map['trade_id'] as String, + date: DateTime.parse(map['date'] as String), + tickerFrom: map['ticker_from'] as String, + tickerTo: map['ticker_to'] as String, + coinFrom: map['coin_from'] as String, + coinTo: map['coin_to'] as String, + networkFrom: map['network_from'] as String, + networkTo: map['network_to'] as String, + amountFrom: Decimal.parse(map['amount_from'].toString()), + amountTo: Decimal.parse(map['amount_to'].toString()), + provider: map['provider'] as String, + fixed: map['fixed'] as bool, + payment: map['payment'] as bool, + status: map['status'] as String, + quotes: quotes, + ); + } + + @override + String toString() { + final quotesString = quotes.map((quote) => quote.toString()).join(', '); + return 'TrocadorRate(' + 'tradeId: $tradeId, ' + 'date: $date, ' + 'tickerFrom: $tickerFrom, ' + 'tickerTo: $tickerTo, ' + 'coinFrom: $coinFrom, ' + 'coinTo: $coinTo, ' + 'networkFrom: $networkFrom, ' + 'networkTo: $networkTo, ' + 'amountFrom: $amountFrom, ' + 'amountTo: $amountTo, ' + 'provider: $provider, ' + 'fixed: $fixed, ' + 'payment: $payment, ' + 'status: $status, ' + 'quotes: [$quotesString] ' + ')'; + } +} diff --git a/lib/services/exchange/trocador/response_objects/trocador_trade.dart b/lib/services/exchange/trocador/response_objects/trocador_trade.dart new file mode 100644 index 000000000..5768a9423 --- /dev/null +++ b/lib/services/exchange/trocador/response_objects/trocador_trade.dart @@ -0,0 +1,113 @@ +import 'package:decimal/decimal.dart'; + +class TrocadorTrade { + final String tradeId; + final DateTime date; + final String tickerFrom; + final String tickerTo; + final String coinFrom; + final String coinTo; + final String networkFrom; + final String networkTo; + final Decimal amountFrom; + final Decimal amountTo; + final String provider; + final bool fixed; + final String status; + final String addressProvider; + final String addressProviderMemo; + final String addressUser; + final String addressUserMemo; + final String refundAddress; + final String refundAddressMemo; + final String password; + final String idProvider; + + // dynamic is the devil but this could be anything... because json. + final dynamic quotes; + + final bool payment; + + TrocadorTrade({ + required this.tradeId, + required this.date, + required this.tickerFrom, + required this.tickerTo, + required this.coinFrom, + required this.coinTo, + required this.networkFrom, + required this.networkTo, + required this.amountFrom, + required this.amountTo, + required this.provider, + required this.fixed, + required this.status, + required this.addressProvider, + required this.addressProviderMemo, + required this.addressUser, + required this.addressUserMemo, + required this.refundAddress, + required this.refundAddressMemo, + required this.password, + required this.idProvider, + required this.quotes, + required this.payment, + }); + + factory TrocadorTrade.fromMap(Map map) { + return TrocadorTrade( + tradeId: map['trade_id'] as String, + date: DateTime.parse(map['date'] as String), + tickerFrom: map['ticker_from'] as String, + tickerTo: map['ticker_to'] as String, + coinFrom: map['coin_from'] as String, + coinTo: map['coin_to'] as String, + networkFrom: map['network_from'] as String, + networkTo: map['network_to'] as String, + amountFrom: Decimal.parse(map['amount_from'].toString()), + amountTo: Decimal.parse(map['amount_to'].toString()), + provider: map['provider'] as String, + fixed: map['fixed'] as bool, + status: map['status'] as String, + addressProvider: map['address_provider'] as String, + addressProviderMemo: map['address_provider_memo'] as String, + addressUser: map['address_user'] as String, + addressUserMemo: map['address_user_memo'] as String, + refundAddress: map['refund_address'] as String, + refundAddressMemo: map['refund_address_memo'] as String, + password: map['password'] as String, + idProvider: map['id_provider'] as String, + quotes: map['quotes'], + payment: map['payment'] as bool, + ); + } + + @override + String toString() { + return 'TrocadorTrade( ' + 'tradeId: $tradeId, ' + 'date: $date, ' + 'tickerFrom: $tickerFrom, ' + 'tickerTo: $tickerTo, ' + 'coinFrom: $coinFrom, ' + 'coinTo: $coinTo, ' + 'networkFrom: $networkFrom, ' + 'networkTo: $networkTo, ' + 'amountFrom: $amountFrom, ' + 'amountTo: $amountTo, ' + 'provider: $provider, ' + 'fixed: $fixed, ' + 'status: $status, ' + 'addressProvider: $addressProvider, ' + 'addressProviderMemo: $addressProviderMemo, ' + 'addressUser: $addressUser, ' + 'addressUserMemo: $addressUserMemo, ' + 'refundAddress: $refundAddress, ' + 'refundAddressMemo: $refundAddressMemo, ' + 'password: $password, ' + 'idProvider: $idProvider, ' + 'quotes: $quotes, ' + 'payment: $payment ' + ')'; + } +} diff --git a/lib/services/exchange/trocador/response_objects/trocador_trade_new.dart b/lib/services/exchange/trocador/response_objects/trocador_trade_new.dart new file mode 100644 index 000000000..4dc8ae380 --- /dev/null +++ b/lib/services/exchange/trocador/response_objects/trocador_trade_new.dart @@ -0,0 +1,106 @@ +import 'package:decimal/decimal.dart'; + +class TrocadorTradeNew { + final String tradeId; + final DateTime date; + final String tickerFrom; + final String tickerTo; + final String coinFrom; + final String coinTo; + final String networkFrom; + final String networkTo; + final Decimal amountFrom; + final Decimal amountTo; + final String provider; + final bool fixed; + final String status; + final String addressProvider; + final String addressProviderMemo; + final String addressUser; + final String addressUserMemo; + final String refundAddress; + final String refundAddressMemo; + final String password; + final String idProvider; + final bool payment; + + TrocadorTradeNew({ + required this.tradeId, + required this.date, + required this.tickerFrom, + required this.tickerTo, + required this.coinFrom, + required this.coinTo, + required this.networkFrom, + required this.networkTo, + required this.amountFrom, + required this.amountTo, + required this.provider, + required this.fixed, + required this.status, + required this.addressProvider, + required this.addressProviderMemo, + required this.addressUser, + required this.addressUserMemo, + required this.refundAddress, + required this.refundAddressMemo, + required this.password, + required this.idProvider, + required this.payment, + }); + + factory TrocadorTradeNew.fromMap(Map map) { + return TrocadorTradeNew( + tradeId: map['trade_id'] as String, + date: DateTime.parse(map['date'] as String), + tickerFrom: map['ticker_from'] as String, + tickerTo: map['ticker_to'] as String, + coinFrom: map['coin_from'] as String, + coinTo: map['coin_to'] as String, + networkFrom: map['network_from'] as String, + networkTo: map['network_to'] as String, + amountFrom: Decimal.parse(map['amount_from'].toString()), + amountTo: Decimal.parse(map['amount_to'].toString()), + provider: map['provider'] as String, + fixed: map['fixed'] as bool, + status: map['status'] as String, + addressProvider: map['address_provider'] as String, + addressProviderMemo: map['address_provider_memo'] as String, + addressUser: map['address_user'] as String, + addressUserMemo: map['address_user_memo'] as String, + refundAddress: map['refund_address'] as String, + refundAddressMemo: map['refund_address_memo'] as String, + password: map['password'] as String, + idProvider: map['id_provider'] as String, + payment: map['payment'] as bool, + ); + } + + @override + String toString() { + return 'TrocadorTradeNew( ' + 'tradeId: $tradeId, ' + 'date: $date, ' + 'tickerFrom: $tickerFrom, ' + 'tickerTo: $tickerTo, ' + 'coinFrom: $coinFrom, ' + 'coinTo: $coinTo, ' + 'networkFrom: $networkFrom, ' + 'networkTo: $networkTo, ' + 'amountFrom: $amountFrom, ' + 'amountTo: $amountTo, ' + 'provider: $provider, ' + 'fixed: $fixed, ' + 'status: $status, ' + 'addressProvider: $addressProvider, ' + 'addressProviderMemo: $addressProviderMemo, ' + 'addressUser: $addressUser, ' + 'addressUserMemo: $addressUserMemo, ' + 'refundAddress: $refundAddress, ' + 'refundAddressMemo: $refundAddressMemo, ' + 'password: $password, ' + 'idProvider: $idProvider, ' + 'payment: $payment ' + ')'; + } +} diff --git a/lib/services/exchange/trocador/trocador_api.dart b/lib/services/exchange/trocador/trocador_api.dart new file mode 100644 index 000000000..d74ff2460 --- /dev/null +++ b/lib/services/exchange/trocador/trocador_api.dart @@ -0,0 +1,339 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter_native_splash/cli_commands.dart'; +import 'package:http/http.dart' as http; +import 'package:stackwallet/exceptions/exchange/exchange_exception.dart'; +import 'package:stackwallet/services/exchange/exchange_response.dart'; +import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_coin.dart'; +import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_rate.dart'; +import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_trade.dart'; +import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_trade_new.dart'; +import 'package:stackwallet/utilities/logger.dart'; + +const kTrocadorApiKey = "8rFqf7QLxX1mUBiNPEMaLUpV2biz6n"; +const kTrocadorRefCode = "9eHm9BkQfS"; + +abstract class TrocadorAPI { + static const String authority = "trocador.app"; + static const String onionAuthority = + "trocadorfyhlu27aefre5u7zri66gudtzdyelymftvr4yjwcxhfaqsid.onion"; + + static const String markup = "1"; + static const String minKYCRating = "C"; + + static Uri _buildUri({ + required String method, + required bool isOnion, + Map? params, + }) { + return isOnion + ? Uri.http(onionAuthority, "api/$method", params) + : Uri.https(authority, "api/$method", params); + } + + static Future _makeGetRequest(Uri uri) async { + int code = -1; + try { + debugPrint("URI: $uri"); + final response = await http.get( + uri, + headers: {'Content-Type': 'application/json'}, + ); + + code = response.statusCode; + + debugPrint("CODE: $code"); + debugPrint("BODY: ${response.body}"); + + final json = jsonDecode(response.body); + + return json; + } catch (e, s) { + Logging.instance.log( + "_makeRequest($uri) HTTP:$code threw: $e\n$s", + level: LogLevel.Error, + ); + rethrow; + } + } + + /// fetch all supported coins + static Future>> getCoins({ + required bool isOnion, + }) async { + final uri = _buildUri( + isOnion: isOnion, + method: "coins", + params: { + "api_key": kTrocadorApiKey, + "ref": kTrocadorRefCode, + }, + ); + + try { + final json = await _makeGetRequest(uri); + + if (json is List) { + final list = List>.from(json); + final List coins = list + .map( + (e) => TrocadorCoin.fromMap(e), + ) + .toList(); + + return ExchangeResponse(value: coins); + } else { + throw Exception("unexpected json: $json"); + } + } catch (e, s) { + Logging.instance.log("getCoins exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + /// get trade info + static Future> getTrade({ + required bool isOnion, + required String tradeId, + }) async { + final uri = _buildUri( + isOnion: isOnion, + method: "trade", + params: { + "api_key": kTrocadorApiKey, + "ref": kTrocadorRefCode, + "id": tradeId, + }, + ); + + try { + final json = await _makeGetRequest(uri); + final map = Map.from((json as List).first as Map); + + return ExchangeResponse(value: TrocadorTrade.fromMap(map)); + } catch (e, s) { + Logging.instance.log("getTrade exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + /// get standard/floating rate + static Future> getNewStandardRate({ + required bool isOnion, + required String fromTicker, + required String fromNetwork, + required String toTicker, + required String toNetwork, + required String fromAmount, + }) async { + final params = { + "api_key": kTrocadorApiKey, + "ref": kTrocadorRefCode, + "ticker_from": fromTicker.toLowerCase(), + "network_from": fromNetwork, + "ticker_to": toTicker.toLowerCase(), + "network_to": toNetwork, + "amount_from": fromAmount, + "payment": "false", + "min_kycrating": minKYCRating, + "markup": markup, + }; + + return await _getNewRate(isOnion: isOnion, params: params); + } + + /// get fixed rate/payment rate + static Future> getNewPaymentRate({ + required bool isOnion, + required String fromTicker, + required String fromNetwork, + required String toTicker, + required String toNetwork, + required String toAmount, + }) async { + final params = { + "api_key": kTrocadorApiKey, + "ref": kTrocadorRefCode, + "ticker_from": fromTicker.toLowerCase(), + "network_from": fromNetwork, + "ticker_to": toTicker.toLowerCase(), + "network_to": toNetwork, + "amount_to": toAmount, + "payment": "true", + "min_kycrating": minKYCRating, + "markup": markup, + }; + + return await _getNewRate(isOnion: isOnion, params: params); + } + + static Future> _getNewRate({ + required bool isOnion, + required Map params, + }) async { + final uri = _buildUri( + isOnion: isOnion, + method: "new_rate", + params: params, + ); + + try { + final json = await _makeGetRequest(uri); + final map = Map.from(json as Map); + + return ExchangeResponse(value: TrocadorRate.fromMap(map)); + } catch (e, s) { + Logging.instance + .log("getNewRate exception: $e\n$s", level: LogLevel.Error); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } + + /// create new floating rate/standard trade + static Future> createNewStandardRateTrade({ + required bool isOnion, + required String? rateId, + required String fromTicker, + required String fromNetwork, + required String toTicker, + required String toNetwork, + required String fromAmount, + required String receivingAddress, + required String? receivingMemo, + required String refundAddress, + required String? refundMemo, + required String exchangeProvider, + required bool isFixedRate, + }) async { + final Map params = { + "api_key": kTrocadorApiKey, + "ref": kTrocadorRefCode, + "ticker_from": fromTicker.toLowerCase(), + "network_from": fromNetwork, + "ticker_to": toTicker.toLowerCase(), + "network_to": toNetwork, + "amount_from": fromAmount, + "address": receivingAddress, + "address_memo": receivingMemo ?? "0", + "refund": refundAddress, + "refund_memo": refundMemo ?? "0", + "provider": exchangeProvider, + "fixed": isFixedRate.toString().capitalize(), + "payment": "False", + "min_kycrating": minKYCRating, + "markup": markup, + }; + + if (rateId != null) { + params["id"] = rateId; + } + + return await _getNewTrade(isOnion: isOnion, params: params); + } + + static Future> createNewPaymentRateTrade({ + required bool isOnion, + required String? rateId, + required String fromTicker, + required String fromNetwork, + required String toTicker, + required String toNetwork, + required String toAmount, + required String receivingAddress, + required String? receivingMemo, + required String refundAddress, + required String? refundMemo, + required String exchangeProvider, + required bool isFixedRate, + }) async { + final params = { + "api_key": kTrocadorApiKey, + "ref": kTrocadorRefCode, + "ticker_from": fromTicker.toLowerCase(), + "network_from": fromNetwork, + "ticker_to": toTicker.toLowerCase(), + "network_to": toNetwork, + "amount_to": toAmount, + "address": receivingAddress, + "address_memo": receivingMemo ?? "0", + "refund": refundAddress, + "refund_memo": refundMemo ?? "0", + "provider": exchangeProvider, + "fixed": isFixedRate.toString().capitalize(), + "payment": "True", + "min_kycrating": minKYCRating, + "markup": markup, + }; + + if (rateId != null) { + params["id"] = rateId; + } + + return await _getNewTrade(isOnion: isOnion, params: params); + } + + static Future> _getNewTrade({ + required bool isOnion, + required Map params, + }) async { + final uri = _buildUri( + isOnion: isOnion, + method: "new_trade", + params: params, + ); + + try { + final json = await _makeGetRequest(uri); + final map = Map.from(json as Map); + + try { + return ExchangeResponse(value: TrocadorTradeNew.fromMap(map)); + } catch (e, s) { + String error = map["error"] as String? ?? json.toString(); + if (error == + "trade could not be generated, some unknown error happened") { + error = + "This trade couldn't be completed. Please select another provider."; + } + + Logging.instance.log( + "_getNewTrade failed to parse response: $json\n$e\n$s", + level: LogLevel.Error, + ); + return ExchangeResponse( + exception: ExchangeException( + error, + ExchangeExceptionType.serializeResponseError, + ), + ); + } + } catch (e, s) { + Logging.instance.log( + "_getNewTrade exception: $e\n$s", + level: LogLevel.Error, + ); + return ExchangeResponse( + exception: ExchangeException( + e.toString(), + ExchangeExceptionType.generic, + ), + ); + } + } +} diff --git a/lib/services/exchange/trocador/trocador_exchange.dart b/lib/services/exchange/trocador/trocador_exchange.dart new file mode 100644 index 000000000..e0becb11c --- /dev/null +++ b/lib/services/exchange/trocador/trocador_exchange.dart @@ -0,0 +1,394 @@ +import 'dart:math'; + +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/exceptions/exchange/exchange_exception.dart'; +import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; +import 'package:stackwallet/models/exchange/response_objects/range.dart'; +import 'package:stackwallet/models/exchange/response_objects/trade.dart'; +import 'package:stackwallet/models/isar/exchange_cache/currency.dart'; +import 'package:stackwallet/models/isar/exchange_cache/pair.dart'; +import 'package:stackwallet/services/exchange/exchange.dart'; +import 'package:stackwallet/services/exchange/exchange_response.dart'; +import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_coin.dart'; +import 'package:stackwallet/services/exchange/trocador/response_objects/trocador_quote.dart'; +import 'package:stackwallet/services/exchange/trocador/trocador_api.dart'; +import 'package:uuid/uuid.dart'; + +class TrocadorExchange extends Exchange { + TrocadorExchange._(); + + static TrocadorExchange? _instance; + static TrocadorExchange get instance => _instance ??= TrocadorExchange._(); + + static const exchangeName = "Trocador"; + + static const onlySupportedNetwork = "Mainnet"; + + @override + Future> createTrade({ + required String from, + required String to, + required bool fixedRate, + required Decimal amount, + required String addressTo, + String? extraId, + required String addressRefund, + required String refundExtraId, + Estimate? estimate, + required bool reversed, + }) async { + final response = reversed + ? await TrocadorAPI.createNewPaymentRateTrade( + isOnion: false, + rateId: estimate?.rateId, + fromTicker: from.toLowerCase(), + fromNetwork: onlySupportedNetwork, + toTicker: to.toLowerCase(), + toNetwork: onlySupportedNetwork, + toAmount: amount.toString(), + receivingAddress: addressTo, + receivingMemo: null, + refundAddress: addressRefund, + refundMemo: null, + exchangeProvider: estimate!.exchangeProvider!, + isFixedRate: fixedRate, + ) + : await TrocadorAPI.createNewStandardRateTrade( + isOnion: false, + rateId: estimate?.rateId, + fromTicker: from.toLowerCase(), + fromNetwork: onlySupportedNetwork, + toTicker: to.toLowerCase(), + toNetwork: onlySupportedNetwork, + fromAmount: amount.toString(), + receivingAddress: addressTo, + receivingMemo: null, + refundAddress: addressRefund, + refundMemo: null, + exchangeProvider: estimate!.exchangeProvider!, + isFixedRate: fixedRate, + ); + + if (response.value == null) { + return ExchangeResponse(exception: response.exception); + } + + final trade = response.value!; + + return ExchangeResponse( + value: Trade( + uuid: const Uuid().v1(), + tradeId: trade.tradeId, + rateType: fixedRate ? "fixed" : "floating", + direction: reversed ? "reversed" : "direct", + timestamp: trade.date, + updatedAt: trade.date, + payInCurrency: trade.coinFrom, + payInAmount: trade.amountFrom.toString(), + payInAddress: trade.addressProvider, + payInNetwork: trade.networkFrom, + payInExtraId: trade.addressProviderMemo, + payInTxid: "", + payOutCurrency: trade.coinTo, + payOutAmount: trade.amountTo.toString(), + payOutAddress: trade.addressUser, + payOutNetwork: trade.networkTo, + payOutExtraId: trade.addressUserMemo, + payOutTxid: "", + refundAddress: trade.refundAddress, + refundExtraId: trade.refundAddressMemo, + status: trade.status, + exchangeName: "$exchangeName (${trade.provider})", + ), + ); + } + + List? _cachedCurrencies; + + @override + Future>> getAllCurrencies( + bool fixedRate) async { + _cachedCurrencies ??= (await TrocadorAPI.getCoins(isOnion: false)).value; + + _cachedCurrencies?.removeWhere((e) => e.network != onlySupportedNetwork); + + final value = _cachedCurrencies + ?.map( + (e) => Currency( + exchangeName: exchangeName, + ticker: e.ticker, + name: e.name, + network: e.network, + image: e.image, + isFiat: false, + rateType: SupportedRateType.both, + isStackCoin: Currency.checkIsStackCoin(e.ticker), + tokenContract: null, + isAvailable: true, + ), + ) + .toList(); + + if (value == null) { + return ExchangeResponse( + exception: ExchangeException( + "Failed to fetch trocador coins", + ExchangeExceptionType.generic, + ), + ); + } else { + return ExchangeResponse(value: value); + } + } + + @override + Future>> getAllPairs(bool fixedRate) async { + final response = await getAllCurrencies(fixedRate); + + if (response.value == null) { + return ExchangeResponse(exception: response.exception); + } + + final List pairs = []; + + for (int i = 0; i < response.value!.length; i++) { + final a = response.value![i]; + + for (int j = i + 1; j < response.value!.length; j++) { + final b = response.value![j]; + + pairs.add( + Pair( + exchangeName: exchangeName, + from: a.ticker, + to: b.ticker, + rateType: SupportedRateType.both, + ), + ); + pairs.add( + Pair( + exchangeName: exchangeName, + to: a.ticker, + from: b.ticker, + rateType: SupportedRateType.both, + ), + ); + } + } + + return ExchangeResponse(value: pairs); + } + + @override + Future>> getEstimates( + String from, + String to, + Decimal amount, + bool fixedRate, + bool reversed, + ) async { + final response = reversed + ? await TrocadorAPI.getNewPaymentRate( + isOnion: false, + fromTicker: from, + fromNetwork: onlySupportedNetwork, + toTicker: to, + toNetwork: onlySupportedNetwork, + toAmount: amount.toString(), + ) + : await TrocadorAPI.getNewStandardRate( + isOnion: false, + fromTicker: from, + fromNetwork: onlySupportedNetwork, + toTicker: to, + toNetwork: onlySupportedNetwork, + fromAmount: amount.toString(), + ); + + if (response.value == null) { + return ExchangeResponse(exception: response.exception); + } + + final List estimates = []; + final List cOrLowerQuotes = []; + + for (final quote in response.value!.quotes) { + if (quote.fixed == fixedRate && + quote.provider.toLowerCase() != "changenow") { + final rating = quote.kycRating.toLowerCase(); + if (rating == "a" || rating == "b") { + estimates.add( + Estimate( + estimatedAmount: reversed ? quote.amountFrom! : quote.amountTo!, + fixedRate: quote.fixed, + reversed: reversed, + exchangeProvider: quote.provider, + rateId: response.value!.tradeId, + kycRating: quote.kycRating, + ), + ); + } else { + cOrLowerQuotes.add(quote); + } + } + } + + cOrLowerQuotes.sort((a, b) => b.waste.compareTo(a.waste)); + + for (int i = 0; i < min(3, cOrLowerQuotes.length); i++) { + final quote = cOrLowerQuotes[i]; + estimates.add( + Estimate( + estimatedAmount: reversed ? quote.amountFrom! : quote.amountTo!, + fixedRate: quote.fixed, + reversed: reversed, + exchangeProvider: quote.provider, + rateId: response.value!.tradeId, + kycRating: quote.kycRating, + ), + ); + } + + return ExchangeResponse( + value: estimates + ..sort((a, b) => b.estimatedAmount.compareTo(a.estimatedAmount)), + ); + } + + @override + Future>> getPairedCurrencies( + String forCurrency, bool fixedRate) async { + // TODO: implement getPairedCurrencies + throw UnimplementedError(); + } + + @override + Future>> getPairsFor( + String currency, + bool fixedRate, + ) async { + final response = await getAllPairs(fixedRate); + if (response.value == null) { + return ExchangeResponse(exception: response.exception); + } + + final pairs = response.value!.where( + (e) => + e.from.toUpperCase() == currency.toUpperCase() || + e.to.toUpperCase() == currency.toUpperCase(), + ); + + return ExchangeResponse(value: pairs.toList()); + } + + @override + Future> getRange( + String from, + String to, + bool fixedRate, + ) async { + if (_cachedCurrencies == null) { + await getAllCurrencies(fixedRate); + } + if (_cachedCurrencies == null) { + return ExchangeResponse( + exception: ExchangeException( + "Failed to updated trocador cached coins to get min/max range", + ExchangeExceptionType.generic, + ), + ); + } + + final fromCoin = _cachedCurrencies! + .firstWhere((e) => e.ticker.toLowerCase() == from.toLowerCase()); + + return ExchangeResponse( + value: Range( + max: fromCoin.maximum, + min: fromCoin.minimum, + ), + ); + } + + @override + Future> getTrade(String tradeId) async { + // TODO: implement getTrade + throw UnimplementedError(); + } + + @override + Future>> getTrades() async { + // TODO: implement getTrades + throw UnimplementedError(); + } + + @override + String get name => exchangeName; + + @override + Future> updateTrade(Trade trade) async { + final response = await TrocadorAPI.getTrade( + isOnion: false, + tradeId: trade.tradeId, + ); + + if (response.value != null) { + final updated = response.value!; + final updatedTrade = Trade( + uuid: trade.uuid, + tradeId: updated.tradeId, + rateType: trade.rateType, + direction: trade.direction, + timestamp: trade.timestamp, + updatedAt: DateTime.now(), + payInCurrency: updated.coinFrom, + payInAmount: updated.amountFrom.toString(), + payInAddress: updated.addressProvider, + payInNetwork: trade.payInNetwork, + payInExtraId: trade.payInExtraId, + payInTxid: trade.payInTxid, + payOutCurrency: updated.coinTo, + payOutAmount: updated.amountTo.toString(), + payOutAddress: updated.addressUser, + payOutNetwork: trade.payOutNetwork, + payOutExtraId: trade.payOutExtraId, + payOutTxid: trade.payOutTxid, + refundAddress: trade.refundAddress, + refundExtraId: trade.refundExtraId, + status: updated.status, + exchangeName: "$exchangeName (${updated.provider})", + ); + + return ExchangeResponse(value: updatedTrade); + } else { + if (response.exception?.type == ExchangeExceptionType.orderNotFound) { + final updatedTrade = Trade( + uuid: trade.uuid, + tradeId: trade.tradeId, + rateType: trade.rateType, + direction: trade.direction, + timestamp: trade.timestamp, + updatedAt: DateTime.now(), + payInCurrency: trade.payInCurrency, + payInAmount: trade.payInAmount, + payInAddress: trade.payInAddress, + payInNetwork: trade.payInNetwork, + payInExtraId: trade.payInExtraId, + payInTxid: trade.payInTxid, + payOutCurrency: trade.payOutCurrency, + payOutAmount: trade.payOutAmount, + payOutAddress: trade.payOutAddress, + payOutNetwork: trade.payOutNetwork, + payOutExtraId: trade.payOutExtraId, + payOutTxid: trade.payOutTxid, + refundAddress: trade.refundAddress, + refundExtraId: trade.refundExtraId, + status: "Unknown", + exchangeName: trade.exchangeName, + ); + return ExchangeResponse(value: updatedTrade); + } + return ExchangeResponse(exception: response.exception); + } + } +} diff --git a/lib/services/locale_service.dart b/lib/services/locale_service.dart index e91f5a1c6..edb8bfc98 100644 --- a/lib/services/locale_service.dart +++ b/lib/services/locale_service.dart @@ -1,4 +1,5 @@ import 'dart:io'; + import 'package:devicelocale/devicelocale.dart'; import 'package:flutter/material.dart'; @@ -8,7 +9,9 @@ class LocaleService extends ChangeNotifier { String get locale => _locale; Future loadLocale({bool notify = true}) async { - _locale =Platform.isWindows ? "en_US" : await Devicelocale.currentLocale ?? "en_US"; + _locale = Platform.isWindows + ? "en_US" + : await Devicelocale.currentLocale ?? "en_US"; if (notify) { notifyListeners(); } diff --git a/lib/services/mixins/coin_control_interface.dart b/lib/services/mixins/coin_control_interface.dart new file mode 100644 index 000000000..d3e6079a0 --- /dev/null +++ b/lib/services/mixins/coin_control_interface.dart @@ -0,0 +1,95 @@ +import 'dart:async'; + +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/services/event_bus/events/global/balance_refreshed_event.dart'; +import 'package:stackwallet/services/event_bus/global_event_bus.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; + +mixin CoinControlInterface { + late final String _walletId; + late final String _walletName; + late final Coin _coin; + late final MainDB _db; + late final Future Function() _getChainHeight; + late final Future Function(Balance) _refreshedBalanceCallback; + + void initCoinControlInterface({ + required String walletId, + required String walletName, + required Coin coin, + required MainDB db, + required Future Function() getChainHeight, + required Future Function(Balance) refreshedBalanceCallback, + }) { + _walletId = walletId; + _walletName = walletName; + _coin = coin; + _db = db; + _getChainHeight = getChainHeight; + _refreshedBalanceCallback = refreshedBalanceCallback; + } + + Future refreshBalance({bool notify = false}) async { + final utxos = await _db.getUTXOs(_walletId).findAll(); + final currentChainHeight = await _getChainHeight(); + + Amount satoshiBalanceTotal = Amount( + rawValue: BigInt.zero, + fractionDigits: _coin.decimals, + ); + Amount satoshiBalancePending = Amount( + rawValue: BigInt.zero, + fractionDigits: _coin.decimals, + ); + Amount satoshiBalanceSpendable = Amount( + rawValue: BigInt.zero, + fractionDigits: _coin.decimals, + ); + Amount satoshiBalanceBlocked = Amount( + rawValue: BigInt.zero, + fractionDigits: _coin.decimals, + ); + + for (final utxo in utxos) { + final utxoAmount = Amount( + rawValue: BigInt.from(utxo.value), + fractionDigits: _coin.decimals, + ); + + satoshiBalanceTotal += utxoAmount; + + if (utxo.isBlocked) { + satoshiBalanceBlocked += utxoAmount; + } else { + if (utxo.isConfirmed( + currentChainHeight, + _coin.requiredConfirmations, + )) { + satoshiBalanceSpendable += utxoAmount; + } else { + satoshiBalancePending += utxoAmount; + } + } + } + + final balance = Balance( + total: satoshiBalanceTotal, + spendable: satoshiBalanceSpendable, + blockedTotal: satoshiBalanceBlocked, + pendingSpendable: satoshiBalancePending, + ); + + await _refreshedBalanceCallback(balance); + + if (notify) { + GlobalEventBus.instance.fire( + BalanceRefreshedEvent( + _walletId, + ), + ); + } + } +} diff --git a/lib/services/mixins/electrum_x_parsing.dart b/lib/services/mixins/electrum_x_parsing.dart new file mode 100644 index 000000000..c313a91eb --- /dev/null +++ b/lib/services/mixins/electrum_x_parsing.dart @@ -0,0 +1,253 @@ +import 'dart:convert'; + +import 'package:bip47/src/util.dart'; +import 'package:decimal/decimal.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:tuple/tuple.dart'; + +mixin ElectrumXParsing { + Future> parseTransaction( + Map txData, + dynamic electrumxClient, + List
myAddresses, + Coin coin, + int minConfirms, + String walletId, + ) async { + Set receivingAddresses = myAddresses + .where((e) => + e.subType == AddressSubType.receiving || + e.subType == AddressSubType.paynymReceive || + e.subType == AddressSubType.paynymNotification) + .map((e) => e.value) + .toSet(); + Set changeAddresses = myAddresses + .where((e) => e.subType == AddressSubType.change) + .map((e) => e.value) + .toSet(); + + Set inputAddresses = {}; + Set outputAddresses = {}; + + Amount totalInputValue = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); + Amount totalOutputValue = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); + + Amount amountSentFromWallet = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); + Amount amountReceivedInWallet = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); + Amount changeAmount = Amount( + rawValue: BigInt.zero, + fractionDigits: coin.decimals, + ); + + // parse inputs + for (final input in txData["vin"] as List) { + final prevTxid = input["txid"] as String; + final prevOut = input["vout"] as int; + + // fetch input tx to get address + final inputTx = await electrumxClient.getTransaction( + txHash: prevTxid, + coin: coin, + ); + + for (final output in inputTx["vout"] as List) { + // check matching output + if (prevOut == output["n"]) { + // get value + final value = Amount.fromDecimal( + Decimal.parse(output["value"].toString()), + fractionDigits: coin.decimals, + ); + + // add value to total + totalInputValue += value; + + // get input(prevOut) address + final address = output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]?["address"] as String?; + + if (address != null) { + inputAddresses.add(address); + + // if input was from my wallet, add value to amount sent + if (receivingAddresses.contains(address) || + changeAddresses.contains(address)) { + amountSentFromWallet += value; + } + } + } + } + } + + // parse outputs + for (final output in txData["vout"] as List) { + // get value + final value = Amount.fromDecimal( + Decimal.parse(output["value"].toString()), + fractionDigits: coin.decimals, + ); + + // add value to total + totalOutputValue += value; + + // get output address + final address = output["scriptPubKey"]?["addresses"]?[0] as String? ?? + output["scriptPubKey"]?["address"] as String?; + if (address != null) { + outputAddresses.add(address); + + // if output was to my wallet, add value to amount received + if (receivingAddresses.contains(address)) { + amountReceivedInWallet += value; + } else if (changeAddresses.contains(address)) { + changeAmount += value; + } + } + } + + final mySentFromAddresses = [ + ...receivingAddresses.intersection(inputAddresses), + ...changeAddresses.intersection(inputAddresses) + ]; + final myReceivedOnAddresses = + receivingAddresses.intersection(outputAddresses); + final myChangeReceivedOnAddresses = + changeAddresses.intersection(outputAddresses); + + final fee = totalInputValue - totalOutputValue; + + // this is the address initially used to fetch the txid + Address transactionAddress = txData["address"] as Address; + + TransactionType type; + Amount amount; + if (mySentFromAddresses.isNotEmpty && myReceivedOnAddresses.isNotEmpty) { + // tx is sent to self + type = TransactionType.sentToSelf; + + // should be 0 + amount = + amountSentFromWallet - amountReceivedInWallet - fee - changeAmount; + } else if (mySentFromAddresses.isNotEmpty) { + // outgoing tx + type = TransactionType.outgoing; + amount = amountSentFromWallet - changeAmount - fee; + + final possible = + outputAddresses.difference(myChangeReceivedOnAddresses).first; + + if (transactionAddress.value != possible) { + transactionAddress = Address( + walletId: walletId, + value: possible, + derivationIndex: -1, + derivationPath: null, + subType: AddressSubType.nonWallet, + type: AddressType.nonWallet, + publicKey: [], + ); + } + } else { + // incoming tx + type = TransactionType.incoming; + amount = amountReceivedInWallet; + } + + List outs = []; + List ins = []; + + for (final json in txData["vin"] as List) { + bool isCoinBase = json['coinbase'] != null; + String? witness; + if (json['witness'] != null && json['witness'] is String) { + witness = json['witness'] as String; + } else if (json['txinwitness'] != null) { + if (json['txinwitness'] is List) { + witness = jsonEncode(json['txinwitness']); + } + } + final input = Input( + txid: json['txid'] as String, + vout: json['vout'] as int? ?? -1, + scriptSig: json['scriptSig']?['hex'] as String?, + scriptSigAsm: json['scriptSig']?['asm'] as String?, + isCoinbase: isCoinBase ? isCoinBase : json['is_coinbase'] as bool?, + sequence: json['sequence'] as int?, + innerRedeemScriptAsm: json['innerRedeemscriptAsm'] as String?, + witness: witness, + ); + ins.add(input); + } + + for (final json in txData["vout"] as List) { + final output = Output( + scriptPubKey: json['scriptPubKey']?['hex'] as String?, + scriptPubKeyAsm: json['scriptPubKey']?['asm'] as String?, + scriptPubKeyType: json['scriptPubKey']?['type'] as String?, + scriptPubKeyAddress: + json["scriptPubKey"]?["addresses"]?[0] as String? ?? + json['scriptPubKey']?['type'] as String? ?? + "", + value: Amount.fromDecimal( + Decimal.parse(json["value"].toString()), + fractionDigits: coin.decimals, + ).raw.toInt(), + ); + outs.add(output); + } + + TransactionSubType txSubType = TransactionSubType.none; + if (this is PaynymWalletInterface && outs.length > 1 && ins.isNotEmpty) { + for (int i = 0; i < outs.length; i++) { + List? scriptChunks = outs[i].scriptPubKeyAsm?.split(" "); + if (scriptChunks?.length == 2 && scriptChunks?[0] == "OP_RETURN") { + final blindedPaymentCode = scriptChunks![1]; + final bytes = blindedPaymentCode.fromHex; + + // https://en.bitcoin.it/wiki/BIP_0047#Sending + if (bytes.length == 80 && bytes.first == 1) { + txSubType = TransactionSubType.bip47Notification; + } + } + } + } + + final tx = Transaction( + walletId: walletId, + txid: txData["txid"] as String, + timestamp: txData["blocktime"] as int? ?? + (DateTime.now().millisecondsSinceEpoch ~/ 1000), + type: type, + subType: txSubType, + // amount may overflow. Deprecated. Use amountString + amount: amount.raw.toInt(), + amountString: amount.toJsonString(), + fee: fee.raw.toInt(), + height: txData["height"] as int?, + isCancelled: false, + isLelantus: false, + slateId: null, + otherData: null, + nonce: null, + inputs: ins, + outputs: outs, + ); + + return Tuple2(tx, transactionAddress); + } +} diff --git a/lib/services/mixins/epic_cash_hive.dart b/lib/services/mixins/epic_cash_hive.dart new file mode 100644 index 000000000..3c5c91b07 --- /dev/null +++ b/lib/services/mixins/epic_cash_hive.dart @@ -0,0 +1,112 @@ +import 'package:stackwallet/db/hive/db.dart'; + +mixin EpicCashHive { + late final String _walletId; + + void initEpicCashHive(String walletId) { + _walletId = walletId; + } + + // receiving index + int? epicGetReceivingIndex() { + return DB.instance.get(boxName: _walletId, key: "receivingIndex") + as int?; + } + + Future epicUpdateReceivingIndex(int index) async { + await DB.instance.put( + boxName: _walletId, + key: "receivingIndex", + value: index, + ); + } + + // change index + int? epicGetChangeIndex() { + return DB.instance.get(boxName: _walletId, key: "changeIndex") + as int?; + } + + Future epicUpdateChangeIndex(int index) async { + await DB.instance.put( + boxName: _walletId, + key: "changeIndex", + value: index, + ); + } + + // slateToAddresses + Map epicGetSlatesToAddresses() { + return DB.instance.get( + boxName: _walletId, + key: "slate_to_address", + ) as Map? ?? + {}; + } + + Future epicUpdateSlatesToAddresses(Map map) async { + await DB.instance.put( + boxName: _walletId, + key: "slate_to_address", + value: map, + ); + } + + // slatesToCommits + Map? epicGetSlatesToCommits() { + return DB.instance.get( + boxName: _walletId, + key: "slatesToCommits", + ) as Map?; + } + + Future epicUpdateSlatesToCommits(Map map) async { + await DB.instance.put( + boxName: _walletId, + key: "slatesToCommits", + value: map, + ); + } + + // last scanned block + int? epicGetLastScannedBlock() { + return DB.instance.get(boxName: _walletId, key: "lastScannedBlock") + as int?; + } + + Future epicUpdateLastScannedBlock(int blockHeight) async { + await DB.instance.put( + boxName: _walletId, + key: "lastScannedBlock", + value: blockHeight, + ); + } + + // epic restore height + int? epicGetRestoreHeight() { + return DB.instance.get(boxName: _walletId, key: "restoreHeight") + as int?; + } + + Future epicUpdateRestoreHeight(int height) async { + await DB.instance.put( + boxName: _walletId, + key: "restoreHeight", + value: height, + ); + } + + // epic creation height + int? epicGetCreationHeight() { + return DB.instance.get(boxName: _walletId, key: "creationHeight") + as int?; + } + + Future epicUpdateCreationHeight(int height) async { + await DB.instance.put( + boxName: _walletId, + key: "creationHeight", + value: height, + ); + } +} diff --git a/lib/services/mixins/eth_token_cache.dart b/lib/services/mixins/eth_token_cache.dart new file mode 100644 index 000000000..b1fdf6bb3 --- /dev/null +++ b/lib/services/mixins/eth_token_cache.dart @@ -0,0 +1,60 @@ +import 'package:stackwallet/db/hive/db.dart'; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; + +abstract class TokenCacheKeys { + static String tokenBalance(String contractAddress) { + return "tokenBalanceCache_$contractAddress"; + } +} + +mixin EthTokenCache { + late final String _walletId; + late final EthContract _token; + + void initCache(String walletId, EthContract token) { + _walletId = walletId; + _token = token; + } + + // token balance cache + Balance getCachedBalance() { + final jsonString = DB.instance.get( + boxName: _walletId, + key: TokenCacheKeys.tokenBalance(_token.address), + ) as String?; + if (jsonString == null) { + return Balance( + total: Amount( + rawValue: BigInt.zero, + fractionDigits: _token.decimals, + ), + spendable: Amount( + rawValue: BigInt.zero, + fractionDigits: _token.decimals, + ), + blockedTotal: Amount( + rawValue: BigInt.zero, + fractionDigits: _token.decimals, + ), + pendingSpendable: Amount( + rawValue: BigInt.zero, + fractionDigits: _token.decimals, + ), + ); + } + return Balance.fromJson( + jsonString, + _token.decimals, + ); + } + + Future updateCachedBalance(Balance balance) async { + await DB.instance.put( + boxName: _walletId, + key: TokenCacheKeys.tokenBalance(_token.address), + value: balance.toJsonIgnoreCoin(), + ); + } +} diff --git a/lib/services/mixins/firo_hive.dart b/lib/services/mixins/firo_hive.dart new file mode 100644 index 000000000..321724ad1 --- /dev/null +++ b/lib/services/mixins/firo_hive.dart @@ -0,0 +1,50 @@ +import 'package:stackwallet/db/hive/db.dart'; + +mixin FiroHive { + late final String _walletId; + + void initFiroHive(String walletId) { + _walletId = walletId; + } + + // jindex + List? firoGetJIndex() { + return DB.instance.get(boxName: _walletId, key: "jindex") as List?; + } + + Future firoUpdateJIndex(List jIndex) async { + await DB.instance.put( + boxName: _walletId, + key: "jindex", + value: jIndex, + ); + } + + // _lelantus_coins + List? firoGetLelantusCoins() { + return DB.instance.get(boxName: _walletId, key: "_lelantus_coins") + as List?; + } + + Future firoUpdateLelantusCoins(List lelantusCoins) async { + await DB.instance.put( + boxName: _walletId, + key: "_lelantus_coins", + value: lelantusCoins, + ); + } + + // mintIndex + int? firoGetMintIndex() { + return DB.instance.get(boxName: _walletId, key: "mintIndex") + as int?; + } + + Future firoUpdateMintIndex(int mintIndex) async { + await DB.instance.put( + boxName: _walletId, + key: "mintIndex", + value: mintIndex, + ); + } +} diff --git a/lib/services/mixins/paynym_wallet_interface.dart b/lib/services/mixins/paynym_wallet_interface.dart new file mode 100644 index 000000000..a6902fd2b --- /dev/null +++ b/lib/services/mixins/paynym_wallet_interface.dart @@ -0,0 +1,1401 @@ +import 'dart:convert'; +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:bip32/bip32.dart' as bip32; +import 'package:bip47/bip47.dart'; +import 'package:bip47/src/util.dart'; +import 'package:bitcoindart/bitcoindart.dart' as btc_dart; +import 'package:bitcoindart/src/utils/constants/op.dart' as op; +import 'package:bitcoindart/src/utils/script.dart' as bscript; +import 'package:isar/isar.dart'; +import 'package:pointycastle/digests/sha256.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/electrumx_rpc/electrumx.dart'; +import 'package:stackwallet/exceptions/wallet/insufficient_balance_exception.dart'; +import 'package:stackwallet/exceptions/wallet/paynym_send_exception.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/models/signing_data.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/bip32_utils.dart'; +import 'package:stackwallet/utilities/bip47_utils.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +import 'package:stackwallet/utilities/format.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:tuple/tuple.dart'; + +const String kPCodeKeyPrefix = "pCode_key_"; + +String _basePaynymDerivePath({required bool testnet}) => + "m/47'/${testnet ? "1" : "0"}'/0'"; +String _notificationDerivationPath({required bool testnet}) => + "${_basePaynymDerivePath(testnet: testnet)}/0"; + +String _receivingPaynymAddressDerivationPath( + int index, { + required bool testnet, +}) => + "${_basePaynymDerivePath(testnet: testnet)}/$index/0"; +String _sendPaynymAddressDerivationPath( + int index, { + required bool testnet, +}) => + "${_basePaynymDerivePath(testnet: testnet)}/0/$index"; + +mixin PaynymWalletInterface { + // passed in wallet data + late final String _walletId; + late final String _walletName; + late final btc_dart.NetworkType _network; + late final Coin _coin; + late final MainDB _db; + late final ElectrumX _electrumXClient; + late final SecureStorageInterface _secureStorage; + late final int _dustLimit; + late final int _dustLimitP2PKH; + late final int _minConfirms; + + // passed in wallet functions + late final Future Function() _getMnemonicString; + late final Future Function() _getMnemonicPassphrase; + late final Future Function() _getChainHeight; + late final Future Function() _getCurrentChangeAddress; + late final int Function({ + required int vSize, + required int feeRatePerKB, + }) _estimateTxFee; + late final Future> Function({ + required String address, + required Amount amount, + Map? args, + }) _prepareSend; + late final Future Function({ + required String address, + }) _getTxCount; + late final Future> Function( + List utxosToUse, + ) _fetchBuildTxData; + late final Future Function() _refresh; + late final Future Function() _checkChangeAddressForTransactions; + + // initializer + void initPaynymWalletInterface({ + required String walletId, + required String walletName, + required btc_dart.NetworkType network, + required Coin coin, + required MainDB db, + required ElectrumX electrumXClient, + required SecureStorageInterface secureStorage, + required int dustLimit, + required int dustLimitP2PKH, + required int minConfirms, + required Future Function() getMnemonicString, + required Future Function() getMnemonicPassphrase, + required Future Function() getChainHeight, + required Future Function() getCurrentChangeAddress, + required int Function({ + required int vSize, + required int feeRatePerKB, + }) + estimateTxFee, + required Future> Function({ + required String address, + required Amount amount, + Map? args, + }) + prepareSend, + required Future Function({ + required String address, + }) + getTxCount, + required Future> Function( + List utxosToUse, + ) + fetchBuildTxData, + required Future Function() refresh, + required Future Function() checkChangeAddressForTransactions, + }) { + _walletId = walletId; + _walletName = walletName; + _network = network; + _coin = coin; + _db = db; + _electrumXClient = electrumXClient; + _secureStorage = secureStorage; + _dustLimit = dustLimit; + _dustLimitP2PKH = dustLimitP2PKH; + _minConfirms = minConfirms; + _getMnemonicString = getMnemonicString; + _getMnemonicPassphrase = getMnemonicPassphrase; + _getChainHeight = getChainHeight; + _getCurrentChangeAddress = getCurrentChangeAddress; + _estimateTxFee = estimateTxFee; + _prepareSend = prepareSend; + _getTxCount = getTxCount; + _fetchBuildTxData = fetchBuildTxData; + _refresh = refresh; + _checkChangeAddressForTransactions = checkChangeAddressForTransactions; + } + + // convenience getter + btc_dart.NetworkType get networkType => _network; + + Future getBip47BaseNode() async { + final root = await _getRootNode(); + final node = root.derivePath( + _basePaynymDerivePath( + testnet: _coin.isTestNet, + ), + ); + return node; + } + + Future getPrivateKeyForPaynymReceivingAddress({ + required String paymentCodeString, + required int index, + }) async { + final bip47base = await getBip47BaseNode(); + + final paymentAddress = PaymentAddress( + bip32Node: bip47base.derive(index), + paymentCode: PaymentCode.fromPaymentCode( + paymentCodeString, + networkType: networkType, + ), + networkType: networkType, + index: 0, + ); + + final pair = paymentAddress.getReceiveAddressKeyPair(); + + return pair.privateKey!; + } + + Future
currentReceivingPaynymAddress({ + required PaymentCode sender, + required bool isSegwit, + }) async { + final keys = await lookupKey(sender.toString()); + + final address = await _db + .getAddresses(_walletId) + .filter() + .subTypeEqualTo(AddressSubType.paynymReceive) + .and() + .group((q) { + if (isSegwit) { + return q + .typeEqualTo(AddressType.p2sh) + .or() + .typeEqualTo(AddressType.p2wpkh); + } else { + return q.typeEqualTo(AddressType.p2pkh); + } + }) + .and() + .anyOf(keys, (q, String e) => q.otherDataEqualTo(e)) + .sortByDerivationIndexDesc() + .findFirst(); + + if (address == null) { + final generatedAddress = await _generatePaynymReceivingAddress( + sender: sender, + index: 0, + generateSegwitAddress: isSegwit, + ); + + final existing = await _db + .getAddresses(_walletId) + .filter() + .valueEqualTo(generatedAddress.value) + .findFirst(); + + if (existing == null) { + // Add that new address + await _db.putAddress(generatedAddress); + } else { + // we need to update the address + await _db.updateAddress(existing, generatedAddress); + } + + return currentReceivingPaynymAddress( + isSegwit: isSegwit, + sender: sender, + ); + } else { + return address; + } + } + + Future
_generatePaynymReceivingAddress({ + required PaymentCode sender, + required int index, + required bool generateSegwitAddress, + }) async { + final root = await _getRootNode(); + final node = root.derivePath( + _basePaynymDerivePath( + testnet: _coin.isTestNet, + ), + ); + + final paymentAddress = PaymentAddress( + bip32Node: node.derive(index), + paymentCode: sender, + networkType: networkType, + index: 0, + ); + + final addressString = generateSegwitAddress + ? paymentAddress.getReceiveAddressP2WPKH() + : paymentAddress.getReceiveAddressP2PKH(); + + final address = Address( + walletId: _walletId, + value: addressString, + publicKey: [], + derivationIndex: index, + derivationPath: DerivationPath() + ..value = _receivingPaynymAddressDerivationPath( + index, + testnet: _coin.isTestNet, + ), + type: generateSegwitAddress ? AddressType.p2wpkh : AddressType.p2pkh, + subType: AddressSubType.paynymReceive, + otherData: await storeCode(sender.toString()), + ); + + return address; + } + + Future
_generatePaynymSendAddress({ + required PaymentCode other, + required int index, + required bool generateSegwitAddress, + bip32.BIP32? mySendBip32Node, + }) async { + final node = mySendBip32Node ?? await deriveNotificationBip32Node(); + + final paymentAddress = PaymentAddress( + bip32Node: node, + paymentCode: other, + networkType: networkType, + index: index, + ); + + final addressString = generateSegwitAddress + ? paymentAddress.getSendAddressP2WPKH() + : paymentAddress.getSendAddressP2PKH(); + + final address = Address( + walletId: _walletId, + value: addressString, + publicKey: [], + derivationIndex: index, + derivationPath: DerivationPath() + ..value = _sendPaynymAddressDerivationPath( + index, + testnet: _coin.isTestNet, + ), + type: AddressType.nonWallet, + subType: AddressSubType.paynymSend, + otherData: await storeCode(other.toString()), + ); + + return address; + } + + Future checkCurrentPaynymReceivingAddressForTransactions({ + required PaymentCode sender, + required bool isSegwit, + }) async { + final address = await currentReceivingPaynymAddress( + sender: sender, + isSegwit: isSegwit, + ); + + final txCount = await _getTxCount(address: address.value); + if (txCount > 0) { + // generate next address and add to db + final nextAddress = await _generatePaynymReceivingAddress( + sender: sender, + index: address.derivationIndex + 1, + generateSegwitAddress: isSegwit, + ); + + final existing = await _db + .getAddresses(_walletId) + .filter() + .valueEqualTo(nextAddress.value) + .findFirst(); + + if (existing == null) { + // Add that new address + await _db.putAddress(nextAddress); + } else { + // we need to update the address + await _db.updateAddress(existing, nextAddress); + } + // keep checking until address with no tx history is set as current + await checkCurrentPaynymReceivingAddressForTransactions( + sender: sender, + isSegwit: isSegwit, + ); + } + } + + Future checkAllCurrentReceivingPaynymAddressesForTransactions() async { + final codes = await getAllPaymentCodesFromNotificationTransactions(); + final List> futures = []; + for (final code in codes) { + futures.add(checkCurrentPaynymReceivingAddressForTransactions( + sender: code, + isSegwit: true, + )); + futures.add(checkCurrentPaynymReceivingAddressForTransactions( + sender: code, + isSegwit: false, + )); + } + await Future.wait(futures); + } + + // generate bip32 payment code root + Future _getRootNode() async { + return _cachedRootNode ??= await Bip32Utils.getBip32Root( + (await _getMnemonicString())!, + (await _getMnemonicPassphrase())!, + _network, + ); + } + + bip32.BIP32? _cachedRootNode; + + Future deriveNotificationBip32Node() async { + final root = await _getRootNode(); + final node = root + .derivePath( + _basePaynymDerivePath( + testnet: _coin.isTestNet, + ), + ) + .derive(0); + return node; + } + + /// fetch or generate this wallet's bip47 payment code + Future getPaymentCode({ + required bool isSegwit, + }) async { + final node = await _getRootNode(); + + final paymentCode = PaymentCode.fromBip32Node( + node.derivePath(_basePaynymDerivePath(testnet: _coin.isTestNet)), + networkType: networkType, + shouldSetSegwitBit: isSegwit, + ); + + return paymentCode; + } + + Future signWithNotificationKey(Uint8List data) async { + final myPrivateKeyNode = await deriveNotificationBip32Node(); + final pair = btc_dart.ECPair.fromPrivateKey(myPrivateKeyNode.privateKey!, + network: _network); + final signed = pair.sign(SHA256Digest().process(data)); + return signed; + } + + Future signStringWithNotificationKey(String data) async { + final bytes = + await signWithNotificationKey(Uint8List.fromList(utf8.encode(data))); + return Format.uint8listToString(bytes); + } + + Future> preparePaymentCodeSend({ + required PaymentCode paymentCode, + required bool isSegwit, + required Amount amount, + Map? args, + }) async { + if (!(await hasConnected(paymentCode.toString()))) { + throw PaynymSendException( + "No notification transaction sent to $paymentCode"); + } else { + final myPrivateKeyNode = await deriveNotificationBip32Node(); + final sendToAddress = await nextUnusedSendAddressFrom( + pCode: paymentCode, + privateKeyNode: myPrivateKeyNode, + isSegwit: isSegwit, + ); + + return _prepareSend( + address: sendToAddress.value, + amount: amount, + args: args, + ); + } + } + + /// get the next unused address to send to given the receiver's payment code + /// and your own private key + Future
nextUnusedSendAddressFrom({ + required PaymentCode pCode, + required bool isSegwit, + required bip32.BIP32 privateKeyNode, + int startIndex = 0, + }) async { + // https://en.bitcoin.it/wiki/BIP_0047#Path_levels + const maxCount = 2147483647; + + for (int i = startIndex; i < maxCount; i++) { + final keys = await lookupKey(pCode.toString()); + final address = await _db + .getAddresses(_walletId) + .filter() + .subTypeEqualTo(AddressSubType.paynymSend) + .and() + .anyOf(keys, (q, String e) => q.otherDataEqualTo(e)) + .and() + .derivationIndexEqualTo(i) + .findFirst(); + + if (address != null) { + final count = await _getTxCount(address: address.value); + // return address if unused, otherwise continue to next index + if (count == 0) { + return address; + } + } else { + final address = await _generatePaynymSendAddress( + other: pCode, + index: i, + generateSegwitAddress: isSegwit, + mySendBip32Node: privateKeyNode, + ); + + final storedAddress = await _db.getAddress(_walletId, address.value); + if (storedAddress == null) { + await _db.putAddress(address); + } else { + await _db.updateAddress(storedAddress, address); + } + final count = await _getTxCount(address: address.value); + // return address if unused, otherwise continue to next index + if (count == 0) { + return address; + } + } + } + + throw PaynymSendException("Exhausted unused send addresses!"); + } + + Future> prepareNotificationTx({ + required int selectedTxFeeRate, + required String targetPaymentCodeString, + int additionalOutputs = 0, + List? utxos, + }) async { + try { + final amountToSend = _dustLimitP2PKH; + final List availableOutputs = + utxos ?? await _db.getUTXOs(_walletId).findAll(); + final List spendableOutputs = []; + int spendableSatoshiValue = 0; + + // Build list of spendable outputs and totaling their satoshi amount + for (var i = 0; i < availableOutputs.length; i++) { + if (availableOutputs[i].isBlocked == false && + availableOutputs[i] + .isConfirmed(await _getChainHeight(), _minConfirms) == + true) { + spendableOutputs.add(availableOutputs[i]); + spendableSatoshiValue += availableOutputs[i].value; + } + } + + if (spendableSatoshiValue < amountToSend) { + // insufficient balance + throw InsufficientBalanceException( + "Spendable balance is less than the minimum required for a notification transaction."); + } else if (spendableSatoshiValue == amountToSend) { + // insufficient balance due to missing amount to cover fee + throw InsufficientBalanceException( + "Remaining balance does not cover the network fee."); + } + + // sort spendable by age (oldest first) + spendableOutputs.sort((a, b) => b.blockTime!.compareTo(a.blockTime!)); + + int satoshisBeingUsed = 0; + int outputsBeingUsed = 0; + List utxoObjectsToUse = []; + + for (int i = 0; + satoshisBeingUsed < amountToSend && i < spendableOutputs.length; + i++) { + utxoObjectsToUse.add(spendableOutputs[i]); + satoshisBeingUsed += spendableOutputs[i].value; + outputsBeingUsed += 1; + } + + // add additional outputs if required + for (int i = 0; + i < additionalOutputs && outputsBeingUsed < spendableOutputs.length; + i++) { + utxoObjectsToUse.add(spendableOutputs[outputsBeingUsed]); + satoshisBeingUsed += spendableOutputs[outputsBeingUsed].value; + outputsBeingUsed += 1; + } + + // gather required signing data + final utxoSigningData = await _fetchBuildTxData(utxoObjectsToUse); + + final int vSizeForNoChange = (await _createNotificationTx( + targetPaymentCodeString: targetPaymentCodeString, + utxoSigningData: utxoSigningData, + change: 0, + // override amount to get around absurd fees error + overrideAmountForTesting: satoshisBeingUsed, + )) + .item2; + + final int vSizeForWithChange = (await _createNotificationTx( + targetPaymentCodeString: targetPaymentCodeString, + utxoSigningData: utxoSigningData, + change: satoshisBeingUsed - amountToSend, + )) + .item2; + + // Assume 2 outputs, for recipient and payment code script + int feeForNoChange = _estimateTxFee( + vSize: vSizeForNoChange, + feeRatePerKB: selectedTxFeeRate, + ); + + // Assume 3 outputs, for recipient, payment code script, and change + int feeForWithChange = _estimateTxFee( + vSize: vSizeForWithChange, + feeRatePerKB: selectedTxFeeRate, + ); + + if (_coin == Coin.dogecoin || _coin == Coin.dogecoinTestNet) { + if (feeForNoChange < vSizeForNoChange * 1000) { + feeForNoChange = vSizeForNoChange * 1000; + } + if (feeForWithChange < vSizeForWithChange * 1000) { + feeForWithChange = vSizeForWithChange * 1000; + } + } + + if (satoshisBeingUsed - amountToSend > feeForNoChange + _dustLimitP2PKH) { + // try to add change output due to "left over" amount being greater than + // the estimated fee + the dust limit + int changeAmount = satoshisBeingUsed - amountToSend - feeForWithChange; + + // check estimates are correct and build notification tx + if (changeAmount >= _dustLimitP2PKH && + satoshisBeingUsed - amountToSend - changeAmount == + feeForWithChange) { + var txn = await _createNotificationTx( + targetPaymentCodeString: targetPaymentCodeString, + utxoSigningData: utxoSigningData, + change: changeAmount, + ); + + int feeBeingPaid = satoshisBeingUsed - amountToSend - changeAmount; + + // make sure minimum fee is accurate if that is being used + if (txn.item2 - feeBeingPaid == 1) { + changeAmount -= 1; + feeBeingPaid += 1; + txn = await _createNotificationTx( + targetPaymentCodeString: targetPaymentCodeString, + utxoSigningData: utxoSigningData, + change: changeAmount, + ); + } + + Map transactionObject = { + "hex": txn.item1, + "recipientPaynym": targetPaymentCodeString, + "amount": amountToSend.toAmountAsRaw( + fractionDigits: _coin.decimals, + ), + "fee": feeBeingPaid, + "vSize": txn.item2, + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), + }; + return transactionObject; + } else { + // something broke during fee estimation or the change amount is smaller + // than the dust limit. Try without change + final txn = await _createNotificationTx( + targetPaymentCodeString: targetPaymentCodeString, + utxoSigningData: utxoSigningData, + change: 0, + ); + + int feeBeingPaid = satoshisBeingUsed - amountToSend; + + Map transactionObject = { + "hex": txn.item1, + "recipientPaynym": targetPaymentCodeString, + "amount": + amountToSend.toAmountAsRaw(fractionDigits: _coin.decimals), + "fee": feeBeingPaid, + "vSize": txn.item2, + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), + }; + return transactionObject; + } + } else if (satoshisBeingUsed - amountToSend >= feeForNoChange) { + // since we already checked if we need to add a change output we can just + // build without change here + final txn = await _createNotificationTx( + targetPaymentCodeString: targetPaymentCodeString, + utxoSigningData: utxoSigningData, + change: 0, + ); + + int feeBeingPaid = satoshisBeingUsed - amountToSend; + + Map transactionObject = { + "hex": txn.item1, + "recipientPaynym": targetPaymentCodeString, + "amount": amountToSend.toAmountAsRaw(fractionDigits: _coin.decimals), + "fee": feeBeingPaid, + "vSize": txn.item2, + "usedUTXOs": utxoSigningData.map((e) => e.utxo).toList(), + }; + return transactionObject; + } else { + // if we get here we do not have enough funds to cover the tx total so we + // check if we have any more available outputs and try again + if (spendableOutputs.length > outputsBeingUsed) { + return prepareNotificationTx( + selectedTxFeeRate: selectedTxFeeRate, + targetPaymentCodeString: targetPaymentCodeString, + additionalOutputs: additionalOutputs + 1, + ); + } else { + throw InsufficientBalanceException( + "Remaining balance does not cover the network fee."); + } + } + } catch (e) { + rethrow; + } + } + + // return tuple with string value equal to the raw tx hex and the int value + // equal to its vSize + Future> _createNotificationTx({ + required String targetPaymentCodeString, + required List utxoSigningData, + required int change, + int? overrideAmountForTesting, + }) async { + try { + final targetPaymentCode = PaymentCode.fromPaymentCode( + targetPaymentCodeString, + networkType: _network, + ); + final myCode = await getPaymentCode(isSegwit: false); + + final utxo = utxoSigningData.first.utxo; + final txPoint = utxo.txid.fromHex.reversed.toList(); + final txPointIndex = utxo.vout; + + final rev = Uint8List(txPoint.length + 4); + Util.copyBytes(Uint8List.fromList(txPoint), 0, rev, 0, txPoint.length); + final buffer = rev.buffer.asByteData(); + buffer.setUint32(txPoint.length, txPointIndex, Endian.little); + + final myKeyPair = utxoSigningData.first.keyPair!; + + final S = SecretPoint( + myKeyPair.privateKey!, + targetPaymentCode.notificationPublicKey(), + ); + + final blindingMask = PaymentCode.getMask(S.ecdhSecret(), rev); + + final blindedPaymentCode = PaymentCode.blind( + payload: myCode.getPayload(), + mask: blindingMask, + unBlind: false, + ); + + final opReturnScript = bscript.compile([ + (op.OPS["OP_RETURN"] as int), + blindedPaymentCode, + ]); + + // build a notification tx + final txb = btc_dart.TransactionBuilder(network: _network); + txb.setVersion(1); + + txb.addInput( + utxo.txid, + txPointIndex, + null, + utxoSigningData.first.output!, + ); + + // add rest of possible inputs + for (var i = 1; i < utxoSigningData.length; i++) { + final utxo = utxoSigningData[i].utxo; + txb.addInput( + utxo.txid, + utxo.vout, + null, + utxoSigningData[i].output!, + ); + } + final String notificationAddress = + targetPaymentCode.notificationAddressP2PKH(); + + txb.addOutput( + notificationAddress, + overrideAmountForTesting ?? _dustLimitP2PKH, + ); + txb.addOutput(opReturnScript, 0); + + // TODO: add possible change output and mark output as dangerous + if (change > 0) { + // generate new change address if current change address has been used + await _checkChangeAddressForTransactions(); + final String changeAddress = await _getCurrentChangeAddress(); + txb.addOutput(changeAddress, change); + } + + txb.sign( + vin: 0, + keyPair: myKeyPair, + witnessValue: utxo.value, + witnessScript: utxoSigningData.first.redeemScript, + ); + + // sign rest of possible inputs + for (var i = 1; i < utxoSigningData.length; i++) { + txb.sign( + vin: i, + keyPair: utxoSigningData[i].keyPair!, + witnessValue: utxoSigningData[i].utxo.value, + witnessScript: utxoSigningData[i].redeemScript, + ); + } + + final builtTx = txb.build(); + + return Tuple2(builtTx.toHex(), builtTx.virtualSize()); + } catch (e, s) { + Logging.instance.log( + "_createNotificationTx(): $e\n$s", + level: LogLevel.Error, + ); + rethrow; + } + } + + Future broadcastNotificationTx({ + required Map preparedTx, + }) async { + try { + Logging.instance.log("confirmNotificationTx txData: $preparedTx", + level: LogLevel.Info); + final txHash = await _electrumXClient.broadcastTransaction( + rawTx: preparedTx["hex"] as String); + Logging.instance.log("Sent txHash: $txHash", level: LogLevel.Info); + + // TODO: only refresh transaction data + try { + await _refresh(); + } catch (e) { + Logging.instance.log( + "refresh() failed in confirmNotificationTx ($_walletName::$_walletId): $e", + level: LogLevel.Error, + ); + } + + return txHash; + } catch (e, s) { + Logging.instance.log("Exception rethrown from confirmSend(): $e\n$s", + level: LogLevel.Error); + rethrow; + } + } + + // Future _checkHasConnectedCache(String paymentCodeString) async { + // final value = await _secureStorage.read( + // key: "$_connectedKeyPrefix$paymentCodeString"); + // if (value == null) { + // return null; + // } else { + // final int rawBool = int.parse(value); + // return rawBool > 0; + // } + // } + // + // Future _setConnectedCache( + // String paymentCodeString, bool hasConnected) async { + // await _secureStorage.write( + // key: "$_connectedKeyPrefix$paymentCodeString", + // value: hasConnected ? "1" : "0"); + // } + + // TODO optimize + Future hasConnected(String paymentCodeString) async { + // final didConnect = await _checkHasConnectedCache(paymentCodeString); + // if (didConnect == true) { + // return true; + // } + // + // final keys = await lookupKey(paymentCodeString); + // + // final tx = await _db + // .getTransactions(_walletId) + // .filter() + // .subTypeEqualTo(TransactionSubType.bip47Notification).and() + // .address((q) => + // q.anyOf(keys, (q, e) => q.otherDataEqualTo(e))) + // .findAll(); + + final myNotificationAddress = await getMyNotificationAddress(); + + final txns = await _db + .getTransactions(_walletId) + .filter() + .subTypeEqualTo(TransactionSubType.bip47Notification) + .findAll(); + + for (final tx in txns) { + if (tx.type == TransactionType.incoming && + tx.address.value?.value == myNotificationAddress.value) { + final unBlindedPaymentCode = await unBlindedPaymentCodeFromTransaction( + transaction: tx, + ); + + if (unBlindedPaymentCode != null && + paymentCodeString == unBlindedPaymentCode.toString()) { + // await _setConnectedCache(paymentCodeString, true); + return true; + } + + final unBlindedPaymentCodeBad = + await unBlindedPaymentCodeFromTransactionBad( + transaction: tx, + ); + + if (unBlindedPaymentCodeBad != null && + paymentCodeString == unBlindedPaymentCodeBad.toString()) { + // await _setConnectedCache(paymentCodeString, true); + return true; + } + } else if (tx.type == TransactionType.outgoing) { + if (tx.address.value?.otherData != null) { + final code = + await paymentCodeStringByKey(tx.address.value!.otherData!); + if (code == paymentCodeString) { + // await _setConnectedCache(paymentCodeString, true); + return true; + } + } + } + } + + // otherwise return no + // await _setConnectedCache(paymentCodeString, false); + return false; + } + + Uint8List? _pubKeyFromInput(Input input) { + final scriptSigComponents = input.scriptSigAsm?.split(" ") ?? []; + if (scriptSigComponents.length > 1) { + return scriptSigComponents[1].fromHex; + } + if (input.witness != null) { + try { + final witnessComponents = jsonDecode(input.witness!) as List; + if (witnessComponents.length == 2) { + return (witnessComponents[1] as String).fromHex; + } + } catch (_) { + // + } + } + return null; + } + + Future unBlindedPaymentCodeFromTransaction({ + required Transaction transaction, + }) async { + try { + final blindedCodeBytes = + Bip47Utils.getBlindedPaymentCodeBytesFrom(transaction); + + // transaction does not contain a payment code + if (blindedCodeBytes == null) { + return null; + } + + final designatedInput = transaction.inputs.first; + + final txPoint = designatedInput.txid.fromHex.reversed.toList(); + final txPointIndex = designatedInput.vout; + + final rev = Uint8List(txPoint.length + 4); + Util.copyBytes(Uint8List.fromList(txPoint), 0, rev, 0, txPoint.length); + final buffer = rev.buffer.asByteData(); + buffer.setUint32(txPoint.length, txPointIndex, Endian.little); + + final pubKey = _pubKeyFromInput(designatedInput)!; + + final myPrivateKey = (await deriveNotificationBip32Node()).privateKey!; + + final S = SecretPoint(myPrivateKey, pubKey); + + final mask = PaymentCode.getMask(S.ecdhSecret(), rev); + + final unBlindedPayload = PaymentCode.blind( + payload: blindedCodeBytes, + mask: mask, + unBlind: true, + ); + + final unBlindedPaymentCode = PaymentCode.fromPayload( + unBlindedPayload, + networkType: _network, + ); + + return unBlindedPaymentCode; + } catch (e) { + Logging.instance.log( + "unBlindedPaymentCodeFromTransaction() failed: $e\nFor tx: $transaction", + level: LogLevel.Warning, + ); + return null; + } + } + + Future unBlindedPaymentCodeFromTransactionBad({ + required Transaction transaction, + }) async { + try { + final blindedCodeBytes = + Bip47Utils.getBlindedPaymentCodeBytesFrom(transaction); + + // transaction does not contain a payment code + if (blindedCodeBytes == null) { + return null; + } + + final designatedInput = transaction.inputs.first; + + final txPoint = designatedInput.txid.fromHex.toList(); + final txPointIndex = designatedInput.vout; + + final rev = Uint8List(txPoint.length + 4); + Util.copyBytes(Uint8List.fromList(txPoint), 0, rev, 0, txPoint.length); + final buffer = rev.buffer.asByteData(); + buffer.setUint32(txPoint.length, txPointIndex, Endian.little); + + final pubKey = _pubKeyFromInput(designatedInput)!; + + final myPrivateKey = (await deriveNotificationBip32Node()).privateKey!; + + final S = SecretPoint(myPrivateKey, pubKey); + + final mask = PaymentCode.getMask(S.ecdhSecret(), rev); + + final unBlindedPayload = PaymentCode.blind( + payload: blindedCodeBytes, + mask: mask, + unBlind: true, + ); + + final unBlindedPaymentCode = PaymentCode.fromPayload( + unBlindedPayload, + networkType: _network, + ); + + return unBlindedPaymentCode; + } catch (e) { + Logging.instance.log( + "unBlindedPaymentCodeFromTransactionBad() failed: $e\nFor tx: $transaction", + level: LogLevel.Warning, + ); + return null; + } + } + + Future> + getAllPaymentCodesFromNotificationTransactions() async { + final txns = await _db + .getTransactions(_walletId) + .filter() + .subTypeEqualTo(TransactionSubType.bip47Notification) + .findAll(); + + List codes = []; + + for (final tx in txns) { + // tx is sent so we can check the address's otherData for the code String + if (tx.type == TransactionType.outgoing && + tx.address.value?.otherData != null) { + final codeString = + await paymentCodeStringByKey(tx.address.value!.otherData!); + if (codeString != null && + codes.where((e) => e.toString() == codeString).isEmpty) { + codes.add( + PaymentCode.fromPaymentCode( + codeString, + networkType: _network, + ), + ); + } + } else { + // otherwise we need to un blind the code + final unBlinded = await unBlindedPaymentCodeFromTransaction( + transaction: tx, + ); + if (unBlinded != null && + codes.where((e) => e.toString() == unBlinded.toString()).isEmpty) { + codes.add(unBlinded); + } + + final unBlindedBad = await unBlindedPaymentCodeFromTransactionBad( + transaction: tx, + ); + if (unBlindedBad != null && + codes + .where((e) => e.toString() == unBlindedBad.toString()) + .isEmpty) { + codes.add(unBlindedBad); + } + } + } + + return codes; + } + + Future checkForNotificationTransactionsTo( + Set otherCodeStrings) async { + final sentNotificationTransactions = await _db + .getTransactions(_walletId) + .filter() + .subTypeEqualTo(TransactionSubType.bip47Notification) + .and() + .typeEqualTo(TransactionType.outgoing) + .findAll(); + + final List codes = []; + for (final codeString in otherCodeStrings) { + codes.add(PaymentCode.fromPaymentCode(codeString, networkType: _network)); + } + + for (final tx in sentNotificationTransactions) { + if (tx.address.value != null && tx.address.value!.otherData == null) { + final oldAddress = + await _db.getAddress(_walletId, tx.address.value!.value); + for (final code in codes) { + final notificationAddress = code.notificationAddressP2PKH(); + if (notificationAddress == oldAddress!.value) { + final address = Address( + walletId: _walletId, + value: notificationAddress, + publicKey: [], + derivationIndex: 0, + derivationPath: oldAddress.derivationPath, + type: oldAddress.type, + subType: AddressSubType.paynymNotification, + otherData: await storeCode(code.toString()), + ); + await _db.updateAddress(oldAddress, address); + } + } + } + } + } + + Future restoreAllHistory({ + required int maxUnusedAddressGap, + required int maxNumberOfIndexesToCheck, + required Set paymentCodeStrings, + }) async { + final codes = await getAllPaymentCodesFromNotificationTransactions(); + final List extraCodes = []; + for (final codeString in paymentCodeStrings) { + if (codes.where((e) => e.toString() == codeString).isEmpty) { + final extraCode = PaymentCode.fromPaymentCode( + codeString, + networkType: _network, + ); + if (extraCode.isValid()) { + extraCodes.add(extraCode); + } + } + } + + codes.addAll(extraCodes); + + final List> futures = []; + for (final code in codes) { + futures.add( + restoreHistoryWith( + other: code, + maxUnusedAddressGap: maxUnusedAddressGap, + maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + checkSegwitAsWell: code.isSegWitEnabled(), + ), + ); + } + + await Future.wait(futures); + } + + Future restoreHistoryWith({ + required PaymentCode other, + required bool checkSegwitAsWell, + required int maxUnusedAddressGap, + required int maxNumberOfIndexesToCheck, + }) async { + // https://en.bitcoin.it/wiki/BIP_0047#Path_levels + const maxCount = 2147483647; + assert(maxNumberOfIndexesToCheck < maxCount); + + final mySendBip32Node = await deriveNotificationBip32Node(); + + List
addresses = []; + int receivingGapCounter = 0; + int outgoingGapCounter = 0; + + // non segwit receiving + for (int i = 0; + i < maxNumberOfIndexesToCheck && + receivingGapCounter < maxUnusedAddressGap; + i++) { + if (receivingGapCounter < maxUnusedAddressGap) { + final address = await _generatePaynymReceivingAddress( + sender: other, + index: i, + generateSegwitAddress: false, + ); + + addresses.add(address); + + final count = await _getTxCount(address: address.value); + + if (count > 0) { + receivingGapCounter = 0; + } else { + receivingGapCounter++; + } + } + } + + // non segwit sends + for (int i = 0; + i < maxNumberOfIndexesToCheck && + outgoingGapCounter < maxUnusedAddressGap; + i++) { + if (outgoingGapCounter < maxUnusedAddressGap) { + final address = await _generatePaynymSendAddress( + other: other, + index: i, + generateSegwitAddress: false, + mySendBip32Node: mySendBip32Node, + ); + + addresses.add(address); + + final count = await _getTxCount(address: address.value); + + if (count > 0) { + outgoingGapCounter = 0; + } else { + outgoingGapCounter++; + } + } + } + + if (checkSegwitAsWell) { + int receivingGapCounterSegwit = 0; + int outgoingGapCounterSegwit = 0; + // segwit receiving + for (int i = 0; + i < maxNumberOfIndexesToCheck && + receivingGapCounterSegwit < maxUnusedAddressGap; + i++) { + if (receivingGapCounterSegwit < maxUnusedAddressGap) { + final address = await _generatePaynymReceivingAddress( + sender: other, + index: i, + generateSegwitAddress: true, + ); + + addresses.add(address); + + final count = await _getTxCount(address: address.value); + + if (count > 0) { + receivingGapCounterSegwit = 0; + } else { + receivingGapCounterSegwit++; + } + } + } + + // segwit sends + for (int i = 0; + i < maxNumberOfIndexesToCheck && + outgoingGapCounterSegwit < maxUnusedAddressGap; + i++) { + if (outgoingGapCounterSegwit < maxUnusedAddressGap) { + final address = await _generatePaynymSendAddress( + other: other, + index: i, + generateSegwitAddress: true, + mySendBip32Node: mySendBip32Node, + ); + + addresses.add(address); + + final count = await _getTxCount(address: address.value); + + if (count > 0) { + outgoingGapCounterSegwit = 0; + } else { + outgoingGapCounterSegwit++; + } + } + } + } + await _db.updateOrPutAddresses(addresses); + } + + Future
getMyNotificationAddress() async { + final storedAddress = await _db + .getAddresses(_walletId) + .filter() + .subTypeEqualTo(AddressSubType.paynymNotification) + .and() + .typeEqualTo(AddressType.p2pkh) + .and() + .not() + .typeEqualTo(AddressType.nonWallet) + .findFirst(); + + if (storedAddress != null) { + return storedAddress; + } else { + final root = await _getRootNode(); + final node = root.derivePath( + _basePaynymDerivePath( + testnet: _coin.isTestNet, + ), + ); + final paymentCode = PaymentCode.fromBip32Node( + node, + networkType: _network, + shouldSetSegwitBit: false, + ); + + final data = btc_dart.PaymentData( + pubkey: paymentCode.notificationPublicKey(), + ); + + final addressString = btc_dart + .P2PKH( + data: data, + network: _network, + ) + .data + .address!; + + Address address = Address( + walletId: _walletId, + value: addressString, + publicKey: paymentCode.getPubKey(), + derivationIndex: 0, + derivationPath: DerivationPath() + ..value = _notificationDerivationPath( + testnet: _coin.isTestNet, + ), + type: AddressType.p2pkh, + subType: AddressSubType.paynymNotification, + otherData: await storeCode(paymentCode.toString()), + ); + + // check against possible race condition. Ff this function was called + // multiple times an address could've been saved after the check at the + // beginning to see if there already was notification address. This would + // lead to a Unique Index violation error + await _db.isar.writeTxn(() async { + final storedAddress = await _db + .getAddresses(_walletId) + .filter() + .subTypeEqualTo(AddressSubType.paynymNotification) + .and() + .typeEqualTo(AddressType.p2pkh) + .and() + .not() + .typeEqualTo(AddressType.nonWallet) + .findFirst(); + + if (storedAddress == null) { + await _db.isar.addresses.put(address); + } else { + address = storedAddress; + } + }); + + return address; + } + } + + /// look up a key that corresponds to a payment code string + Future> lookupKey(String paymentCodeString) async { + final keys = + (await _secureStorage.keys).where((e) => e.startsWith(kPCodeKeyPrefix)); + final List result = []; + for (final key in keys) { + final value = await _secureStorage.read(key: key); + if (value == paymentCodeString) { + result.add(key); + } + } + return result; + } + + /// fetch a payment code string + Future paymentCodeStringByKey(String key) async { + final value = await _secureStorage.read(key: key); + return value; + } + + /// store payment code string and return the generated key used + Future storeCode(String paymentCodeString) async { + final key = _generateKey(); + await _secureStorage.write(key: key, value: paymentCodeString); + return key; + } + + /// generate a new payment code string storage key + String _generateKey() { + final bytes = _randomBytes(24); + return "$kPCodeKeyPrefix${bytes.toHex}"; + } + + // https://github.com/AaronFeickert/stack_wallet_backup/blob/master/lib/secure_storage.dart#L307-L311 + /// Generate cryptographically-secure random bytes + Uint8List _randomBytes(int n) { + final Random rng = Random.secure(); + return Uint8List.fromList( + List.generate(n, (_) => rng.nextInt(0xFF + 1))); + } +} diff --git a/lib/services/mixins/wallet_cache.dart b/lib/services/mixins/wallet_cache.dart new file mode 100644 index 000000000..435e28717 --- /dev/null +++ b/lib/services/mixins/wallet_cache.dart @@ -0,0 +1,138 @@ +import 'package:stackwallet/db/hive/db.dart'; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; + +mixin WalletCache { + late final String _walletId; + late final Coin _coin; + + void initCache(String walletId, Coin coin) { + _walletId = walletId; + _coin = coin; + } + + // cached wallet id + String? getCachedId() { + return DB.instance.get( + boxName: _walletId, + key: DBKeys.id, + ) as String?; + } + + Future updateCachedId(String? id) async { + await DB.instance.put( + boxName: _walletId, + key: DBKeys.id, + value: id, + ); + } + + // cached Chain Height + int getCachedChainHeight() { + return DB.instance.get( + boxName: _walletId, + key: DBKeys.storedChainHeight, + ) as int? ?? + 0; + } + + Future updateCachedChainHeight(int height) async { + await DB.instance.put( + boxName: _walletId, + key: DBKeys.storedChainHeight, + value: height, + ); + } + + // wallet favorite flag + bool getCachedIsFavorite() { + return DB.instance.get( + boxName: _walletId, + key: DBKeys.isFavorite, + ) as bool? ?? + false; + } + + Future updateCachedIsFavorite(bool isFavorite) async { + await DB.instance.put( + boxName: _walletId, + key: DBKeys.isFavorite, + value: isFavorite, + ); + } + + // main balance cache + Balance getCachedBalance() { + final jsonString = DB.instance.get( + boxName: _walletId, + key: DBKeys.cachedBalance, + ) as String?; + if (jsonString == null) { + return Balance( + total: Amount(rawValue: BigInt.zero, fractionDigits: _coin.decimals), + spendable: + Amount(rawValue: BigInt.zero, fractionDigits: _coin.decimals), + blockedTotal: + Amount(rawValue: BigInt.zero, fractionDigits: _coin.decimals), + pendingSpendable: + Amount(rawValue: BigInt.zero, fractionDigits: _coin.decimals), + ); + } + return Balance.fromJson(jsonString, _coin.decimals); + } + + Future updateCachedBalance(Balance balance) async { + await DB.instance.put( + boxName: _walletId, + key: DBKeys.cachedBalance, + value: balance.toJsonIgnoreCoin(), + ); + } + + // secondary balance cache for coins such as firo + Balance getCachedBalanceSecondary() { + final jsonString = DB.instance.get( + boxName: _walletId, + key: DBKeys.cachedBalanceSecondary, + ) as String?; + if (jsonString == null) { + return Balance( + total: Amount(rawValue: BigInt.zero, fractionDigits: _coin.decimals), + spendable: + Amount(rawValue: BigInt.zero, fractionDigits: _coin.decimals), + blockedTotal: + Amount(rawValue: BigInt.zero, fractionDigits: _coin.decimals), + pendingSpendable: + Amount(rawValue: BigInt.zero, fractionDigits: _coin.decimals), + ); + } + return Balance.fromJson(jsonString, _coin.decimals); + } + + Future updateCachedBalanceSecondary(Balance balance) async { + await DB.instance.put( + boxName: _walletId, + key: DBKeys.cachedBalanceSecondary, + value: balance.toJsonIgnoreCoin(), + ); + } + + // Ethereum specific + List getWalletTokenContractAddresses() { + return DB.instance.get( + boxName: _walletId, + key: DBKeys.ethTokenContracts, + ) as List? ?? + []; + } + + Future updateWalletTokenContractAddresses( + List contractAddresses) async { + await DB.instance.put( + boxName: _walletId, + key: DBKeys.ethTokenContracts, + value: contractAddresses, + ); + } +} diff --git a/lib/services/mixins/wallet_db.dart b/lib/services/mixins/wallet_db.dart new file mode 100644 index 000000000..bb46be7d1 --- /dev/null +++ b/lib/services/mixins/wallet_db.dart @@ -0,0 +1,10 @@ +import 'package:stackwallet/db/isar/main_db.dart'; + +mixin WalletDB { + MainDB? _db; + MainDB get db => _db!; + + void initWalletDB({MainDB? mockableOverride}) async { + _db = mockableOverride ?? MainDB.instance; + } +} diff --git a/lib/services/mixins/xpubable.dart b/lib/services/mixins/xpubable.dart new file mode 100644 index 000000000..bd3d74d60 --- /dev/null +++ b/lib/services/mixins/xpubable.dart @@ -0,0 +1,3 @@ +mixin XPubAble { + Future get xpub; +} diff --git a/lib/services/node_service.dart b/lib/services/node_service.dart index ca1aae082..fc588fa6a 100644 --- a/lib/services/node_service.dart +++ b/lib/services/node_service.dart @@ -2,7 +2,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart'; -import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/utilities/default_nodes.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; @@ -33,11 +33,15 @@ class NodeService extends ChangeNotifier { ); } } else { - // update all fields but copy over previously set enabled state + // update all fields but copy over previously set enabled and trusted states await DB.instance.put( boxName: DB.boxNameNodeModels, key: savedNode.id, - value: defaultNode.copyWith(enabled: savedNode.enabled)); + value: defaultNode.copyWith( + enabled: savedNode.enabled, + isFailover: savedNode.isFailover, + trusted: savedNode.trusted, + )); } // check if a default node is the primary node for the crypto currency @@ -49,6 +53,8 @@ class NodeService extends ChangeNotifier { coin: coin, node: defaultNode.copyWith( enabled: primaryNode.enabled, + isFailover: primaryNode.isFailover, + trusted: primaryNode.trusted, ), ); } @@ -161,6 +167,17 @@ class NodeService extends ChangeNotifier { String? password, bool shouldNotifyListeners, ) async { + // check if the node being edited is the primary one; if it is, setPrimaryNodeFor coin + final coin = coinFromPrettyName(editedNode.coinName); + var primaryNode = getPrimaryNodeFor(coin: coin); + if (primaryNode?.id == editedNode.id) { + await setPrimaryNodeFor( + coin: coin, + node: editedNode, + shouldNotifyListeners: true, + ); + } + return add(editedNode, password, shouldNotifyListeners); } diff --git a/lib/services/notes_service.dart b/lib/services/notes_service.dart index cffe770e5..eac8b6c22 100644 --- a/lib/services/notes_service.dart +++ b/lib/services/notes_service.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/utilities/logger.dart'; class NotesService extends ChangeNotifier { @@ -51,6 +51,7 @@ class NotesService extends ChangeNotifier { _notes[txid] = note; await DB.instance .put(boxName: walletId, key: 'notes', value: _notes); + //todo: check if this is needed Logging.instance.log("editOrAddNote: tx note saved", level: LogLevel.Info); await _refreshNotes(); } diff --git a/lib/services/notifications_service.dart b/lib/services/notifications_service.dart index 2368adeab..54f6d60ae 100644 --- a/lib/services/notifications_service.dart +++ b/lib/services/notifications_service.dart @@ -1,13 +1,12 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; +import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; -import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/exceptions/electrumx/no_such_transaction.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; import 'package:stackwallet/models/notification_model.dart'; -import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; import 'package:stackwallet/services/exchange/exchange_response.dart'; -import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; import 'package:stackwallet/services/node_service.dart'; import 'package:stackwallet/services/notifications_api.dart'; import 'package:stackwallet/services/trade_service.dart'; @@ -15,6 +14,8 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; +import 'exchange/exchange.dart'; + class NotificationsService extends ChangeNotifier { late NodeService nodeService; late TradesService tradesService; @@ -169,12 +170,14 @@ class NotificationsService extends ChangeNotifier { } // replaces the current notification with the updated one - add(updatedNotification, true); + await add(updatedNotification, true); } } else { // TODO: check non electrumx coins } } + } on NoSuchTransactionException catch (e, s) { + await _deleteWatchedTxNotification(notification); } catch (e, s) { Logging.instance.log("$e $s", level: LogLevel.Error); } @@ -193,15 +196,12 @@ class NotificationsService extends ChangeNotifier { } final oldTrade = trades.first; late final ExchangeResponse response; - switch (oldTrade.exchangeName) { - case SimpleSwapExchange.exchangeName: - response = await SimpleSwapExchange().updateTrade(oldTrade); - break; - case ChangeNowExchange.exchangeName: - response = await ChangeNowExchange().updateTrade(oldTrade); - break; - default: - return; + + try { + final exchange = Exchange.fromName(oldTrade.exchangeName); + response = await exchange.updateTrade(oldTrade); + } catch (_) { + return; } if (response.value == null) { @@ -223,6 +223,9 @@ class NotificationsService extends ChangeNotifier { case "expired": case "Finished": case "finished": + case "Completed": + case "completed": + case "Not found": shouldWatchForUpdates = false; break; default: diff --git a/lib/services/price.dart b/lib/services/price.dart index 43751c141..16bd93c6a 100644 --- a/lib/services/price.dart +++ b/lib/services/price.dart @@ -4,7 +4,7 @@ import 'dart:convert'; import 'package:decimal/decimal.dart'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart'; -import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; @@ -86,10 +86,12 @@ class PriceAPI { } Map> result = {}; try { - final uri = Uri.parse( - "https://api.coingecko.com/api/v3/coins/markets?vs_currency=${baseCurrency.toLowerCase()}&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero,particl&order=market_cap_desc&per_page=10&page=1&sparkline=false"); - // final uri = Uri.parse( - // "https://api.coingecko.com/api/v3/coins/markets?vs_currency=${baseCurrency.toLowerCase()}&ids=monero%2Cbitcoin%2Cepic-cash%2Czcoin%2Cdogecoin&order=market_cap_desc&per_page=10&page=1&sparkline=false"); + final uri = + Uri.parse("https://api.coingecko.com/api/v3/coins/markets?vs_currency" + "=${baseCurrency.toLowerCase()}" + "&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," + "bitcoin-cash,namecoin,wownero,ethereum,particl" + "&order=market_cap_desc&per_page=50&page=1&sparkline=false"); final coinGeckoResponse = await client.get( uri, @@ -146,4 +148,59 @@ class PriceAPI { return null; } } + + Future>> + getPricesAnd24hChangeForEthTokens({ + required Set contractAddresses, + required String baseCurrency, + }) async { + final Map> tokenPrices = {}; + + if (contractAddresses.isEmpty) return tokenPrices; + + final externalCalls = Prefs.instance.externalCalls; + if ((!Logger.isTestEnv && !externalCalls) || + !(await Prefs.instance.isExternalCallsSet())) { + Logging.instance.log("User does not want to use external calls", + level: LogLevel.Info); + return tokenPrices; + } + + try { + final contractAddressesString = + contractAddresses.reduce((value, element) => "$value,$element"); + final uri = Uri.parse( + "https://api.coingecko.com/api/v3/simple/token_price/ethereum" + "?vs_currencies=${baseCurrency.toLowerCase()}&contract_addresses" + "=$contractAddressesString&include_24hr_change=true"); + + final coinGeckoResponse = await client.get( + uri, + headers: {'Content-Type': 'application/json'}, + ); + + final coinGeckoData = jsonDecode(coinGeckoResponse.body) as Map; + + for (final key in coinGeckoData.keys) { + final contractAddress = key as String; + + final map = coinGeckoData[contractAddress] as Map; + + final price = Decimal.parse(map[baseCurrency.toLowerCase()].toString()); + final change24h = double.parse( + map["${baseCurrency.toLowerCase()}_24h_change"].toString()); + + tokenPrices[contractAddress] = Tuple2(price, change24h); + } + + return tokenPrices; + } catch (e, s) { + Logging.instance.log( + "getPricesAnd24hChangeForEthTokens($baseCurrency,$contractAddresses): $e\n$s", + level: LogLevel.Error, + ); + // return previous cached values + return tokenPrices; + } + } } diff --git a/lib/services/price_service.dart b/lib/services/price_service.dart index eb2b1eba4..b699af943 100644 --- a/lib/services/price_service.dart +++ b/lib/services/price_service.dart @@ -9,16 +9,24 @@ import 'package:tuple/tuple.dart'; class PriceService extends ChangeNotifier { late final String baseTicker; + final Set tokenContractAddressesToCheck = {}; final Duration updateInterval = const Duration(seconds: 60); Timer? _timer; final Map> _cachedPrices = { for (final coin in Coin.values) coin: Tuple2(Decimal.zero, 0.0) }; + + final Map> _cachedTokenPrices = {}; + final _priceAPI = PriceAPI(Client()); Tuple2 getPrice(Coin coin) => _cachedPrices[coin]!; + Tuple2 getTokenPrice(String contractAddress) => + _cachedTokenPrices[contractAddress.toLowerCase()] ?? + Tuple2(Decimal.zero, 0); + PriceService(this.baseTicker); Future updatePrice() async { @@ -33,6 +41,20 @@ class PriceService extends ChangeNotifier { } } + if (tokenContractAddressesToCheck.isNotEmpty) { + final tokenPriceMap = await _priceAPI.getPricesAnd24hChangeForEthTokens( + contractAddresses: tokenContractAddressesToCheck, + baseCurrency: baseTicker, + ); + + for (final map in tokenPriceMap.entries) { + if (_cachedTokenPrices[map.key] != map.value) { + _cachedTokenPrices[map.key] = map.value; + shouldNotify = true; + } + } + } + if (shouldNotify) { notifyListeners(); } diff --git a/lib/services/trade_notes_service.dart b/lib/services/trade_notes_service.dart index 763566736..ba5226001 100644 --- a/lib/services/trade_notes_service.dart +++ b/lib/services/trade_notes_service.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/db/hive/db.dart'; class TradeNotesService extends ChangeNotifier { Map get all { diff --git a/lib/services/trade_sent_from_stack_service.dart b/lib/services/trade_sent_from_stack_service.dart index 88c7d7602..20d2b0ffb 100644 --- a/lib/services/trade_sent_from_stack_service.dart +++ b/lib/services/trade_sent_from_stack_service.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/models/trade_wallet_lookup.dart'; class TradeSentFromStackService extends ChangeNotifier { diff --git a/lib/services/trade_service.dart b/lib/services/trade_service.dart index abdcebb4b..7d1a3e00c 100644 --- a/lib/services/trade_service.dart +++ b/lib/services/trade_service.dart @@ -1,5 +1,5 @@ import 'package:flutter/cupertino.dart'; -import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; class TradesService extends ChangeNotifier { diff --git a/lib/services/transaction_notification_tracker.dart b/lib/services/transaction_notification_tracker.dart index 696465e85..732c4a110 100644 --- a/lib/services/transaction_notification_tracker.dart +++ b/lib/services/transaction_notification_tracker.dart @@ -1,4 +1,4 @@ -import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/db/hive/db.dart'; class TransactionNotificationTracker { final String walletId; @@ -54,4 +54,25 @@ class TransactionNotificationTracker { key: "notifiedConfirmedTransactions", value: notifiedConfirmedTransactions); } + + Future deleteTransaction(String txid) async { + final notifiedPendingTransactions = DB.instance.get( + boxName: walletId, key: "notifiedPendingTransactions") as Map? ?? + {}; + final notifiedConfirmedTransactions = DB.instance.get( + boxName: walletId, key: "notifiedConfirmedTransactions") as Map? ?? + {}; + + notifiedPendingTransactions.remove(txid); + notifiedConfirmedTransactions.remove(txid); + + await DB.instance.put( + boxName: walletId, + key: "notifiedConfirmedTransactions", + value: notifiedConfirmedTransactions); + await DB.instance.put( + boxName: walletId, + key: "notifiedPendingTransactions", + value: notifiedPendingTransactions); + } } diff --git a/lib/services/wallets.dart b/lib/services/wallets.dart index faed86f75..ab3886015 100644 --- a/lib/services/wallets.dart +++ b/lib/services/wallets.dart @@ -1,6 +1,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/services/coins/coin_service.dart'; import 'package:stackwallet/services/coins/manager.dart'; @@ -62,15 +62,24 @@ class Wallets extends ChangeNotifier { return result; } - Map>> getManagerProvidersByCoin() { - Map>> result = {}; + List>>> + getManagerProvidersByCoin() { + Map>> map = {}; for (final manager in _managerMap.values) { - if (result[manager.coin] == null) { - result[manager.coin] = []; + if (map[manager.coin] == null) { + map[manager.coin] = []; } - result[manager.coin]!.add(_managerProviderMap[manager.walletId] + map[manager.coin]!.add(_managerProviderMap[manager.walletId] as ChangeNotifierProvider); } + final List>>> result = []; + + for (final coin in map.keys) { + result.add(Tuple2(coin, map[coin]!)); + } + + result.sort((a, b) => a.item1.prettyName.compareTo(b.item1.prettyName)); + return result; } @@ -238,7 +247,7 @@ class Wallets extends ChangeNotifier { walletIdsToEnableAutoSync.contains(manager.walletId); if (manager.coin == Coin.monero || manager.coin == Coin.wownero) { - walletsToInitLinearly.add(Tuple2(manager, shouldSetAutoSync)); + // walletsToInitLinearly.add(Tuple2(manager, shouldSetAutoSync)); } else { walletInitFutures.add(manager.initializeExisting().then((value) { if (shouldSetAutoSync) { @@ -328,7 +337,7 @@ class Wallets extends ChangeNotifier { walletIdsToEnableAutoSync.contains(manager.walletId); if (manager.coin == Coin.monero || manager.coin == Coin.wownero) { - walletsToInitLinearly.add(Tuple2(manager, shouldSetAutoSync)); + // walletsToInitLinearly.add(Tuple2(manager, shouldSetAutoSync)); } else { walletInitFutures.add(manager.initializeExisting().then((value) { if (shouldSetAutoSync) { diff --git a/lib/services/wallets_service.dart b/lib/services/wallets_service.dart index 7bb37d2a6..cbba6bf5a 100644 --- a/lib/services/wallets_service.dart +++ b/lib/services/wallets_service.dart @@ -3,7 +3,8 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:flutter_libmonero/monero/monero.dart'; import 'package:flutter_libmonero/wownero/wownero.dart'; -import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/db/hive/db.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart'; import 'package:stackwallet/services/notifications_service.dart'; import 'package:stackwallet/services/trade_sent_from_stack_service.dart'; @@ -139,6 +140,29 @@ class WalletsService extends ChangeNotifier { name, WalletInfo.fromJson(Map.from(dyn as Map)))); } + Map fetchWalletsData() { + final names = DB.instance.get( + boxName: DB.boxNameAllWalletsData, key: 'names') as Map? ?? + {}; + + Logging.instance.log("Fetched wallet names: $names", level: LogLevel.Info); + final mapped = Map.from(names); + mapped.removeWhere((name, dyn) { + final jsonObject = Map.from(dyn as Map); + try { + Coin.values.byName(jsonObject["coin"] as String); + return false; + } catch (e, s) { + Logging.instance.log("Error, ${jsonObject["coin"]} does not exist", + level: LogLevel.Error); + return true; + } + }); + + return mapped.map((name, dyn) => MapEntry( + name, WalletInfo.fromJson(Map.from(dyn as Map)))); + } + Future addExistingStackWallet({ required String name, required String walletId, @@ -385,6 +409,11 @@ class WalletsService extends ChangeNotifier { level: LogLevel.Info); } + // delete wallet data in main db + await MainDB.instance.deleteWalletBlockchainData(walletId); + await MainDB.instance.deleteAddressLabels(walletId); + await MainDB.instance.deleteTransactionNotes(walletId); + // box data may currently still be read/written to if wallet was refreshing // when delete was requested so instead of deleting now we mark the wallet // as needs delete by adding it's id to a list which gets checked on app start diff --git a/lib/themes/coin_icon_provider.dart b/lib/themes/coin_icon_provider.dart new file mode 100644 index 000000000..6c17969e6 --- /dev/null +++ b/lib/themes/coin_icon_provider.dart @@ -0,0 +1,44 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; + +final coinIconProvider = Provider.family((ref, coin) { + final assets = ref.watch(themeAssetsProvider); + + if (assets is ThemeAssets) { + switch (coin) { + case Coin.bitcoin: + case Coin.bitcoinTestNet: + return assets.bitcoin; + case Coin.litecoin: + case Coin.litecoinTestNet: + return assets.litecoin; + case Coin.bitcoincash: + case Coin.bitcoincashTestnet: + return assets.bitcoincash; + case Coin.dogecoin: + case Coin.dogecoinTestNet: + return assets.dogecoin; + case Coin.eCash: + return assets.bitcoin; + case Coin.epicCash: + return assets.epicCash; + case Coin.firo: + case Coin.firoTestNet: + return assets.firo; + case Coin.monero: + return assets.monero; + case Coin.wownero: + return assets.wownero; + case Coin.namecoin: + return assets.namecoin; + case Coin.particl: + return assets.particl; + case Coin.ethereum: + return assets.ethereum; + } + } else { + return (assets as ThemeAssetsV2).coinIcons[coin.mainNetVersion]!; + } +}); diff --git a/lib/themes/coin_image_provider.dart b/lib/themes/coin_image_provider.dart new file mode 100644 index 000000000..239e1d1cb --- /dev/null +++ b/lib/themes/coin_image_provider.dart @@ -0,0 +1,92 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; + +final coinImageProvider = Provider.family((ref, coin) { + final assets = ref.watch(themeAssetsProvider); + + if (assets is ThemeAssets) { + switch (coin) { + case Coin.bitcoin: + return assets.bitcoinImage; + case Coin.litecoin: + case Coin.litecoinTestNet: + return assets.litecoinImage; + case Coin.bitcoincash: + return assets.bitcoincashImage; + case Coin.dogecoin: + return assets.dogecoinImage; + case Coin.eCash: + return assets.bitcoinImage; + case Coin.epicCash: + return assets.epicCashImage; + case Coin.firo: + return assets.firoImage; + case Coin.monero: + return assets.moneroImage; + case Coin.wownero: + return assets.wowneroImage; + case Coin.namecoin: + return assets.namecoinImage; + case Coin.particl: + return assets.particlImage; + case Coin.bitcoinTestNet: + return assets.bitcoinImage; + case Coin.bitcoincashTestnet: + return assets.bitcoincashImage; + case Coin.firoTestNet: + return assets.firoImage; + case Coin.dogecoinTestNet: + return assets.dogecoinImage; + case Coin.ethereum: + return assets.ethereumImage; + } + } else { + return (assets as ThemeAssetsV2).coinImages[coin.mainNetVersion]!; + } +}); + +final coinImageSecondaryProvider = Provider.family((ref, coin) { + final assets = ref.watch(themeAssetsProvider); + + if (assets is ThemeAssets) { + switch (coin) { + case Coin.bitcoin: + return assets.bitcoinImageSecondary; + case Coin.litecoin: + case Coin.litecoinTestNet: + return assets.litecoinImageSecondary; + case Coin.bitcoincash: + return assets.bitcoincashImageSecondary; + case Coin.dogecoin: + return assets.dogecoinImageSecondary; + case Coin.eCash: + return assets.bitcoinImageSecondary; + case Coin.epicCash: + return assets.epicCashImageSecondary; + case Coin.firo: + return assets.firoImageSecondary; + case Coin.monero: + return assets.moneroImageSecondary; + case Coin.wownero: + return assets.wowneroImageSecondary; + case Coin.namecoin: + return assets.namecoinImageSecondary; + case Coin.particl: + return assets.particlImageSecondary; + case Coin.bitcoinTestNet: + return assets.bitcoinImageSecondary; + case Coin.bitcoincashTestnet: + return assets.bitcoincashImageSecondary; + case Coin.firoTestNet: + return assets.firoImageSecondary; + case Coin.dogecoinTestNet: + return assets.dogecoinImageSecondary; + case Coin.ethereum: + return assets.ethereumImageSecondary; + } + } else { + return (assets as ThemeAssetsV2).coinSecondaryImages[coin.mainNetVersion]!; + } +}); diff --git a/lib/themes/color_theme.dart b/lib/themes/color_theme.dart new file mode 100644 index 000000000..b4f00adcf --- /dev/null +++ b/lib/themes/color_theme.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; + +const kCoinThemeColorDefaults = CoinThemeColorDefault(); + +class CoinThemeColorDefault { + const CoinThemeColorDefault(); + + Color get bitcoin => const Color(0xFFFCC17B); + Color get litecoin => const Color(0xFF7FA6E1); + Color get bitcoincash => const Color(0xFF7BCFB8); + Color get firo => const Color(0xFFFF897A); + Color get dogecoin => const Color(0xFFFFE079); + Color get epicCash => const Color(0xFFC5C7CB); + Color get eCash => const Color(0xFFC5C7CB); + Color get ethereum => const Color(0xFFA7ADE9); + Color get monero => const Color(0xFFFF9E6B); + Color get namecoin => const Color(0xFF91B1E1); + Color get wownero => const Color(0xFFED80C1); + Color get particl => const Color(0xFF8175BD); + + Color forCoin(Coin coin) { + switch (coin) { + case Coin.bitcoin: + case Coin.bitcoinTestNet: + return bitcoin; + case Coin.litecoin: + case Coin.litecoinTestNet: + return litecoin; + case Coin.bitcoincash: + case Coin.bitcoincashTestnet: + return bitcoincash; + case Coin.dogecoin: + case Coin.dogecoinTestNet: + return dogecoin; + case Coin.eCash: + return eCash; + case Coin.epicCash: + return epicCash; + case Coin.ethereum: + return ethereum; + case Coin.firo: + case Coin.firoTestNet: + return firo; + case Coin.monero: + return monero; + case Coin.namecoin: + return namecoin; + case Coin.wownero: + return wownero; + case Coin.particl: + return particl; + } + } +} diff --git a/lib/utilities/theme/stack_colors.dart b/lib/themes/stack_colors.dart similarity index 80% rename from lib/utilities/theme/stack_colors.dart rename to lib/themes/stack_colors.dart index 9764128e4..6717615b1 100644 --- a/lib/utilities/theme/stack_colors.dart +++ b/lib/themes/stack_colors.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/themes/color_theme.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/theme/color_theme.dart'; class StackColors extends ThemeExtension { - final ThemeType themeType; + final String themeId; + final Brightness brightness; final Color background; final Color backgroundAppBar; @@ -32,6 +34,7 @@ class StackColors extends ThemeExtension { final Color textWhite; final Color textFavoriteCard; final Color textError; + final Color textRestore; // button background final Color buttonBackPrimary; @@ -40,6 +43,8 @@ class StackColors extends ThemeExtension { final Color buttonBackSecondaryDisabled; final Color buttonBackBorder; final Color buttonBackBorderDisabled; + final Color buttonBackBorderSecondary; + final Color buttonBackBorderSecondaryDisabled; final Color numberBackDefault; final Color numpadBackDefault; final Color bottomNavBack; @@ -56,6 +61,8 @@ class StackColors extends ThemeExtension { final Color numberTextDefault; final Color numpadTextDefault; final Color bottomNavText; + final Color customTextButtonEnabledText; + final Color customTextButtonDisabledText; // switch background final Color switchBGOn; @@ -96,6 +103,7 @@ class StackColors extends ThemeExtension { // icons final Color bottomNavIconBack; final Color bottomNavIconIcon; + final Color bottomNavIconIconHighlighted; final Color topNavIconPrimary; final Color topNavIconGreen; final Color topNavIconYellow; @@ -110,6 +118,8 @@ class StackColors extends ThemeExtension { final Color textFieldDefaultBG; final Color textFieldErrorBG; final Color textFieldSuccessBG; + final Color textFieldErrorBorder; + final Color textFieldSuccessBorder; final Color textFieldActiveSearchIconLeft; final Color textFieldDefaultSearchIconLeft; final Color textFieldErrorSearchIconLeft; @@ -173,8 +183,29 @@ class StackColors extends ThemeExtension { final Color textConfirmTotalAmount; final Color textSelectedWordTableItem; +// rate type toggle + final Color rateTypeToggleColorOn; + final Color rateTypeToggleColorOff; + final Color rateTypeToggleDesktopColorOn; + final Color rateTypeToggleDesktopColorOff; + + // token view colors + final Color ethTagText; + final Color ethTagBG; + final Color ethWalletTagText; + final Color ethWalletTagBG; + final Color tokenSummaryTextPrimary; + final Color tokenSummaryTextSecondary; + final Color tokenSummaryBG; + final Color tokenSummaryButtonBG; + final Color tokenSummaryIcon; + + final BoxShadow standardBoxShadow; + final BoxShadow? homeViewButtonBarBoxShadow; + StackColors({ - required this.themeType, + required this.themeId, + required this.brightness, required this.background, required this.backgroundAppBar, required this.gradientBackground, @@ -198,12 +229,15 @@ class StackColors extends ThemeExtension { required this.textWhite, required this.textFavoriteCard, required this.textError, + required this.textRestore, required this.buttonBackPrimary, required this.buttonBackSecondary, required this.buttonBackPrimaryDisabled, required this.buttonBackSecondaryDisabled, required this.buttonBackBorder, required this.buttonBackBorderDisabled, + required this.buttonBackBorderSecondary, + required this.buttonBackBorderSecondaryDisabled, required this.numberBackDefault, required this.numpadBackDefault, required this.bottomNavBack, @@ -218,6 +252,8 @@ class StackColors extends ThemeExtension { required this.numberTextDefault, required this.numpadTextDefault, required this.bottomNavText, + required this.customTextButtonEnabledText, + required this.customTextButtonDisabledText, required this.switchBGOn, required this.switchBGOff, required this.switchBGDisabled, @@ -246,6 +282,7 @@ class StackColors extends ThemeExtension { required this.snackBarTextInfo, required this.bottomNavIconBack, required this.bottomNavIconIcon, + required this.bottomNavIconIconHighlighted, required this.topNavIconPrimary, required this.topNavIconGreen, required this.topNavIconYellow, @@ -258,6 +295,8 @@ class StackColors extends ThemeExtension { required this.textFieldDefaultBG, required this.textFieldErrorBG, required this.textFieldSuccessBG, + required this.textFieldErrorBorder, + required this.textFieldSuccessBorder, required this.textFieldActiveSearchIconLeft, required this.textFieldDefaultSearchIconLeft, required this.textFieldErrorSearchIconLeft, @@ -306,11 +345,27 @@ class StackColors extends ThemeExtension { required this.myStackContactIconBG, required this.textConfirmTotalAmount, required this.textSelectedWordTableItem, + required this.rateTypeToggleColorOn, + required this.rateTypeToggleColorOff, + required this.rateTypeToggleDesktopColorOn, + required this.rateTypeToggleDesktopColorOff, + required this.standardBoxShadow, + required this.homeViewButtonBarBoxShadow, + required this.ethTagText, + required this.ethTagBG, + required this.ethWalletTagText, + required this.ethWalletTagBG, + required this.tokenSummaryTextPrimary, + required this.tokenSummaryTextSecondary, + required this.tokenSummaryBG, + required this.tokenSummaryButtonBG, + required this.tokenSummaryIcon, }); - factory StackColors.fromStackColorTheme(StackColorTheme colorTheme) { + factory StackColors.fromStackColorTheme(StackTheme colorTheme) { return StackColors( - themeType: colorTheme.themeType, + themeId: colorTheme.themeId, + brightness: colorTheme.brightness, background: colorTheme.background, backgroundAppBar: colorTheme.backgroundAppBar, gradientBackground: colorTheme.gradientBackground, @@ -334,12 +389,16 @@ class StackColors extends ThemeExtension { textWhite: colorTheme.textWhite, textFavoriteCard: colorTheme.textFavoriteCard, textError: colorTheme.textError, + textRestore: colorTheme.textRestore, buttonBackPrimary: colorTheme.buttonBackPrimary, buttonBackSecondary: colorTheme.buttonBackSecondary, buttonBackPrimaryDisabled: colorTheme.buttonBackPrimaryDisabled, buttonBackSecondaryDisabled: colorTheme.buttonBackSecondaryDisabled, buttonBackBorder: colorTheme.buttonBackBorder, buttonBackBorderDisabled: colorTheme.buttonBackBorderDisabled, + buttonBackBorderSecondary: colorTheme.buttonBackBorderSecondary, + buttonBackBorderSecondaryDisabled: + colorTheme.buttonBackBorderSecondaryDisabled, numberBackDefault: colorTheme.numberBackDefault, numpadBackDefault: colorTheme.numpadBackDefault, bottomNavBack: colorTheme.bottomNavBack, @@ -354,6 +413,8 @@ class StackColors extends ThemeExtension { numberTextDefault: colorTheme.numberTextDefault, numpadTextDefault: colorTheme.numpadTextDefault, bottomNavText: colorTheme.bottomNavText, + customTextButtonEnabledText: colorTheme.customTextButtonEnabledText, + customTextButtonDisabledText: colorTheme.customTextButtonDisabledText, switchBGOn: colorTheme.switchBGOn, switchBGOff: colorTheme.switchBGOff, switchBGDisabled: colorTheme.switchBGDisabled, @@ -382,6 +443,7 @@ class StackColors extends ThemeExtension { snackBarTextInfo: colorTheme.snackBarTextInfo, bottomNavIconBack: colorTheme.bottomNavIconBack, bottomNavIconIcon: colorTheme.bottomNavIconIcon, + bottomNavIconIconHighlighted: colorTheme.bottomNavIconIconHighlighted, topNavIconPrimary: colorTheme.topNavIconPrimary, topNavIconGreen: colorTheme.topNavIconGreen, topNavIconYellow: colorTheme.topNavIconYellow, @@ -394,6 +456,8 @@ class StackColors extends ThemeExtension { textFieldDefaultBG: colorTheme.textFieldDefaultBG, textFieldErrorBG: colorTheme.textFieldErrorBG, textFieldSuccessBG: colorTheme.textFieldSuccessBG, + textFieldErrorBorder: colorTheme.textFieldErrorBorder, + textFieldSuccessBorder: colorTheme.textFieldSuccessBorder, textFieldActiveSearchIconLeft: colorTheme.textFieldActiveSearchIconLeft, textFieldDefaultSearchIconLeft: colorTheme.textFieldDefaultSearchIconLeft, textFieldErrorSearchIconLeft: colorTheme.textFieldErrorSearchIconLeft, @@ -444,12 +508,28 @@ class StackColors extends ThemeExtension { myStackContactIconBG: colorTheme.myStackContactIconBG, textConfirmTotalAmount: colorTheme.textConfirmTotalAmount, textSelectedWordTableItem: colorTheme.textSelectedWordTableItem, + rateTypeToggleColorOn: colorTheme.rateTypeToggleColorOn, + rateTypeToggleColorOff: colorTheme.rateTypeToggleColorOff, + rateTypeToggleDesktopColorOn: colorTheme.rateTypeToggleDesktopColorOn, + rateTypeToggleDesktopColorOff: colorTheme.rateTypeToggleDesktopColorOff, + homeViewButtonBarBoxShadow: colorTheme.homeViewButtonBarBoxShadow, + standardBoxShadow: colorTheme.standardBoxShadow, + ethTagText: colorTheme.ethTagText, + ethTagBG: colorTheme.ethTagBG, + ethWalletTagText: colorTheme.ethWalletTagText, + ethWalletTagBG: colorTheme.ethWalletTagBG, + tokenSummaryTextPrimary: colorTheme.tokenSummaryTextPrimary, + tokenSummaryTextSecondary: colorTheme.tokenSummaryTextSecondary, + tokenSummaryBG: colorTheme.tokenSummaryBG, + tokenSummaryButtonBG: colorTheme.tokenSummaryButtonBG, + tokenSummaryIcon: colorTheme.tokenSummaryIcon, ); } @override ThemeExtension copyWith({ - ThemeType? themeType, + String? themeId, + Brightness? brightness, Color? background, Color? backgroundAppBar, Gradient? gradientBackground, @@ -473,12 +553,15 @@ class StackColors extends ThemeExtension { Color? textWhite, Color? textFavoriteCard, Color? textError, + Color? textRestore, Color? buttonBackPrimary, Color? buttonBackSecondary, Color? buttonBackPrimaryDisabled, Color? buttonBackSecondaryDisabled, Color? buttonBackBorder, Color? buttonBackBorderDisabled, + Color? buttonBackBorderSecondary, + Color? buttonBackBorderSecondaryDisabled, Color? numberBackDefault, Color? numpadBackDefault, Color? bottomNavBack, @@ -493,6 +576,8 @@ class StackColors extends ThemeExtension { Color? numberTextDefault, Color? numpadTextDefault, Color? bottomNavText, + Color? customTextButtonEnabledText, + Color? customTextButtonDisabledText, Color? switchBGOn, Color? switchBGOff, Color? switchBGDisabled, @@ -521,6 +606,7 @@ class StackColors extends ThemeExtension { Color? snackBarTextInfo, Color? bottomNavIconBack, Color? bottomNavIconIcon, + Color? bottomNavIconIconHighlighted, Color? topNavIconPrimary, Color? topNavIconGreen, Color? topNavIconYellow, @@ -533,6 +619,8 @@ class StackColors extends ThemeExtension { Color? textFieldDefaultBG, Color? textFieldErrorBG, Color? textFieldSuccessBG, + Color? textFieldErrorBorder, + Color? textFieldSuccessBorder, Color? textFieldActiveSearchIconLeft, Color? textFieldDefaultSearchIconLeft, Color? textFieldErrorSearchIconLeft, @@ -581,9 +669,25 @@ class StackColors extends ThemeExtension { Color? myStackContactIconBG, Color? textConfirmTotalAmount, Color? textSelectedWordTableItem, + Color? rateTypeToggleColorOn, + Color? rateTypeToggleColorOff, + Color? rateTypeToggleDesktopColorOn, + Color? rateTypeToggleDesktopColorOff, + Color? ethTagText, + Color? ethTagBG, + Color? ethWalletTagText, + Color? ethWalletTagBG, + Color? tokenSummaryTextPrimary, + Color? tokenSummaryTextSecondary, + Color? tokenSummaryBG, + Color? tokenSummaryButtonBG, + Color? tokenSummaryIcon, + BoxShadow? homeViewButtonBarBoxShadow, + BoxShadow? standardBoxShadow, }) { return StackColors( - themeType: themeType ?? this.themeType, + themeId: themeId ?? this.themeId, + brightness: brightness ?? this.brightness, background: background ?? this.background, backgroundAppBar: backgroundAppBar ?? this.backgroundAppBar, gradientBackground: gradientBackground ?? this.gradientBackground, @@ -607,6 +711,7 @@ class StackColors extends ThemeExtension { textWhite: textWhite ?? this.textWhite, textFavoriteCard: textFavoriteCard ?? this.textFavoriteCard, textError: textError ?? this.textError, + textRestore: textRestore ?? this.textRestore, buttonBackPrimary: buttonBackPrimary ?? this.buttonBackPrimary, buttonBackSecondary: buttonBackSecondary ?? this.buttonBackSecondary, buttonBackPrimaryDisabled: @@ -616,6 +721,10 @@ class StackColors extends ThemeExtension { buttonBackBorder: buttonBackBorder ?? this.buttonBackBorder, buttonBackBorderDisabled: buttonBackBorderDisabled ?? this.buttonBackBorderDisabled, + buttonBackBorderSecondary: + buttonBackBorderSecondary ?? this.buttonBackBorderSecondary, + buttonBackBorderSecondaryDisabled: buttonBackBorderSecondaryDisabled ?? + this.buttonBackBorderSecondaryDisabled, numberBackDefault: numberBackDefault ?? this.numberBackDefault, numpadBackDefault: numpadBackDefault ?? this.numpadBackDefault, bottomNavBack: bottomNavBack ?? this.bottomNavBack, @@ -633,6 +742,10 @@ class StackColors extends ThemeExtension { numberTextDefault: numberTextDefault ?? this.numberTextDefault, numpadTextDefault: numpadTextDefault ?? this.numpadTextDefault, bottomNavText: bottomNavText ?? this.bottomNavText, + customTextButtonEnabledText: + customTextButtonEnabledText ?? this.customTextButtonEnabledText, + customTextButtonDisabledText: + customTextButtonDisabledText ?? this.customTextButtonDisabledText, switchBGOn: switchBGOn ?? this.switchBGOn, switchBGOff: switchBGOff ?? this.switchBGOff, switchBGDisabled: switchBGDisabled ?? this.switchBGDisabled, @@ -667,6 +780,8 @@ class StackColors extends ThemeExtension { snackBarTextInfo: snackBarTextInfo ?? this.snackBarTextInfo, bottomNavIconBack: bottomNavIconBack ?? this.bottomNavIconBack, bottomNavIconIcon: bottomNavIconIcon ?? this.bottomNavIconIcon, + bottomNavIconIconHighlighted: + bottomNavIconIconHighlighted ?? this.bottomNavIconIconHighlighted, topNavIconPrimary: topNavIconPrimary ?? this.topNavIconPrimary, topNavIconGreen: topNavIconGreen ?? this.topNavIconGreen, topNavIconYellow: topNavIconYellow ?? this.topNavIconYellow, @@ -679,6 +794,9 @@ class StackColors extends ThemeExtension { textFieldDefaultBG: textFieldDefaultBG ?? this.textFieldDefaultBG, textFieldErrorBG: textFieldErrorBG ?? this.textFieldErrorBG, textFieldSuccessBG: textFieldSuccessBG ?? this.textFieldSuccessBG, + textFieldErrorBorder: textFieldErrorBorder ?? this.textFieldErrorBorder, + textFieldSuccessBorder: + textFieldSuccessBorder ?? this.textFieldSuccessBorder, textFieldActiveSearchIconLeft: textFieldActiveSearchIconLeft ?? this.textFieldActiveSearchIconLeft, textFieldDefaultSearchIconLeft: @@ -752,6 +870,28 @@ class StackColors extends ThemeExtension { textConfirmTotalAmount ?? this.textConfirmTotalAmount, textSelectedWordTableItem: textSelectedWordTableItem ?? this.textSelectedWordTableItem, + rateTypeToggleColorOn: + rateTypeToggleColorOn ?? this.rateTypeToggleColorOn, + rateTypeToggleColorOff: + rateTypeToggleColorOff ?? this.rateTypeToggleColorOff, + rateTypeToggleDesktopColorOn: + rateTypeToggleDesktopColorOn ?? this.rateTypeToggleDesktopColorOn, + rateTypeToggleDesktopColorOff: + rateTypeToggleDesktopColorOff ?? this.rateTypeToggleDesktopColorOff, + ethTagText: ethTagText ?? this.ethTagText, + ethTagBG: ethTagBG ?? this.ethTagBG, + ethWalletTagText: ethWalletTagText ?? this.ethWalletTagText, + ethWalletTagBG: ethWalletTagBG ?? this.ethWalletTagBG, + tokenSummaryTextPrimary: + tokenSummaryTextPrimary ?? this.tokenSummaryTextPrimary, + tokenSummaryTextSecondary: + tokenSummaryTextSecondary ?? this.tokenSummaryTextSecondary, + tokenSummaryBG: tokenSummaryBG ?? this.tokenSummaryBG, + tokenSummaryButtonBG: tokenSummaryButtonBG ?? this.tokenSummaryButtonBG, + tokenSummaryIcon: tokenSummaryIcon ?? this.tokenSummaryIcon, + homeViewButtonBarBoxShadow: + homeViewButtonBarBoxShadow ?? this.homeViewButtonBarBoxShadow, + standardBoxShadow: standardBoxShadow ?? this.standardBoxShadow, ); } @@ -765,8 +905,11 @@ class StackColors extends ThemeExtension { } return StackColors( - themeType: other.themeType, + themeId: other.themeId, + brightness: other.brightness, gradientBackground: other.gradientBackground, + homeViewButtonBarBoxShadow: other.homeViewButtonBarBoxShadow, + standardBoxShadow: other.standardBoxShadow, background: Color.lerp( background, other.background, @@ -877,6 +1020,11 @@ class StackColors extends ThemeExtension { other.textError, t, )!, + textRestore: Color.lerp( + textRestore, + other.textRestore, + t, + )!, buttonBackPrimary: Color.lerp( buttonBackPrimary, other.buttonBackPrimary, @@ -907,6 +1055,16 @@ class StackColors extends ThemeExtension { other.buttonBackBorderDisabled, t, )!, + buttonBackBorderSecondary: Color.lerp( + buttonBackBorderSecondary, + other.buttonBackBorderSecondary, + t, + )!, + buttonBackBorderSecondaryDisabled: Color.lerp( + buttonBackBorderSecondaryDisabled, + other.buttonBackBorderSecondaryDisabled, + t, + )!, numberBackDefault: Color.lerp( numberBackDefault, other.numberBackDefault, @@ -977,6 +1135,16 @@ class StackColors extends ThemeExtension { other.bottomNavText, t, )!, + customTextButtonEnabledText: Color.lerp( + customTextButtonEnabledText, + other.customTextButtonEnabledText, + t, + )!, + customTextButtonDisabledText: Color.lerp( + customTextButtonDisabledText, + other.customTextButtonDisabledText, + t, + )!, switchBGOn: Color.lerp( switchBGOn, other.switchBGOn, @@ -1117,6 +1285,11 @@ class StackColors extends ThemeExtension { other.bottomNavIconIcon, t, )!, + bottomNavIconIconHighlighted: Color.lerp( + bottomNavIconIconHighlighted, + other.bottomNavIconIconHighlighted, + t, + )!, topNavIconPrimary: Color.lerp( topNavIconPrimary, other.topNavIconPrimary, @@ -1177,6 +1350,16 @@ class StackColors extends ThemeExtension { other.textFieldSuccessBG, t, )!, + textFieldErrorBorder: Color.lerp( + textFieldErrorBorder, + other.textFieldErrorBorder, + t, + )!, + textFieldSuccessBorder: Color.lerp( + textFieldSuccessBorder, + other.textFieldSuccessBorder, + t, + )!, textFieldActiveSearchIconLeft: Color.lerp( textFieldActiveSearchIconLeft, other.textFieldActiveSearchIconLeft, @@ -1415,6 +1598,71 @@ class StackColors extends ThemeExtension { other.textSelectedWordTableItem, t, )!, + rateTypeToggleColorOn: Color.lerp( + rateTypeToggleColorOn, + other.rateTypeToggleColorOn, + t, + )!, + rateTypeToggleColorOff: Color.lerp( + rateTypeToggleColorOff, + other.rateTypeToggleColorOff, + t, + )!, + rateTypeToggleDesktopColorOn: Color.lerp( + rateTypeToggleDesktopColorOn, + other.rateTypeToggleDesktopColorOn, + t, + )!, + rateTypeToggleDesktopColorOff: Color.lerp( + rateTypeToggleDesktopColorOff, + other.rateTypeToggleDesktopColorOff, + t, + )!, + ethTagText: Color.lerp( + ethTagText, + other.ethTagText, + t, + )!, + ethTagBG: Color.lerp( + ethTagBG, + other.ethTagBG, + t, + )!, + ethWalletTagText: Color.lerp( + ethWalletTagText, + other.ethWalletTagText, + t, + )!, + ethWalletTagBG: Color.lerp( + ethWalletTagBG, + other.ethWalletTagBG, + t, + )!, + tokenSummaryTextPrimary: Color.lerp( + tokenSummaryTextPrimary, + other.tokenSummaryTextPrimary, + t, + )!, + tokenSummaryTextSecondary: Color.lerp( + tokenSummaryTextSecondary, + other.tokenSummaryTextSecondary, + t, + )!, + tokenSummaryBG: Color.lerp( + tokenSummaryBG, + other.tokenSummaryBG, + t, + )!, + tokenSummaryButtonBG: Color.lerp( + tokenSummaryButtonBG, + other.tokenSummaryButtonBG, + t, + )!, + tokenSummaryIcon: Color.lerp( + tokenSummaryIcon, + other.tokenSummaryIcon, + t, + )!, ); } @@ -1434,6 +1682,10 @@ class StackColors extends ThemeExtension { return _coin.dogecoin; case Coin.epicCash: return _coin.epicCash; + case Coin.eCash: + return _coin.eCash; + case Coin.ethereum: + return _coin.ethereum; case Coin.firo: case Coin.firoTestNet: return _coin.firo; @@ -1448,13 +1700,7 @@ class StackColors extends ThemeExtension { } } - static const _coin = CoinThemeColor(); - - BoxShadow get standardBoxShadow => BoxShadow( - color: shadow, - spreadRadius: 3, - blurRadius: 4, - ); + static const _coin = CoinThemeColorDefault(); Color colorForStatus(String status) { switch (status) { @@ -1473,6 +1719,7 @@ class StackColors extends ThemeExtension { return const Color(0xFFD3A90F); case "Finished": case "finished": + case "Completed": return accentColorGreen; case "Failed": case "failed": @@ -1487,70 +1734,121 @@ class StackColors extends ThemeExtension { } } - ButtonStyle? getDeleteEnabledButtonColor(BuildContext context) => + ButtonStyle? getDeleteEnabledButtonStyle(BuildContext context) => Theme.of(context).textButtonTheme.style?.copyWith( backgroundColor: MaterialStateProperty.all( textFieldErrorBG, ), ); - ButtonStyle? getDeleteDisabledButtonColor(BuildContext context) => + ButtonStyle? getDeleteDisabledButtonStyle(BuildContext context) => Theme.of(context).textButtonTheme.style?.copyWith( backgroundColor: MaterialStateProperty.all( buttonBackSecondaryDisabled, ), ); - ButtonStyle? getPrimaryEnabledButtonColor(BuildContext context) => + ButtonStyle? getPrimaryEnabledButtonStyle(BuildContext context) => Theme.of(context).textButtonTheme.style?.copyWith( backgroundColor: MaterialStateProperty.all( buttonBackPrimary, ), ); - ButtonStyle? getPrimaryDisabledButtonColor(BuildContext context) => + ButtonStyle? getPrimaryDisabledButtonStyle(BuildContext context) => Theme.of(context).textButtonTheme.style?.copyWith( backgroundColor: MaterialStateProperty.all( buttonBackPrimaryDisabled, ), ); - ButtonStyle? getSecondaryEnabledButtonColor(BuildContext context) => + ButtonStyle? getOutlineBlueButtonStyle(BuildContext context) => + Theme.of(context).textButtonTheme.style?.copyWith( + backgroundColor: MaterialStateProperty.all( + Colors.transparent, + ), + side: MaterialStateProperty.all( + BorderSide( + color: customTextButtonEnabledText, + ), + ), + ); + + ButtonStyle? getOutlineBlueButtonDisabledStyle(BuildContext context) => + Theme.of(context).textButtonTheme.style?.copyWith( + backgroundColor: MaterialStateProperty.all( + Colors.transparent, + ), + side: MaterialStateProperty.all( + BorderSide( + color: customTextButtonDisabledText, + ), + ), + ); + + ButtonStyle? getSecondaryEnabledButtonStyle(BuildContext context) => Theme.of(context).textButtonTheme.style?.copyWith( backgroundColor: MaterialStateProperty.all( buttonBackSecondary, ), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + side: BorderSide( + color: buttonBackBorderSecondary, + width: 1, + ), + borderRadius: BorderRadius.circular(10000), + ), + ), ); - ButtonStyle? getSecondaryDisabledButtonColor(BuildContext context) => + ButtonStyle? getSecondaryDisabledButtonStyle(BuildContext context) => Theme.of(context).textButtonTheme.style?.copyWith( backgroundColor: MaterialStateProperty.all( buttonBackSecondaryDisabled, ), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + side: BorderSide( + color: buttonBackBorderSecondaryDisabled, + width: 1, + ), + borderRadius: BorderRadius.circular(10000), + ), + ), ); - ButtonStyle? getSmallSecondaryEnabledButtonColor(BuildContext context) => + ButtonStyle? getSmallSecondaryEnabledButtonStyle(BuildContext context) => Theme.of(context).textButtonTheme.style?.copyWith( backgroundColor: MaterialStateProperty.all( textFieldDefaultBG, ), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + side: BorderSide( + color: buttonBackBorderSecondary, + width: 1, + ), + borderRadius: BorderRadius.circular(10000), + ), + ), ); - ButtonStyle? getDesktopMenuButtonColor(BuildContext context) => + ButtonStyle? getDesktopMenuButtonStyle(BuildContext context) => Theme.of(context).textButtonTheme.style?.copyWith( backgroundColor: MaterialStateProperty.all( popupBG, ), ); - ButtonStyle? getDesktopMenuButtonColorSelected(BuildContext context) => + ButtonStyle? getDesktopMenuButtonStyleSelected(BuildContext context) => Theme.of(context).textButtonTheme.style?.copyWith( backgroundColor: MaterialStateProperty.all( textFieldDefaultBG, ), ); - ButtonStyle? getDesktopSettingsButtonColor(BuildContext context) => + ButtonStyle? getDesktopSettingsButtonStyle(BuildContext context) => Theme.of(context).textButtonTheme.style?.copyWith( backgroundColor: MaterialStateProperty.all( background, diff --git a/lib/themes/theme_providers.dart b/lib/themes/theme_providers.dart new file mode 100644 index 000000000..08a5be65c --- /dev/null +++ b/lib/themes/theme_providers.dart @@ -0,0 +1,30 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_service.dart'; + +final applicationThemesDirectoryPathProvider = StateProvider((ref) => ""); + +final colorProvider = StateProvider( + (ref) => StackColors.fromStackColorTheme( + ref.watch(themeProvider.state).state, + ), +); + +final themeProvider = StateProvider( + (ref) => ref.watch( + pThemeService.select( + (value) => value.getTheme( + themeId: "light", + )!, + ), + ), +); + +final themeAssetsProvider = StateProvider( + (ref) => ref.watch( + themeProvider.select( + (value) => value.assets, + ), + ), +); diff --git a/lib/themes/theme_service.dart b/lib/themes/theme_service.dart new file mode 100644 index 000000000..e130daba8 --- /dev/null +++ b/lib/themes/theme_service.dart @@ -0,0 +1,287 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:archive/archive_io.dart'; +import 'package:crypto/crypto.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:http/http.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/utilities/logger.dart'; +import 'package:stackwallet/utilities/stack_file_system.dart'; + +final pThemeService = Provider((ref) { + return ThemeService.instance; +}); + +class ThemeService { + static const _currentDefaultThemeVersion = 2; + ThemeService._(); + static ThemeService? _instance; + static ThemeService get instance => _instance ??= ThemeService._(); + + static const String baseServerUrl = "https://themes.stackwallet.com"; + + MainDB? _db; + MainDB get db => _db!; + + void init(MainDB db) => _db ??= db; + + Future install({required Uint8List themeArchiveData}) async { + final themesDir = await StackFileSystem.applicationThemesDirectory(); + + final archive = ZipDecoder().decodeBytes(themeArchiveData); + + final themeJsonFiles = archive.files.where((e) => e.name == "theme.json"); + + if (themeJsonFiles.length != 1) { + throw Exception("Invalid theme archive: Missing theme.json"); + } + + final OutputStream os = OutputStream(); + themeJsonFiles.first.decompress(os); + final String jsonString = utf8.decode(os.getBytes()); + final json = jsonDecode(jsonString) as Map; + + final theme = StackTheme.fromJson( + json: Map.from(json), + applicationThemesDirectoryPath: themesDir.path, + ); + + final String assetsPath = "${themesDir.path}/${theme.themeId}"; + + for (final file in archive.files) { + if (file.isFile) { + // TODO more sanitation? + if (file.name.contains("..")) { + Logging.instance.log( + "Bad theme asset file path: ${file.name}", + level: LogLevel.Error, + ); + } else { + final os = OutputFileStream("$assetsPath/${file.name}"); + file.writeContent(os); + await os.close(); + } + } + } + + await db.isar.writeTxn(() async { + await db.isar.stackThemes.put(theme); + }); + } + + Future remove({required String themeId}) async { + final themesDir = await StackFileSystem.applicationThemesDirectory(); + final isarId = await db.isar.stackThemes + .where() + .themeIdEqualTo(themeId) + .idProperty() + .findFirst(); + if (isarId != null) { + await db.isar.writeTxn(() async { + await db.isar.stackThemes.delete(isarId); + }); + await Directory("${themesDir.path}/$themeId").delete(recursive: true); + } else { + Logging.instance.log( + "Failed to delete theme $themeId", + level: LogLevel.Warning, + ); + } + } + + Future checkDefaultThemesOnStartup() async { + // install default themes + if (!(await ThemeService.instance.verifyInstalled(themeId: "light"))) { + Logging.instance.log( + "Installing default light theme...", + level: LogLevel.Info, + ); + final lightZip = await rootBundle.load("assets/default_themes/light.zip"); + await ThemeService.instance + .install(themeArchiveData: lightZip.buffer.asUint8List()); + Logging.instance.log( + "Installing default light theme... finished", + level: LogLevel.Info, + ); + } else { + // check installed version + final theme = ThemeService.instance.getTheme(themeId: "light"); + if ((theme?.version ?? 1) < _currentDefaultThemeVersion) { + Logging.instance.log( + "Updating default light theme...", + level: LogLevel.Info, + ); + final lightZip = + await rootBundle.load("assets/default_themes/light.zip"); + await ThemeService.instance + .install(themeArchiveData: lightZip.buffer.asUint8List()); + Logging.instance.log( + "Updating default light theme... finished", + level: LogLevel.Info, + ); + } + } + + if (!(await ThemeService.instance.verifyInstalled(themeId: "dark"))) { + Logging.instance.log( + "Installing default dark theme... ", + level: LogLevel.Info, + ); + final darkZip = await rootBundle.load("assets/default_themes/dark.zip"); + await ThemeService.instance + .install(themeArchiveData: darkZip.buffer.asUint8List()); + Logging.instance.log( + "Installing default dark theme... finished", + level: LogLevel.Info, + ); + } else { + // check installed version + final theme = ThemeService.instance.getTheme(themeId: "dark"); + if ((theme?.version ?? 1) < _currentDefaultThemeVersion) { + Logging.instance.log( + "Updating default dark theme...", + level: LogLevel.Info, + ); + final darkZip = await rootBundle.load("assets/default_themes/dark.zip"); + await ThemeService.instance + .install(themeArchiveData: darkZip.buffer.asUint8List()); + Logging.instance.log( + "Updating default dark theme... finished", + level: LogLevel.Info, + ); + } + } + } + + // TODO more thorough check/verification of theme + Future verifyInstalled({required String themeId}) async { + final dbHasTheme = + await db.isar.stackThemes.where().themeIdEqualTo(themeId).count() > 0; + if (dbHasTheme) { + final themesDir = await StackFileSystem.applicationThemesDirectory(); + final jsonFileExists = + await File("${themesDir.path}/$themeId/theme.json").exists(); + final assetsDirExists = + await Directory("${themesDir.path}/$themeId/assets").exists(); + + if (!jsonFileExists || !assetsDirExists) { + Logging.instance.log( + "Theme $themeId found in DB but is missing files", + level: LogLevel.Warning, + ); + } + + return jsonFileExists && assetsDirExists; + } else { + return false; + } + } + + Future> fetchThemes() async { + try { + final response = await get(Uri.parse("$baseServerUrl/themes")); + + final jsonList = jsonDecode(response.body) as List; + + final result = List>.from(jsonList) + .map((e) => StackThemeMetaData.fromMap(e)) + .where((e) => e.id != "light" && e.id != "dark") + .toList(); + + return result; + } catch (e, s) { + Logging.instance.log( + "Failed to fetch themes list: $e\n$s", + level: LogLevel.Warning, + ); + rethrow; + } + } + + Future fetchTheme({ + required StackThemeMetaData themeMetaData, + }) async { + try { + final response = + await get(Uri.parse("$baseServerUrl/theme/${themeMetaData.id}")); + + final bytes = response.bodyBytes; + + // verify hash + final digest = sha256.convert(bytes); + if (digest.toString() == themeMetaData.sha256) { + return bytes; + } else { + throw Exception( + "Fetched theme archive sha256 hash ($digest) does not" + " match requested $themeMetaData", + ); + } + } catch (e, s) { + Logging.instance.log( + "Failed to fetch themes list: $e\n$s", + level: LogLevel.Warning, + ); + rethrow; + } + } + + StackTheme? getTheme({required String themeId}) => + db.isar.stackThemes.where().themeIdEqualTo(themeId).findFirstSync(); + + List get installedThemes => + db.isar.stackThemes.where().findAllSync(); +} + +class StackThemeMetaData { + final String name; + final String id; + final int version; + final String sha256; + final String size; + final String previewImageUrl; + + StackThemeMetaData({ + required this.name, + required this.id, + required this.version, + required this.sha256, + required this.size, + required this.previewImageUrl, + }); + + static StackThemeMetaData fromMap(Map map) { + try { + return StackThemeMetaData( + name: map["name"] as String, + id: map["id"] as String, + version: map["version"] as int? ?? 1, + sha256: map["sha256"] as String, + size: map["size"] as String, + previewImageUrl: map["previewImageUrl"] as String, + ); + } catch (e, s) { + Logging.instance.log( + "Failed to create instance of StackThemeMetaData using $map: \n$e\n$s", + level: LogLevel.Fatal, + ); + rethrow; + } + } + + @override + String toString() { + return "$runtimeType(" + "name: $name, " + "id: $id, " + "version: $version, " + "sha256: $sha256, " + "size: $size, " + "previewImageUrl: $previewImageUrl" + ")"; + } +} diff --git a/lib/utilities/address_utils.dart b/lib/utilities/address_utils.dart index a6cbb8b58..44850bc65 100644 --- a/lib/utilities/address_utils.dart +++ b/lib/utilities/address_utils.dart @@ -5,6 +5,7 @@ import 'package:crypto/crypto.dart'; import 'package:flutter_libepiccash/epic_cash.dart'; import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart'; import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart'; +import 'package:stackwallet/services/coins/ecash/ecash_wallet.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; import 'package:stackwallet/services/coins/litecoin/litecoin_wallet.dart'; import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart'; @@ -20,9 +21,14 @@ class AddressUtils { /// attempts to convert a string to a valid scripthash /// /// Returns the scripthash or throws an exception on invalid firo address - static String convertToScriptHash(String address, NetworkType network) { + static String convertToScriptHash( + String address, + NetworkType network, [ + String overridePrefix = "", + ]) { try { - final output = Address.addressToOutputScript(address, network); + final output = + Address.addressToOutputScript(address, network, overridePrefix); final hash = sha256.convert(output.toList(growable: false)).toString(); final chars = hash.split(""); @@ -52,8 +58,12 @@ class AddressUtils { return Address.validateAddress(address, dogecoin); case Coin.epicCash: return validateSendAddress(address) == "1"; + case Coin.ethereum: + return true; //TODO - validate ETH address case Coin.firo: return Address.validateAddress(address, firoNetwork); + case Coin.eCash: + return Address.validateAddress(address, eCashNetwork); case Coin.monero: return RegExp("[a-zA-Z0-9]{95}").hasMatch(address) || RegExp("[a-zA-Z0-9]{106}").hasMatch(address); diff --git a/lib/utilities/amount/amount.dart b/lib/utilities/amount/amount.dart new file mode 100644 index 000000000..30619bb0d --- /dev/null +++ b/lib/utilities/amount/amount.dart @@ -0,0 +1,172 @@ +import 'dart:convert'; + +import 'package:decimal/decimal.dart'; +import 'package:intl/number_symbols.dart'; +import 'package:intl/number_symbols_data.dart'; + +class Amount { + Amount({ + required BigInt rawValue, + required this.fractionDigits, + }) : assert(fractionDigits >= 0), + _value = rawValue; + + /// special zero case with [fractionDigits] set to 0 + static Amount get zero => Amount( + rawValue: BigInt.zero, + fractionDigits: 0, + ); + + /// truncate decimal value to [fractionDigits] places + Amount.fromDecimal(Decimal amount, {required this.fractionDigits}) + : assert(fractionDigits >= 0), + _value = amount.shift(fractionDigits).toBigInt(); + + // =========================================================================== + // ======= Instance properties =============================================== + + final int fractionDigits; + final BigInt _value; + + // =========================================================================== + // ======= Getters =========================================================== + + /// raw base value + BigInt get raw => _value; + + /// actual decimal vale represented + Decimal get decimal => Decimal.fromBigInt(raw).shift(-1 * fractionDigits); + + /// convenience getter + @Deprecated("provided for convenience only. Use fractionDigits instead.") + int get decimals => fractionDigits; + + // =========================================================================== + // ======= Serialization ===================================================== + + Map toMap() { + return {"raw": raw.toString(), "fractionDigits": fractionDigits}; + } + + String toJsonString() { + return jsonEncode(toMap()); + } + + String localizedStringAsFixed({ + required String locale, + int? decimalPlaces, + }) { + decimalPlaces ??= fractionDigits; + assert(decimalPlaces >= 0); + + final wholeNumber = decimal.truncate(); + + if (decimalPlaces == 0) { + return wholeNumber.toStringAsFixed(0); + } + + final String separator = + (numberFormatSymbols[locale] as NumberSymbols?)?.DECIMAL_SEP ?? + (numberFormatSymbols[locale.substring(0, 2)] as NumberSymbols?) + ?.DECIMAL_SEP ?? + "."; + + final fraction = decimal - wholeNumber; + + return "${wholeNumber.toStringAsFixed(0)}$separator${fraction.toStringAsFixed(decimalPlaces).substring(2)}"; + } + + // =========================================================================== + // ======= Deserialization =================================================== + + static Amount fromSerializedJsonString(String json) { + final map = jsonDecode(json) as Map; + return Amount( + rawValue: BigInt.parse(map["raw"] as String), + fractionDigits: map["fractionDigits"] as int, + ); + } + + // =========================================================================== + // ======= operators ========================================================= + + bool operator >(Amount other) => decimal > other.decimal; + + bool operator <(Amount other) => decimal < other.decimal; + + bool operator >=(Amount other) => decimal >= other.decimal; + + bool operator <=(Amount other) => decimal <= other.decimal; + + Amount operator +(Amount other) { + if (fractionDigits != other.fractionDigits) { + throw ArgumentError( + "fractionDigits do not match: this=$this, other=$other"); + } + return Amount( + rawValue: raw + other.raw, + fractionDigits: fractionDigits, + ); + } + + Amount operator -(Amount other) { + if (fractionDigits != other.fractionDigits) { + throw ArgumentError( + "fractionDigits do not match: this=$this, other=$other"); + } + return Amount( + rawValue: raw - other.raw, + fractionDigits: fractionDigits, + ); + } + + Amount operator *(Amount other) { + if (fractionDigits != other.fractionDigits) { + throw ArgumentError( + "fractionDigits do not match: this=$this, other=$other"); + } + return Amount( + rawValue: raw * other.raw, + fractionDigits: fractionDigits, + ); + } + + // =========================================================================== + // ======= Overrides ========================================================= + + @override + String toString() => "Amount($raw, $fractionDigits)"; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is Amount && + runtimeType == other.runtimeType && + raw == other.raw && + fractionDigits == other.fractionDigits; + + @override + int get hashCode => Object.hashAll([raw, fractionDigits]); +} + +// ============================================================================= +// ============================================================================= +// ======= Extensions ========================================================== + +extension DecimalAmountExt on Decimal { + Amount toAmount({required int fractionDigits}) { + return Amount.fromDecimal( + this, + fractionDigits: fractionDigits, + ); + } +} + +extension IntAmountExtension on int { + Amount toAmountAsRaw({required int fractionDigits}) { + return Amount( + rawValue: BigInt.from(this), + fractionDigits: fractionDigits, + ); + } +} diff --git a/lib/utilities/amount/amount_unit.dart b/lib/utilities/amount/amount_unit.dart new file mode 100644 index 000000000..2dd64202c --- /dev/null +++ b/lib/utilities/amount/amount_unit.dart @@ -0,0 +1,120 @@ +import 'dart:math' as math; + +import 'package:decimal/decimal.dart'; +import 'package:intl/number_symbols.dart'; +import 'package:intl/number_symbols_data.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; + +enum AmountUnit { + normal(0), + milli(3), + micro(6), + nano(9), + pico(12), + femto(15), + atto(18), + ; + + const AmountUnit(this.shift); + final int shift; +} + +extension AmountUnitExt on AmountUnit { + String unitForCoin(Coin coin) { + switch (this) { + case AmountUnit.normal: + return coin.ticker; + case AmountUnit.milli: + return "m${coin.ticker}"; + case AmountUnit.micro: + return "µ${coin.ticker}"; + case AmountUnit.nano: + if (coin == Coin.ethereum) { + return "gwei"; + } else if (coin == Coin.wownero || coin == Coin.monero) { + return "n${coin.ticker}"; + } else { + return "sats"; + } + case AmountUnit.pico: + if (coin == Coin.ethereum) { + return "mwei"; + } else if (coin == Coin.wownero || coin == Coin.monero) { + return "p${coin.ticker}"; + } else { + return "invalid"; + } + case AmountUnit.femto: + if (coin == Coin.ethereum) { + return "kwei"; + } else { + return "invalid"; + } + case AmountUnit.atto: + if (coin == Coin.ethereum) { + return "wei"; + } else { + return "invalid"; + } + } + } + + String displayAmount({ + required Amount amount, + required String locale, + required Coin coin, + required int maxDecimalPlaces, + }) { + assert(maxDecimalPlaces >= 0); + // ensure we don't shift past minimum atomic value + final realShift = math.min(shift, amount.fractionDigits); + + // shifted to unit + final Decimal shifted = amount.decimal.shift(realShift); + + // get shift int value without fractional value + final BigInt wholeNumber = shifted.toBigInt(); + + // get decimal places to display + final int places = math.max(0, amount.fractionDigits - realShift); + + // start building the return value with just the whole value + String returnValue = wholeNumber.toString(); + + // if any decimal places should be shown continue building the return value + if (places > 0) { + // get the fractional value + final Decimal fraction = shifted - shifted.truncate(); + + // get final decimal based on max precision wanted + final int actualDecimalPlaces = math.min(places, maxDecimalPlaces); + + // get remainder string without the prepending "0." + String remainder = fraction.toString().substring(2); + + if (remainder.length > actualDecimalPlaces) { + // trim unwanted trailing digits + remainder = remainder.substring(0, actualDecimalPlaces); + } else if (remainder.length < actualDecimalPlaces) { + // pad with zeros to achieve requested precision + for (int i = remainder.length; i < actualDecimalPlaces; i++) { + remainder += "0"; + } + } + + // get decimal separator based on locale + final String separator = + (numberFormatSymbols[locale] as NumberSymbols?)?.DECIMAL_SEP ?? + (numberFormatSymbols[locale.substring(0, 2)] as NumberSymbols?) + ?.DECIMAL_SEP ?? + "."; + + // append separator and fractional amount + returnValue += "$separator$remainder"; + } + + // return the value with the proper unit symbol + return "$returnValue ${unitForCoin(coin)}"; + } +} diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index b02c6584e..2a0909966 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -1,7 +1,8 @@ import 'package:flutter/material.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/theme/color_theme.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; +import 'package:stackwallet/services/exchange/majestic_bank/majestic_bank_exchange.dart'; +import 'package:stackwallet/services/exchange/simpleswap/simpleswap_exchange.dart'; +import 'package:stackwallet/services/exchange/trocador/trocador_exchange.dart'; abstract class Assets { static const svg = _SVG(); @@ -9,73 +10,81 @@ abstract class Assets { static const lottie = _ANIMATIONS(); static const socials = _SOCIALS(); static const exchange = _EXCHANGE(); + static const buy = _BUY(); } class _SOCIALS { const _SOCIALS(); - String get discord => "assets/svg/socials/discord.svg"; - String get reddit => "assets/svg/socials/reddit-alien-brands.svg"; - String get twitter => "assets/svg/socials/twitter-brands.svg"; - String get telegram => "assets/svg/socials/telegram-brands.svg"; + static const _path = "assets/svg/socials/"; + + String get discord => "${_path}discord.svg"; + String get reddit => "${_path}reddit-alien-brands.svg"; + String get twitter => "${_path}twitter-brands.svg"; + String get telegram => "${_path}telegram-brands.svg"; } class _EXCHANGE { const _EXCHANGE(); - String get changeNow => "assets/svg/exchange_icons/change_now_logo_1.svg"; - String get simpleSwap => "assets/svg/exchange_icons/simpleswap-icon.svg"; + static const _path = "assets/svg/exchange_icons/"; + + String get changeNow => "${_path}change_now_logo_1.svg"; + String get simpleSwap => "${_path}simpleswap-icon.svg"; + String get majesticBankBlue => "${_path}mb_blue.svg"; + String get majesticBankGreen => "${_path}mb_green.svg"; + String get trocador => "${_path}trocador.svg"; + + String getIconFor({required String exchangeName}) { + switch (exchangeName) { + case SimpleSwapExchange.exchangeName: + return simpleSwap; + case ChangeNowExchange.exchangeName: + return changeNow; + case MajesticBankExchange.exchangeName: + return majesticBankBlue; + case TrocadorExchange.exchangeName: + return trocador; + default: + throw ArgumentError("Invalid exchange name passed to " + "Assets.exchange.getIconFor()"); + } + } +} + +class _BUY { + const _BUY(); + + String simplexLogo(BuildContext context) { + switch (MediaQuery.of(context).platformBrightness) { + case Brightness.dark: + return "assets/svg/buy/Simplex-Nuvei-Logo-light.svg"; + + case Brightness.light: + return "assets/svg/buy/Simplex-Nuvei-Logo.svg"; + } + } +} + +class _COIN_CONTROL { + const _COIN_CONTROL(); + + static const _path = "assets/svg/coin_control/"; + + String get blocked => "${_path}frozen.svg"; + String get unBlocked => "${_path}unfrozen.svg"; + String get gamePad => "${_path}gamepad.svg"; + String get selected => "${_path}selected.svg"; } class _SVG { const _SVG(); - String? background(BuildContext context) { - switch (Theme.of(context).extension()!.themeType) { - case ThemeType.light: - case ThemeType.dark: - return null; - case ThemeType.oceanBreeze: - return "assets/svg/${Theme.of(context).extension()!.themeType.name}/bg.svg"; - } - } - - String bellNew(BuildContext context) => - "assets/svg/${Theme.of(context).extension()!.themeType.name}/bell-new.svg"; - String stackIcon(BuildContext context) => - "assets/svg/${Theme.of(context).extension()!.themeType.name}/stack-icon1.svg"; - String exchange(BuildContext context) => - "assets/svg/${Theme.of(context).extension()!.themeType.name}/exchange-2.svg"; - String buy(BuildContext context) => - "assets/svg/${Theme.of(context).extension()!.themeType.name}/buy-coins-icon.svg"; - - String receive(BuildContext context) => - "assets/svg/${Theme.of(context).extension()!.themeType.name}/tx-icon-receive.svg"; - String receivePending(BuildContext context) => - "assets/svg/${Theme.of(context).extension()!.themeType.name}/tx-icon-receive-pending.svg"; - String receiveCancelled(BuildContext context) => - "assets/svg/${Theme.of(context).extension()!.themeType.name}/tx-icon-receive-failed.svg"; - - String send(BuildContext context) => - "assets/svg/${Theme.of(context).extension()!.themeType.name}/tx-icon-send.svg"; - String sendPending(BuildContext context) => - "assets/svg/${Theme.of(context).extension()!.themeType.name}/tx-icon-send-pending.svg"; - String sendCancelled(BuildContext context) => - "assets/svg/${Theme.of(context).extension()!.themeType.name}/tx-icon-send-failed.svg"; - - String txExchange(BuildContext context) => - "assets/svg/${Theme.of(context).extension()!.themeType.name}/tx-exchange-icon.svg"; - String txExchangePending(BuildContext context) => - "assets/svg/${Theme.of(context).extension()!.themeType.name}/tx-exchange-icon-pending.svg"; - String txExchangeFailed(BuildContext context) => - "assets/svg/${Theme.of(context).extension()!.themeType.name}/tx-exchange-icon-failed.svg"; - - String get themeOcean => "assets/svg/ocean-breeze-theme.svg"; - String get themeLight => "assets/svg/light-mode.svg"; - String get themeDark => "assets/svg/dark-theme.svg"; + final coinControl = const _COIN_CONTROL(); String get circleSliders => "assets/svg/configuration.svg"; String get circlePlus => "assets/svg/plus-circle.svg"; + String get circlePlusFilled => "assets/svg/circle-plus-filled.svg"; String get framedGear => "assets/svg/framed-gear.svg"; String get framedAddressBook => "assets/svg/framed-address-book.svg"; String get circleNode => "assets/svg/node-circle.svg"; @@ -87,8 +96,6 @@ class _SVG { String get enableButton => "assets/svg/enabled-button.svg"; String get disableButton => "assets/svg/Button.svg"; String get polygon => "assets/svg/Polygon.svg"; - String get personaIncognito => "assets/svg/persona-incognito-1.svg"; - String get personaEasy => "assets/svg/persona-easy-1.svg"; String get drd => "assets/svg/drd-icon.svg"; String get boxAuto => "assets/svg/box-auto.svg"; String get plus => "assets/svg/plus.svg"; @@ -107,7 +114,7 @@ class _SVG { String get pending => "assets/svg/pending.svg"; String get radio => "assets/svg/signal-stream.svg"; String get arrowRotate => "assets/svg/arrow-rotate.svg"; - String get arrowRotate2 => "assets/svg/arrow-rotate2.svg"; + String get arrowsTwoWay => "assets/svg/arrow-rotate2.svg"; String get alertCircle => "assets/svg/alert-circle.svg"; String get checkCircle => "assets/svg/circle-check.svg"; String get clipboard => "assets/svg/clipboard.svg"; @@ -123,7 +130,6 @@ class _SVG { String get networkWired => "assets/svg/network-wired-2.svg"; String get addressBook => "assets/svg/address-book.svg"; String get addressBook2 => "assets/svg/address-book2.svg"; - String get arrowRotate3 => "assets/svg/rotate-exclamation.svg"; String get delete => "assets/svg/delete.svg"; String get arrowRight => "assets/svg/arrow-right.svg"; String get dollarSign => "assets/svg/dollar-sign.svg"; @@ -134,6 +140,8 @@ class _SVG { String get thickX => "assets/svg/x-fat.svg"; String get x => "assets/svg/x.svg"; String get user => "assets/svg/user.svg"; + String get userPlus => "assets/svg/user-plus.svg"; + String get userMinus => "assets/svg/user-minus.svg"; String get trash => "assets/svg/trash.svg"; String get eye => "assets/svg/eye.svg"; String get eyeSlash => "assets/svg/eye-slash.svg"; @@ -167,125 +175,68 @@ class _SVG { String get exitDesktop => "assets/svg/exit-desktop.svg"; String get keys => "assets/svg/keys.svg"; String get arrowDown => "assets/svg/arrow-down.svg"; + String get robotHead => "assets/svg/robot-head.svg"; + String get whirlPool => "assets/svg/whirlpool.svg"; + String get fingerprint => "assets/svg/fingerprint.svg"; + String get faceId => "assets/svg/faceid.svg"; + String get tokens => "assets/svg/tokens.svg"; + String get circlePlusDark => "assets/svg/circle-plus.svg"; + String get creditCard => "assets/svg/cc.svg"; + String get file => "assets/svg/file.svg"; + String get fileUpload => "assets/svg/file-upload.svg"; String get ellipse1 => "assets/svg/Ellipse-43.svg"; String get ellipse2 => "assets/svg/Ellipse-42.svg"; - - String get bitcoin => "assets/svg/coin_icons/Bitcoin.svg"; - String get litecoin => "assets/svg/coin_icons/Litecoin.svg"; - String get bitcoincash => "assets/svg/coin_icons/Bitcoincash.svg"; - String get dogecoin => "assets/svg/coin_icons/Dogecoin.svg"; - String get epicCash => "assets/svg/coin_icons/EpicCash.svg"; - String get firo => "assets/svg/coin_icons/Firo.svg"; - String get monero => "assets/svg/coin_icons/Monero.svg"; - String get wownero => "assets/svg/coin_icons/Wownero.svg"; - String get namecoin => "assets/svg/coin_icons/Namecoin.svg"; - String get particl => "assets/svg/coin_icons/Particl.svg"; - String get chevronRight => "assets/svg/chevron-right.svg"; String get minimize => "assets/svg/minimize.svg"; String get walletFa => "assets/svg/wallet-fa.svg"; String get exchange3 => "assets/svg/exchange-3.svg"; String get messageQuestion => "assets/svg/message-question-1.svg"; + String get list => "assets/svg/list-ul.svg"; + String get unclaimedPaynym => "assets/svg/unclaimed.png"; + + String get trocadorRatingA => "assets/svg/trocador_rating_a.svg"; + String get trocadorRatingB => "assets/svg/trocador_rating_b.svg"; + String get trocadorRatingC => "assets/svg/trocador_rating_c.svg"; + String get trocadorRatingD => "assets/svg/trocador_rating_d.svg"; // TODO provide proper assets String get bitcoinTestnet => "assets/svg/coin_icons/Bitcoin.svg"; String get bitcoincashTestnet => "assets/svg/coin_icons/Bitcoincash.svg"; String get firoTestnet => "assets/svg/coin_icons/Firo.svg"; String get dogecoinTestnet => "assets/svg/coin_icons/Dogecoin.svg"; - String get particlTestnet => - "assets/svg/coin_icons/Dogecoin.svg"; //TODO - Update icon to particl + String get particlTestnet => "assets/svg/coin_icons/Particl.svg"; - String iconFor({required Coin coin}) { - switch (coin) { - case Coin.bitcoin: - return bitcoin; - case Coin.litecoin: - case Coin.litecoinTestNet: - return litecoin; - case Coin.bitcoincash: - return bitcoincash; - case Coin.dogecoin: - return dogecoin; - case Coin.epicCash: - return epicCash; - case Coin.firo: - return firo; - case Coin.monero: - return monero; - case Coin.wownero: - return wownero; - case Coin.namecoin: - return namecoin; - case Coin.particl: - return particl; - case Coin.bitcoinTestNet: - return bitcoinTestnet; - case Coin.bitcoincashTestnet: - return bitcoincashTestnet; - case Coin.firoTestNet: - return firoTestnet; - case Coin.dogecoinTestNet: - return dogecoinTestnet; - } - } + // small icons + String get bitcoin => "assets/svg/coin_icons/Bitcoin.svg"; + String get litecoin => "assets/svg/coin_icons/Litecoin.svg"; + String get bitcoincash => "assets/svg/coin_icons/Bitcoincash.svg"; + String get dogecoin => "assets/svg/coin_icons/Dogecoin.svg"; + String get epicCash => "assets/svg/coin_icons/EpicCash.svg"; + String get ethereum => "assets/svg/coin_icons/Ethereum.svg"; + String get firo => "assets/svg/coin_icons/Firo.svg"; + String get monero => "assets/svg/coin_icons/Monero.svg"; + String get wownero => "assets/svg/coin_icons/Wownero.svg"; + String get namecoin => "assets/svg/coin_icons/Namecoin.svg"; + String get particl => "assets/svg/coin_icons/Particl.svg"; + + String get bnbIcon => "assets/svg/coin_icons/bnb_icon.svg"; } class _PNG { const _PNG(); - String get stack => "assets/images/stack.png"; String get splash => "assets/images/splash.png"; - String get monero => "assets/images/monero.png"; - String get wownero => "assets/images/wownero.png"; - String get firo => "assets/images/firo.png"; - String get dogecoin => "assets/images/doge.png"; - String get bitcoin => "assets/images/bitcoin.png"; - String get litecoin => "assets/images/litecoin.png"; - String get epicCash => "assets/images/epic-cash.png"; - String get bitcoincash => "assets/images/bitcoincash.png"; - String get namecoin => "assets/images/namecoin.png"; - String get particl => "assets/images/particl.png"; - String get glasses => "assets/images/glasses.png"; String get glassesHidden => "assets/images/glasses-hidden.png"; - - String imageFor({required Coin coin}) { - switch (coin) { - case Coin.bitcoin: - case Coin.bitcoinTestNet: - return bitcoin; - case Coin.litecoin: - case Coin.litecoinTestNet: - return litecoin; - case Coin.bitcoincash: - case Coin.bitcoincashTestnet: - return bitcoincash; - case Coin.dogecoin: - case Coin.dogecoinTestNet: - return dogecoin; - case Coin.epicCash: - return epicCash; - case Coin.firo: - return firo; - case Coin.firoTestNet: - return firo; - case Coin.monero: - return monero; - case Coin.wownero: - return wownero; - case Coin.namecoin: - return namecoin; - case Coin.particl: - return particl; - } - } } class _ANIMATIONS { const _ANIMATIONS(); - String get test => "assets/lottie/test.json"; String get test2 => "assets/lottie/test2.json"; + String get iconSend => "assets/lottie/icon_send.json"; + String get loaderAndCheckmark => "assets/lottie/loader_and_checkmark.json"; + String get arrowRotate => "assets/lottie/arrow_rotate.json"; } diff --git a/lib/utilities/bip32_utils.dart b/lib/utilities/bip32_utils.dart new file mode 100644 index 000000000..71dcf7fcb --- /dev/null +++ b/lib/utilities/bip32_utils.dart @@ -0,0 +1,128 @@ +import 'package:bip32/bip32.dart' as bip32; +import 'package:bip39/bip39.dart' as bip39; +import 'package:bitcoindart/bitcoindart.dart'; +import 'package:flutter/foundation.dart'; +import 'package:tuple/tuple.dart'; + +abstract class Bip32Utils { + // =============================== get root ================================== + static bip32.BIP32 getBip32RootSync( + String mnemonic, + String mnemonicPassphrase, + NetworkType networkType, + ) { + final seed = bip39.mnemonicToSeed(mnemonic, passphrase: mnemonicPassphrase); + final _networkType = bip32.NetworkType( + wif: networkType.wif, + bip32: bip32.Bip32Type( + public: networkType.bip32.public, + private: networkType.bip32.private, + ), + ); + + final root = bip32.BIP32.fromSeed(seed, _networkType); + return root; + } + + static Future getBip32Root( + String mnemonic, + String mnemonicPassphrase, + NetworkType networkType, + ) async { + final root = await compute( + _getBip32RootWrapper, + Tuple3( + mnemonic, + mnemonicPassphrase, + networkType, + ), + ); + return root; + } + + /// wrapper for compute() + static bip32.BIP32 _getBip32RootWrapper( + Tuple3 args, + ) { + return getBip32RootSync( + args.item1, + args.item2, + args.item3, + ); + } + + // =========================== get node from root ============================ + static bip32.BIP32 getBip32NodeFromRootSync( + bip32.BIP32 root, + String derivePath, + ) { + return root.derivePath(derivePath); + } + + static Future getBip32NodeFromRoot( + bip32.BIP32 root, + String derivePath, + ) async { + final node = await compute( + _getBip32NodeFromRootWrapper, + Tuple2( + root, + derivePath, + ), + ); + return node; + } + + /// wrapper for compute() + static bip32.BIP32 _getBip32NodeFromRootWrapper( + Tuple2 args, + ) { + return getBip32NodeFromRootSync( + args.item1, + args.item2, + ); + } + + // =============================== get node ================================== + static bip32.BIP32 getBip32NodeSync( + String mnemonic, + String mnemonicPassphrase, + NetworkType network, + String derivePath, + ) { + final root = getBip32RootSync(mnemonic, mnemonicPassphrase, network); + + final node = getBip32NodeFromRootSync(root, derivePath); + return node; + } + + static Future getBip32Node( + String mnemonic, + String mnemonicPassphrase, + NetworkType networkType, + String derivePath, + ) async { + final node = await compute( + _getBip32NodeWrapper, + Tuple4( + mnemonic, + mnemonicPassphrase, + networkType, + derivePath, + ), + ); + return node; + } + + /// wrapper for compute() + static bip32.BIP32 _getBip32NodeWrapper( + Tuple4 args, + ) { + return getBip32NodeSync( + args.item1, + args.item2, + args.item3, + args.item4, + ); + } +} diff --git a/lib/utilities/bip47_utils.dart b/lib/utilities/bip47_utils.dart new file mode 100644 index 000000000..6433f4e1d --- /dev/null +++ b/lib/utilities/bip47_utils.dart @@ -0,0 +1,37 @@ +import 'dart:typed_data'; + +import 'package:bip47/src/util.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/output.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart'; + +abstract class Bip47Utils { + /// looks at tx outputs and returns a blinded payment code if found + static Uint8List? getBlindedPaymentCodeBytesFrom(Transaction transaction) { + for (int i = 0; i < transaction.outputs.length; i++) { + final bytes = getBlindedPaymentCodeBytesFromOutput( + transaction.outputs.elementAt(i)); + if (bytes != null) { + return bytes; + } + } + + return null; + } + + static Uint8List? getBlindedPaymentCodeBytesFromOutput(Output output) { + Uint8List? blindedCodeBytes; + + List? scriptChunks = output.scriptPubKeyAsm?.split(" "); + if (scriptChunks?.length == 2 && scriptChunks?[0] == "OP_RETURN") { + final blindedPaymentCode = scriptChunks![1]; + final bytes = blindedPaymentCode.fromHex; + + // https://en.bitcoin.it/wiki/BIP_0047#Sending + if (bytes.length == 80 && bytes.first == 1) { + blindedCodeBytes = bytes; + } + } + + return blindedCodeBytes; + } +} diff --git a/lib/utilities/block_explorers.dart b/lib/utilities/block_explorers.dart index 4b406b704..cf0628fd2 100644 --- a/lib/utilities/block_explorers.dart +++ b/lib/utilities/block_explorers.dart @@ -1,25 +1,31 @@ +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/models/isar/models/block_explorer.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -Uri getBlockExplorerTransactionUrlFor({ +Uri getDefaultBlockExplorerUrlFor({ required Coin coin, required String txid, }) { switch (coin) { case Coin.bitcoin: - return Uri.parse("https://chain.so/tx/BTC/$txid"); + return Uri.parse("https://mempool.space/tx/$txid"); case Coin.litecoin: return Uri.parse("https://chain.so/tx/LTC/$txid"); case Coin.litecoinTestNet: return Uri.parse("https://chain.so/tx/LTCTEST/$txid"); case Coin.bitcoinTestNet: - return Uri.parse("https://chain.so/tx/BTCTEST/$txid"); + return Uri.parse("https://mempool.space/testnet/tx/$txid"); case Coin.dogecoin: return Uri.parse("https://chain.so/tx/DOGE/$txid"); + case Coin.eCash: + return Uri.parse("https://explorer.bitcoinabc.org/tx/$txid"); case Coin.dogecoinTestNet: return Uri.parse("https://chain.so/tx/DOGETEST/$txid"); case Coin.epicCash: // TODO: Handle this case. throw UnimplementedError("missing block explorer for epic cash"); + case Coin.ethereum: + return Uri.parse("https://etherscan.io/tx/$txid"); case Coin.monero: return Uri.parse("https://xmrchain.net/tx/$txid"); case Coin.wownero: @@ -39,3 +45,29 @@ Uri getBlockExplorerTransactionUrlFor({ return Uri.parse("https://chainz.cryptoid.info/part/tx.dws?$txid.htm"); } } + +/// returns internal Isar ID for the inserted object/record +Future setBlockExplorerForCoin({ + required Coin coin, + required Uri url, +}) async { + return await MainDB.instance.putTransactionBlockExplorer( + TransactionBlockExplorer( + ticker: coin.ticker, + url: url.toString(), + ), + ); +} + +Uri getBlockExplorerTransactionUrlFor({ + required Coin coin, + required String txid, +}) { + String? url = MainDB.instance.getTransactionBlockExplorer(coin: coin)?.url; + if (url == null) { + return getDefaultBlockExplorerUrlFor(coin: coin, txid: txid); + } else { + url = url.replaceAll("%5BTXID%5D", txid); + return Uri.parse(url); + } +} diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index 740e40c24..bab8f3348 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -21,14 +21,20 @@ abstract class Constants { } static bool enableExchange = Util.isDesktop || !Platform.isIOS; + // just use enable exchange flag + // static bool enableBuy = enableExchange; + // // true; // true for development, - //TODO: correct for monero? + static const int _satsPerCoinEthereum = 1000000000000000000; + static const int _satsPerCoinECash = 100; static const int _satsPerCoinMonero = 1000000000000; static const int _satsPerCoinWownero = 100000000000; static const int _satsPerCoin = 100000000; static const int _decimalPlaces = 8; static const int _decimalPlacesWownero = 11; static const int _decimalPlacesMonero = 12; + static const int _decimalPlacesEthereum = 18; + static const int _decimalPlacesECash = 2; static const int notificationsMax = 0xFFFFFFFF; static const Duration networkAliveTimerDuration = Duration(seconds: 10); @@ -38,7 +44,9 @@ abstract class Constants { // Enable Logger.print statements static const bool disableLogger = false; - static const int currentHiveDbVersion = 4; + static const int currentDataVersion = 10; + + static const int rescanV1 = 1; static int satsPerCoin(Coin coin) { switch (coin) { @@ -62,6 +70,12 @@ abstract class Constants { case Coin.monero: return _satsPerCoinMonero; + + case Coin.ethereum: + return _satsPerCoinEthereum; + + case Coin.eCash: + return _satsPerCoinECash; } } @@ -87,6 +101,12 @@ abstract class Constants { case Coin.monero: return _decimalPlacesMonero; + + case Coin.ethereum: + return _decimalPlacesEthereum; + + case Coin.eCash: + return _decimalPlacesECash; } } @@ -103,7 +123,9 @@ abstract class Constants { case Coin.bitcoinTestNet: case Coin.dogecoinTestNet: case Coin.firoTestNet: + case Coin.eCash: case Coin.epicCash: + case Coin.ethereum: case Coin.namecoin: case Coin.particl: values.addAll([24, 21, 18, 15, 12]); @@ -124,10 +146,9 @@ abstract class Constants { switch (coin) { case Coin.bitcoin: case Coin.bitcoinTestNet: - return 600; - case Coin.bitcoincash: case Coin.bitcoincashTestnet: + case Coin.eCash: return 600; case Coin.dogecoin: @@ -145,6 +166,9 @@ abstract class Constants { case Coin.epicCash: return 60; + case Coin.ethereum: + return 15; + case Coin.monero: return 120; diff --git a/lib/utilities/db_version_migration.dart b/lib/utilities/db_version_migration.dart index 1320fab25..a83ea4305 100644 --- a/lib/utilities/db_version_migration.dart +++ b/lib/utilities/db_version_migration.dart @@ -1,19 +1,30 @@ import 'package:hive/hive.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/db/hive/db.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; -import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/models/contact.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; -import 'package:stackwallet/models/lelantus_coin.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/address.dart'; +import 'package:stackwallet/models/isar/models/contact_entry.dart' + as isar_contact; +import 'package:stackwallet/models/isar/models/isar_models.dart' as isar_models; +import 'package:stackwallet/models/models.dart'; import 'package:stackwallet/models/node_model.dart'; +import 'package:stackwallet/services/mixins/wallet_db.dart'; import 'package:stackwallet/services/node_service.dart'; import 'package:stackwallet/services/wallets_service.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/default_nodes.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/prefs.dart'; +import 'package:tuple/tuple.dart'; -class DbVersionMigrator { +class DbVersionMigrator with WalletDB { Future migrate( int fromVersion, { required SecureStorageInterface secureStore, @@ -169,9 +180,362 @@ class DbVersionMigrator { // try to continue migrating return await migrate(4, secureStore: secureStore); + case 4: + // migrate + await _v4(secureStore); + + // update version + await DB.instance.put( + boxName: DB.boxNameDBInfo, key: "hive_data_version", value: 5); + + // try to continue migrating + return await migrate(5, secureStore: secureStore); + + case 5: + // migrate + await Hive.openBox("theme"); + await Hive.openBox(DB.boxNamePrefs); + + final themeName = + DB.instance.get(boxName: "theme", key: "colorScheme") + as String? ?? + "light"; + + await DB.instance.put( + boxName: DB.boxNamePrefs, key: "theme", value: themeName); + + // update version + await DB.instance.put( + boxName: DB.boxNameDBInfo, key: "hive_data_version", value: 6); + + // try to continue migrating + return await migrate(6, secureStore: secureStore); + + case 6: + // migrate + await MainDB.instance.initMainDB(); + final count = await MainDB.instance.isar.addresses.count(); + // add change/receiving tags to address labels + for (var i = 0; i < count; i += 50) { + final addresses = await MainDB.instance.isar.addresses + .where() + .offset(i) + .limit(50) + .findAll(); + + final List labels = []; + for (final address in addresses) { + List? tags; + switch (address.subType) { + case AddressSubType.receiving: + tags = ["receiving"]; + break; + case AddressSubType.change: + tags = ["change"]; + break; + case AddressSubType.paynymNotification: + tags = ["paynym notification"]; + break; + case AddressSubType.paynymSend: + break; + case AddressSubType.paynymReceive: + tags = ["paynym receiving"]; + break; + case AddressSubType.unknown: + break; + case AddressSubType.nonWallet: + break; + } + + // update/create label if tags is not empty + if (tags != null) { + isar_models.AddressLabel? label = await MainDB + .instance.isar.addressLabels + .where() + .addressStringWalletIdEqualTo(address.value, address.walletId) + .findFirst(); + if (label == null) { + label = isar_models.AddressLabel( + walletId: address.walletId, + value: "", + addressString: address.value, + tags: tags, + ); + } else if (label.tags == null) { + label = label.copyWith(tags: tags); + } + labels.add(label); + } + } + + if (labels.isNotEmpty) { + await MainDB.instance.isar.writeTxn(() async { + await MainDB.instance.isar.addressLabels.putAll(labels); + }); + } + } + + // update version + await DB.instance.put( + boxName: DB.boxNameDBInfo, key: "hive_data_version", value: 7); + + // try to continue migrating + return await migrate(7, secureStore: secureStore); + + case 7: + // migrate + await _v7(secureStore); + + // update version + await DB.instance.put( + boxName: DB.boxNameDBInfo, key: "hive_data_version", value: 8); + + // try to continue migrating + return await migrate(8, secureStore: secureStore); + + case 8: + // migrate + await Hive.openBox(DB.boxNameAllWalletsData); + final walletsService = + WalletsService(secureStorageInterface: secureStore); + final walletInfoList = await walletsService.walletNames; + await MainDB.instance.initMainDB(); + for (final walletId in walletInfoList.keys) { + final info = walletInfoList[walletId]!; + if (info.coin == Coin.bitcoincash || + info.coin == Coin.bitcoincashTestnet) { + final ids = await MainDB.instance + .getAddresses(walletId) + .filter() + .typeEqualTo(isar_models.AddressType.p2sh) + .idProperty() + .findAll(); + + await MainDB.instance.isar.writeTxn(() async { + await MainDB.instance.isar.addresses.deleteAll(ids); + }); + } + } + + // update version + await DB.instance.put( + boxName: DB.boxNameDBInfo, key: "hive_data_version", value: 9); + + // try to continue migrating + return await migrate(9, secureStore: secureStore); + + case 9: + // migrate + await _v9(); + + // update version + await DB.instance.put( + boxName: DB.boxNameDBInfo, key: "hive_data_version", value: 10); + + // try to continue migrating + return await migrate(10, secureStore: secureStore); + default: // finally return return; } } + + Future _v4(SecureStorageInterface secureStore) async { + await Hive.openBox(DB.boxNameAllWalletsData); + await Hive.openBox(DB.boxNamePrefs); + final walletsService = WalletsService(secureStorageInterface: secureStore); + final prefs = Prefs.instance; + final walletInfoList = await walletsService.walletNames; + await prefs.init(); + await MainDB.instance.initMainDB(); + + for (final walletId in walletInfoList.keys) { + final info = walletInfoList[walletId]!; + assert(info.walletId == walletId); + + final walletBox = await Hive.openBox(info.walletId); + + const receiveAddressesPrefix = "receivingAddresses"; + const changeAddressesPrefix = "changeAddresses"; + + // we need to manually migrate epic cash transactions as they are not + // stored on the epic cash blockchain + if (info.coin == Coin.epicCash) { + final txnData = walletBox.get("latest_tx_model") as TransactionData?; + + // we ever only used index 0 in the past + const rcvIndex = 0; + + final List> + transactionsData = []; + if (txnData != null) { + final txns = txnData.getAllTransactions(); + + for (final tx in txns.values) { + bool isIncoming = tx.txType == "Received"; + + final iTx = isar_models.Transaction( + walletId: walletId, + txid: tx.txid, + timestamp: tx.timestamp, + type: isIncoming + ? isar_models.TransactionType.incoming + : isar_models.TransactionType.outgoing, + subType: isar_models.TransactionSubType.none, + amount: tx.amount, + amountString: Amount( + rawValue: BigInt.from(tx.amount), + fractionDigits: info.coin.decimals, + ).toJsonString(), + fee: tx.fees, + height: tx.height, + isCancelled: tx.isCancelled, + isLelantus: false, + slateId: tx.slateId, + otherData: tx.otherData, + nonce: null, + inputs: [], + outputs: [], + ); + + if (tx.address.isEmpty) { + transactionsData.add(Tuple2(iTx, null)); + } else { + final address = isar_models.Address( + walletId: walletId, + value: tx.address, + publicKey: [], + derivationIndex: isIncoming ? rcvIndex : -1, + derivationPath: null, + type: isIncoming + ? isar_models.AddressType.mimbleWimble + : isar_models.AddressType.unknown, + subType: isIncoming + ? isar_models.AddressSubType.receiving + : isar_models.AddressSubType.unknown, + ); + transactionsData.add(Tuple2(iTx, address)); + } + } + } + await MainDB.instance.addNewTransactionData(transactionsData, walletId); + } + + // delete data from hive + await walletBox.delete(receiveAddressesPrefix); + await walletBox.delete("${receiveAddressesPrefix}P2PKH"); + await walletBox.delete("${receiveAddressesPrefix}P2SH"); + await walletBox.delete("${receiveAddressesPrefix}P2WPKH"); + await walletBox.delete(changeAddressesPrefix); + await walletBox.delete("${changeAddressesPrefix}P2PKH"); + await walletBox.delete("${changeAddressesPrefix}P2SH"); + await walletBox.delete("${changeAddressesPrefix}P2WPKH"); + await walletBox.delete("latest_tx_model"); + await walletBox.delete("latest_lelantus_tx_model"); + + // set empty mnemonic passphrase as we used that by default before + if ((await secureStore.read(key: '${walletId}_mnemonicPassphrase')) == + null) { + await secureStore.write( + key: '${walletId}_mnemonicPassphrase', value: ""); + } + + // doing this for epic cash will delete transaction history as it is not + // stored on the epic cash blockchain + if (info.coin != Coin.epicCash) { + // set flag to initiate full rescan on opening wallet + await DB.instance.put( + boxName: DB.boxNameDBInfo, + key: "rescan_on_open_$walletId", + value: Constants.rescanV1, + ); + } + } + } + + Future _v7(SecureStorageInterface secureStore) async { + await Hive.openBox(DB.boxNameAllWalletsData); + final walletsService = WalletsService(secureStorageInterface: secureStore); + final walletInfoList = await walletsService.walletNames; + await MainDB.instance.initMainDB(); + + for (final walletId in walletInfoList.keys) { + final info = walletInfoList[walletId]!; + assert(info.walletId == walletId); + + final count = await MainDB.instance.getTransactions(walletId).count(); + + for (var i = 0; i < count; i += 50) { + final txns = await MainDB.instance + .getTransactions(walletId) + .offset(i) + .limit(50) + .findAll(); + + // migrate amount to serialized amount string + final txnsData = txns + .map( + (tx) => Tuple2( + tx + ..amountString = Amount( + rawValue: BigInt.from(tx.amount), + fractionDigits: info.coin.decimals, + ).toJsonString(), + tx.address.value, + ), + ) + .toList(); + + // update db records + await MainDB.instance.addNewTransactionData(txnsData, walletId); + } + } + } + + Future _v9() async { + final addressBookBox = await Hive.openBox(DB.boxNameAddressBook); + await MainDB.instance.initMainDB(); + + final keys = List.from(addressBookBox.keys); + final contacts = keys + .map((id) => Contact.fromJson( + Map.from( + addressBookBox.get(id) as Map, + ), + )) + .toList(growable: false); + + final List newContacts = []; + + for (final contact in contacts) { + final List newContactAddressEntries = + []; + + for (final entry in contact.addresses) { + newContactAddressEntries.add( + isar_contact.ContactAddressEntry() + ..coinName = entry.coin.name + ..address = entry.address + ..label = entry.label + ..other = entry.other, + ); + } + + final newContact = isar_contact.ContactEntry( + name: contact.name, + addresses: newContactAddressEntries, + isFavorite: contact.isFavorite, + customId: contact.id, + ); + + newContacts.add(newContact); + } + + await MainDB.instance.isar.writeTxn(() async { + await MainDB.instance.isar.contactEntrys.putAll(newContacts); + }); + + await addressBookBox.deleteFromDisk(); + } } diff --git a/lib/utilities/default_epicboxes.dart b/lib/utilities/default_epicboxes.dart new file mode 100644 index 000000000..ecbd4524a --- /dev/null +++ b/lib/utilities/default_epicboxes.dart @@ -0,0 +1,43 @@ +import 'package:stackwallet/models/epicbox_server_model.dart'; + +abstract class DefaultEpicBoxes { + static const String defaultName = "Default"; + + static List get all => [americas, asia, europe]; + static List get defaultIds => ['americas', 'asia', 'europe']; + + static EpicBoxServerModel get americas => EpicBoxServerModel( + host: 'epicbox.epic.tech', + port: 443, + name: 'Americas', + id: 'americas', + useSSL: true, + enabled: true, + isFailover: true, + isDown: false, + ); + + static EpicBoxServerModel get asia => EpicBoxServerModel( + host: 'epicbox.hyperbig.com', + port: 443, + name: 'Asia', + id: 'asia', + useSSL: true, + enabled: true, + isFailover: true, + isDown: false, + ); + + static EpicBoxServerModel get europe => EpicBoxServerModel( + host: 'epicbox.fastepic.eu', + port: 443, + name: 'Europe', + id: 'europe', + useSSL: true, + enabled: true, + isFailover: true, + isDown: false, + ); + + static final defaultEpicBoxServer = americas; +} diff --git a/lib/utilities/default_eth_tokens.dart b/lib/utilities/default_eth_tokens.dart new file mode 100644 index 000000000..eb04118bc --- /dev/null +++ b/lib/utilities/default_eth_tokens.dart @@ -0,0 +1,41 @@ +import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; + +abstract class DefaultTokens { + static List list = [ + EthContract( + address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", + name: "USD Coin", + symbol: "USDC", + decimals: 6, + type: EthContractType.erc20, + ), + EthContract( + address: "0xdac17f958d2ee523a2206206994597c13d831ec7", + name: "Tether", + symbol: "USDT", + decimals: 6, + type: EthContractType.erc20, + ), + EthContract( + address: "0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce", + name: "Shiba Inu", + symbol: "SHIB", + decimals: 18, + type: EthContractType.erc20, + ), + EthContract( + address: "0x514910771af9ca656af840dff83e8264ecf986ca", + name: "Chainlink", + symbol: "LINK", + decimals: 18, + type: EthContractType.erc20, + ), + EthContract( + address: "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", + name: "Uniswap", + symbol: "UNI", + decimals: 18, + type: EthContractType.erc20, + ), + ]; +} diff --git a/lib/utilities/default_nodes.dart b/lib/utilities/default_nodes.dart index f4eb41b7c..0f8425c88 100644 --- a/lib/utilities/default_nodes.dart +++ b/lib/utilities/default_nodes.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; @@ -14,7 +12,9 @@ abstract class DefaultNodes { dogecoin, firo, monero, + eCash, epicCash, + ethereum, bitcoincash, namecoin, wownero, @@ -108,6 +108,7 @@ abstract class DefaultNodes { coinName: Coin.monero.name, isFailover: true, isDown: false, + trusted: true, ); static NodeModel get wownero => NodeModel( @@ -120,6 +121,7 @@ abstract class DefaultNodes { coinName: Coin.wownero.name, isFailover: true, isDown: false, + trusted: true, ); static NodeModel get epicCash => NodeModel( @@ -134,6 +136,18 @@ abstract class DefaultNodes { isDown: false, ); + static NodeModel get ethereum => NodeModel( + host: "https://eth.stackwallet.com", + port: 443, + name: defaultName, + id: _nodeId(Coin.ethereum), + useSSL: true, + enabled: true, + coinName: Coin.ethereum.name, + isFailover: true, + isDown: false, + ); + static NodeModel get namecoin => NodeModel( host: "namecoin.stackwallet.com", port: 57002, @@ -158,7 +172,7 @@ abstract class DefaultNodes { isDown: false); static NodeModel get bitcoinTestnet => NodeModel( - host: "electrumx-testnet.cypherstack.com", + host: "bitcoin-testnet.stackwallet.com", port: 51002, name: defaultName, id: _nodeId(Coin.bitcoinTestNet), @@ -205,6 +219,18 @@ abstract class DefaultNodes { isDown: false, ); + static NodeModel get eCash => NodeModel( + host: "electrum.bitcoinabc.org", + port: 50002, + name: defaultName, + id: _nodeId(Coin.eCash), + useSSL: true, + enabled: true, + coinName: Coin.eCash.name, + isFailover: true, + isDown: false, + ); + static NodeModel getNodeFor(Coin coin) { switch (coin) { case Coin.bitcoin: @@ -219,9 +245,15 @@ abstract class DefaultNodes { case Coin.dogecoin: return dogecoin; + case Coin.eCash: + return eCash; + case Coin.epicCash: return epicCash; + case Coin.ethereum: + return ethereum; + case Coin.firo: return firo; @@ -253,9 +285,4 @@ abstract class DefaultNodes { return dogecoinTestnet; } } - - static final String defaultEpicBoxConfig = jsonEncode({ - "domain": "209.127.179.199", - "port": 13420, - }); } diff --git a/lib/utilities/delete_everything.dart b/lib/utilities/delete_everything.dart index 497cd71f4..3c12fba85 100644 --- a/lib/utilities/delete_everything.dart +++ b/lib/utilities/delete_everything.dart @@ -1,4 +1,4 @@ -import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/utilities/logger.dart'; Future deleteEverything() async { @@ -21,7 +21,7 @@ Future deleteEverything() async { .deleteBoxFromDisk(boxName: DB.boxNameWalletsToDeleteOnStart); await DB.instance.deleteBoxFromDisk(boxName: DB.boxNamePriceCache); await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameDBInfo); - await DB.instance.deleteBoxFromDisk(boxName: DB.boxNameTheme); + await DB.instance.deleteBoxFromDisk(boxName: "theme"); return true; } catch (e, s) { Logging.instance.log("$e $s", level: LogLevel.Error); diff --git a/lib/utilities/desktop_password_service.dart b/lib/utilities/desktop_password_service.dart index 9ef83932b..dc32b8254 100644 --- a/lib/utilities/desktop_password_service.dart +++ b/lib/utilities/desktop_password_service.dart @@ -1,12 +1,15 @@ import 'package:hive/hive.dart'; import 'package:stack_wallet_backup/secure_storage.dart'; -import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/utilities/logger.dart'; const String _kKeyBlobKey = "swbKeyBlobKeyStringID"; +const String _kKeyBlobVersionKey = "swbKeyBlobVersionKeyStringID"; + +const int kLatestBlobVersion = 2; String _getMessageFromException(Object exception) { - if (exception is IncorrectPassphrase) { + if (exception is IncorrectPassphraseOrVersion) { return exception.errMsg(); } if (exception is BadDecryption) { @@ -18,6 +21,9 @@ String _getMessageFromException(Object exception) { if (exception is EncodingError) { return exception.errMsg(); } + if (exception is VersionError) { + return exception.errMsg(); + } return exception.toString(); } @@ -41,7 +47,10 @@ class DPS { } try { - _handler = await StorageCryptoHandler.fromNewPassphrase(passphrase); + _handler = await StorageCryptoHandler.fromNewPassphrase( + passphrase, + kLatestBlobVersion, + ); final box = await Hive.openBox(DB.boxNameDesktopData); await DB.instance.put( @@ -49,6 +58,7 @@ class DPS { key: _kKeyBlobKey, value: await _handler!.getKeyBlob(), ); + await _updateStoredKeyBlobVersion(kLatestBlobVersion); await box.close(); } catch (e, s) { Logging.instance.log( @@ -78,7 +88,24 @@ class DPS { } try { - _handler = await StorageCryptoHandler.fromExisting(passphrase, keyBlob); + final blobVersion = await _getStoredKeyBlobVersion(); + _handler = await StorageCryptoHandler.fromExisting( + passphrase, + keyBlob, + blobVersion, + ); + if (blobVersion < kLatestBlobVersion) { + // update blob + await _handler!.resetPassphrase(passphrase, kLatestBlobVersion); + final box = await Hive.openBox(DB.boxNameDesktopData); + await DB.instance.put( + boxName: DB.boxNameDesktopData, + key: _kKeyBlobKey, + value: await _handler!.getKeyBlob(), + ); + await _updateStoredKeyBlobVersion(kLatestBlobVersion); + await box.close(); + } } catch (e, s) { Logging.instance.log( "${_getMessageFromException(e)}\n$s", @@ -102,7 +129,8 @@ class DPS { } try { - await StorageCryptoHandler.fromExisting(passphrase, keyBlob); + final blobVersion = await _getStoredKeyBlobVersion(); + await StorageCryptoHandler.fromExisting(passphrase, keyBlob, blobVersion); // existing passphrase matches key blob return true; } catch (e, s) { @@ -135,8 +163,10 @@ class DPS { return false; } + final blobVersion = await _getStoredKeyBlobVersion(); + try { - await _handler!.resetPassphrase(passphraseNew); + await _handler!.resetPassphrase(passphraseNew, blobVersion); final box = await Hive.openBox(DB.boxNameDesktopData); await DB.instance.put( @@ -144,6 +174,7 @@ class DPS { key: _kKeyBlobKey, value: await _handler!.getKeyBlob(), ); + await _updateStoredKeyBlobVersion(blobVersion); await box.close(); // successfully updated passphrase @@ -164,4 +195,22 @@ class DPS { ); return keyBlob != null; } + + Future _getStoredKeyBlobVersion() async { + final box = await Hive.openBox(DB.boxNameDesktopData); + final keyBlobVersionString = DB.instance.get( + boxName: DB.boxNameDesktopData, + key: _kKeyBlobVersionKey, + ); + await box.close(); + return int.tryParse(keyBlobVersionString ?? "1") ?? 1; + } + + Future _updateStoredKeyBlobVersion(int version) async { + await DB.instance.put( + boxName: DB.boxNameDesktopData, + key: _kKeyBlobVersionKey, + value: version.toString(), + ); + } } diff --git a/lib/utilities/enums/coin_enum.dart b/lib/utilities/enums/coin_enum.dart index e90509305..f1109a626 100644 --- a/lib/utilities/enums/coin_enum.dart +++ b/lib/utilities/enums/coin_enum.dart @@ -3,26 +3,29 @@ import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart' as bch; import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart' as doge; +import 'package:stackwallet/services/coins/ecash/ecash_wallet.dart' as ecash; import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart' as epic; +import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart' + as eth; import 'package:stackwallet/services/coins/firo/firo_wallet.dart' as firo; import 'package:stackwallet/services/coins/litecoin/litecoin_wallet.dart' as ltc; import 'package:stackwallet/services/coins/monero/monero_wallet.dart' as xmr; import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart' as nmc; +import 'package:stackwallet/services/coins/particl/particl_wallet.dart' + as particl; import 'package:stackwallet/services/coins/wownero/wownero_wallet.dart' as wow; -import 'package:stackwallet/services/coins/particl/particl_wallet.dart' - as particl; -import 'package:stackwallet/utilities/util.dart'; -import 'package:stackwallet/services/coins/particl/particl_wallet.dart' - as particl; +import 'package:stackwallet/utilities/constants.dart'; enum Coin { bitcoin, bitcoincash, dogecoin, + eCash, epicCash, + ethereum, firo, litecoin, monero, @@ -42,7 +45,8 @@ enum Coin { firoTestNet, } -final int kTestNetCoinCount = Util.isDesktop ? 5 : 4; +final int kTestNetCoinCount = 4; // Util.isDesktop ? 5 : 4; +// remove firotestnet for now extension CoinExt on Coin { String get prettyName { @@ -57,6 +61,10 @@ extension CoinExt on Coin { return "Dogecoin"; case Coin.epicCash: return "Epic Cash"; + case Coin.eCash: + return "E-Cash"; + case Coin.ethereum: + return "Ethereum"; case Coin.firo: return "Firo"; case Coin.monero: @@ -92,6 +100,10 @@ extension CoinExt on Coin { return "DOGE"; case Coin.epicCash: return "EPIC"; + case Coin.ethereum: + return "ETH"; + case Coin.eCash: + return "XEC"; case Coin.firo: return "FIRO"; case Coin.monero: @@ -128,6 +140,10 @@ extension CoinExt on Coin { case Coin.epicCash: // TODO: is this actually the right one? return "epic"; + case Coin.ethereum: + return "ethereum"; + case Coin.eCash: + return "ecash"; case Coin.firo: return "firo"; case Coin.monero: @@ -165,15 +181,100 @@ extension CoinExt on Coin { case Coin.bitcoincashTestnet: case Coin.firoTestNet: case Coin.dogecoinTestNet: + case Coin.eCash: return true; case Coin.epicCash: + case Coin.ethereum: case Coin.monero: case Coin.wownero: return false; } } + bool get hasBuySupport { + switch (this) { + case Coin.bitcoin: + case Coin.litecoin: + case Coin.bitcoincash: + case Coin.dogecoin: + case Coin.ethereum: + return true; + + case Coin.firo: + case Coin.namecoin: + case Coin.particl: + case Coin.eCash: + case Coin.epicCash: + case Coin.monero: + case Coin.wownero: + case Coin.dogecoinTestNet: + case Coin.bitcoinTestNet: + case Coin.litecoinTestNet: + case Coin.bitcoincashTestnet: + case Coin.firoTestNet: + return false; + } + } + + bool get isTestNet { + switch (this) { + case Coin.bitcoin: + case Coin.litecoin: + case Coin.bitcoincash: + case Coin.dogecoin: + case Coin.firo: + case Coin.namecoin: + case Coin.particl: + case Coin.epicCash: + case Coin.ethereum: + case Coin.monero: + case Coin.wownero: + case Coin.eCash: + return false; + + case Coin.dogecoinTestNet: + case Coin.bitcoinTestNet: + case Coin.litecoinTestNet: + case Coin.bitcoincashTestnet: + case Coin.firoTestNet: + return true; + } + } + + Coin get mainNetVersion { + switch (this) { + case Coin.bitcoin: + case Coin.litecoin: + case Coin.bitcoincash: + case Coin.dogecoin: + case Coin.firo: + case Coin.namecoin: + case Coin.particl: + case Coin.epicCash: + case Coin.ethereum: + case Coin.monero: + case Coin.wownero: + case Coin.eCash: + return this; + + case Coin.dogecoinTestNet: + return Coin.dogecoin; + + case Coin.bitcoinTestNet: + return Coin.bitcoin; + + case Coin.litecoinTestNet: + return Coin.litecoin; + + case Coin.bitcoincashTestnet: + return Coin.bitcoincash; + + case Coin.firoTestNet: + return Coin.firo; + } + } + int get requiredConfirmations { switch (this) { case Coin.bitcoin: @@ -199,6 +300,12 @@ extension CoinExt on Coin { case Coin.epicCash: return epic.MINIMUM_CONFIRMATIONS; + case Coin.eCash: + return ecash.MINIMUM_CONFIRMATIONS; + + case Coin.ethereum: + return eth.MINIMUM_CONFIRMATIONS; + case Coin.monero: return xmr.MINIMUM_CONFIRMATIONS; @@ -212,6 +319,8 @@ extension CoinExt on Coin { return nmc.MINIMUM_CONFIRMATIONS; } } + + int get decimals => Constants.decimalPlacesForCoin(this); } Coin coinFromPrettyName(String name) { @@ -237,10 +346,19 @@ Coin coinFromPrettyName(String name) { case "epicCash": return Coin.epicCash; + case "Ethereum": + case "ethereum": + return Coin.ethereum; + case "Firo": case "firo": return Coin.firo; + case "E-Cash": + case "ecash": + case "eCash": + return Coin.eCash; + case "Monero": case "monero": return Coin.monero; @@ -306,6 +424,10 @@ Coin coinFromTickerCaseInsensitive(String ticker) { return Coin.dogecoin; case "epic": return Coin.epicCash; + case "xec": + return Coin.eCash; + case "eth": + return Coin.ethereum; case "firo": return Coin.firo; case "xmr": @@ -316,8 +438,6 @@ Coin coinFromTickerCaseInsensitive(String ticker) { return Coin.particl; case "tltc": return Coin.litecoinTestNet; - case "part": - return Coin.particl; case "tbtc": return Coin.bitcoinTestNet; case "tbch": diff --git a/lib/utilities/enums/derive_path_type_enum.dart b/lib/utilities/enums/derive_path_type_enum.dart new file mode 100644 index 000000000..72899f5bd --- /dev/null +++ b/lib/utilities/enums/derive_path_type_enum.dart @@ -0,0 +1,44 @@ +import 'package:stackwallet/utilities/enums/coin_enum.dart'; + +enum DerivePathType { + bip44, + bch44, + bip49, + bip84, + eth, + eCash44, +} + +extension DerivePathTypeExt on DerivePathType { + static DerivePathType primaryFor(Coin coin) { + switch (coin) { + case Coin.bitcoincash: + case Coin.bitcoincashTestnet: + case Coin.dogecoin: + case Coin.dogecoinTestNet: + case Coin.firo: + case Coin.firoTestNet: + return DerivePathType.bip44; + + case Coin.bitcoin: + case Coin.bitcoinTestNet: + case Coin.litecoin: + case Coin.litecoinTestNet: + case Coin.namecoin: + case Coin.particl: + return DerivePathType.bip84; + + case Coin.eCash: + return DerivePathType.eCash44; + + case Coin.ethereum: // TODO: do we need something here? + return DerivePathType.eth; + + case Coin.epicCash: + case Coin.monero: + case Coin.wownero: + throw UnsupportedError( + "$coin does not use bitcoin style derivation paths"); + } + } +} diff --git a/lib/utilities/enums/exchange_rate_type_enum.dart b/lib/utilities/enums/exchange_rate_type_enum.dart new file mode 100644 index 000000000..2b6267805 --- /dev/null +++ b/lib/utilities/enums/exchange_rate_type_enum.dart @@ -0,0 +1 @@ +enum ExchangeRateType { estimated, fixed } diff --git a/lib/utilities/enums/fiat_enum.dart b/lib/utilities/enums/fiat_enum.dart new file mode 100644 index 000000000..2a7303c86 --- /dev/null +++ b/lib/utilities/enums/fiat_enum.dart @@ -0,0 +1,1156 @@ +enum Fiats { + AED, + AFN, + ALL, + AMD, + ANG, + AOA, + ARS, + AUD, + AWG, + AZN, + BAM, + BBD, + BDT, + BGN, + BHD, + BIF, + BMD, + BND, + BOB, + BRL, + BSD, + BTN, + BWP, + BYN, + BZD, + CAD, + CDF, + CHF, + CLP, + CNY, + COP, + CRC, + CUC, + CUP, + CVE, + CZK, + DJF, + DKK, + DOP, + DZD, + EGP, + ERN, + ETB, + EUR, + FJD, + FKP, + GBP, + GEL, + GGP, + GHS, + GIP, + GMD, + GNF, + GTQ, + GYD, + HKD, + HNL, + HRK, + HTG, + HUF, + IDR, + ILS, + IMP, + INR, + IQD, + IRR, + ISK, + JEP, + JMD, + JOD, + JPY, + KES, + KGS, + KHR, + KMF, + KPW, + KRW, + KWD, + KYD, + KZT, + LAK, + LBP, + LKR, + LRD, + LSL, + LYD, + MAD, + MDL, + MGA, + MKD, + MMK, + MNT, + MOP, + MRU, + MUR, + MVR, + MWK, + MXN, + MYR, + MZN, + NAD, + NGN, + NIO, + NOK, + NPR, + NZD, + OMR, + PAB, + PEN, + PGK, + PHP, + PKR, + PLN, + PYG, + QAR, + RON, + RSD, + RUB, + RWF, + SAR, + SBD, + SCR, + SDG, + SEK, + SGD, + SHP, + SLL, + SOS, + SPL, + SRD, + STN, + SVC, + SYP, + SZL, + THB, + TJS, + TMT, + TND, + TOP, + TRY, + TTD, + TVD, + TWD, + TZS, + UAH, + UGX, + USD, + UYU, + UZS, + VEF, + VND, + VUV, + WST, + XAF, + XCD, + XDR, + XOF, + XPF, + YER, + ZAR, + ZMW, + ZWD, +} + +extension FiatExt on Fiats { + String get ticker { + switch (this) { + case Fiats.AED: + return 'AED'; + case Fiats.AFN: + return 'AFN'; + case Fiats.ALL: + return 'ALL'; + case Fiats.AMD: + return 'AMD'; + case Fiats.ANG: + return 'ANG'; + case Fiats.AOA: + return 'AOA'; + case Fiats.ARS: + return 'ARS'; + case Fiats.AUD: + return 'AUD'; + case Fiats.AWG: + return 'AWG'; + case Fiats.AZN: + return 'AZN'; + case Fiats.BAM: + return 'BAM'; + case Fiats.BBD: + return 'BBD'; + case Fiats.BDT: + return 'BDT'; + case Fiats.BGN: + return 'BGN'; + case Fiats.BHD: + return 'BHD'; + case Fiats.BIF: + return 'BIF'; + case Fiats.BMD: + return 'BMD'; + case Fiats.BND: + return 'BND'; + case Fiats.BOB: + return 'BOB'; + case Fiats.BRL: + return 'BRL'; + case Fiats.BSD: + return 'BSD'; + case Fiats.BTN: + return 'BTN'; + case Fiats.BWP: + return 'BWP'; + case Fiats.BYN: + return 'BYN'; + case Fiats.BZD: + return 'BZD'; + case Fiats.CAD: + return 'CAD'; + case Fiats.CDF: + return 'CDF'; + case Fiats.CHF: + return 'CHF'; + case Fiats.CLP: + return 'CLP'; + case Fiats.CNY: + return 'CNY'; + case Fiats.COP: + return 'COP'; + case Fiats.CRC: + return 'CRC'; + case Fiats.CUC: + return 'CUC'; + case Fiats.CUP: + return 'CUP'; + case Fiats.CVE: + return 'CVE'; + case Fiats.CZK: + return 'CZK'; + case Fiats.DJF: + return 'DJF'; + case Fiats.DKK: + return 'DKK'; + case Fiats.DOP: + return 'DOP'; + case Fiats.DZD: + return 'DZD'; + case Fiats.EGP: + return 'EGP'; + case Fiats.ERN: + return 'ERN'; + case Fiats.ETB: + return 'ETB'; + case Fiats.EUR: + return 'EUR'; + case Fiats.FJD: + return 'FJD'; + case Fiats.FKP: + return 'FKP'; + case Fiats.GBP: + return 'GBP'; + case Fiats.GEL: + return 'GEL'; + case Fiats.GGP: + return 'GGP'; + case Fiats.GHS: + return 'GHS'; + case Fiats.GIP: + return 'GIP'; + case Fiats.GMD: + return 'GMD'; + case Fiats.GNF: + return 'GNF'; + case Fiats.GTQ: + return 'GTQ'; + case Fiats.GYD: + return 'GYD'; + case Fiats.HKD: + return 'HKD'; + case Fiats.HNL: + return 'HNL'; + case Fiats.HRK: + return 'HRK'; + case Fiats.HTG: + return 'HTG'; + case Fiats.HUF: + return 'HUF'; + case Fiats.IDR: + return 'IDR'; + case Fiats.ILS: + return 'ILS'; + case Fiats.IMP: + return 'IMP'; + case Fiats.INR: + return 'INR'; + case Fiats.IQD: + return 'IQD'; + case Fiats.IRR: + return 'IRR'; + case Fiats.ISK: + return 'ISK'; + case Fiats.JEP: + return 'JEP'; + case Fiats.JMD: + return 'JMD'; + case Fiats.JOD: + return 'JOD'; + case Fiats.JPY: + return 'JPY'; + case Fiats.KES: + return 'KES'; + case Fiats.KGS: + return 'KGS'; + case Fiats.KHR: + return 'KHR'; + case Fiats.KMF: + return 'KMF'; + case Fiats.KPW: + return 'KPW'; + case Fiats.KRW: + return 'KRW'; + case Fiats.KWD: + return 'KWD'; + case Fiats.KYD: + return 'KYD'; + case Fiats.KZT: + return 'KZT'; + case Fiats.LAK: + return 'LAK'; + case Fiats.LBP: + return 'LBP'; + case Fiats.LKR: + return 'LKR'; + case Fiats.LRD: + return 'LRD'; + case Fiats.LSL: + return 'LSL'; + case Fiats.LYD: + return 'LYD'; + case Fiats.MAD: + return 'MAD'; + case Fiats.MDL: + return 'MDL'; + case Fiats.MGA: + return 'MGA'; + case Fiats.MKD: + return 'MKD'; + case Fiats.MMK: + return 'MMK'; + case Fiats.MNT: + return 'MNT'; + case Fiats.MOP: + return 'MOP'; + case Fiats.MRU: + return 'MRU'; + case Fiats.MUR: + return 'MUR'; + case Fiats.MVR: + return 'MVR'; + case Fiats.MWK: + return 'MWK'; + case Fiats.MXN: + return 'MXN'; + case Fiats.MYR: + return 'MYR'; + case Fiats.MZN: + return 'MZN'; + case Fiats.NAD: + return 'NAD'; + case Fiats.NGN: + return 'NGN'; + case Fiats.NIO: + return 'NIO'; + case Fiats.NOK: + return 'NOK'; + case Fiats.NPR: + return 'NPR'; + case Fiats.NZD: + return 'NZD'; + case Fiats.OMR: + return 'OMR'; + case Fiats.PAB: + return 'PAB'; + case Fiats.PEN: + return 'PEN'; + case Fiats.PGK: + return 'PGK'; + case Fiats.PHP: + return 'PHP'; + case Fiats.PKR: + return 'PKR'; + case Fiats.PLN: + return 'PLN'; + case Fiats.PYG: + return 'PYG'; + case Fiats.QAR: + return 'QAR'; + case Fiats.RON: + return 'RON'; + case Fiats.RSD: + return 'RSD'; + case Fiats.RUB: + return 'RUB'; + case Fiats.RWF: + return 'RWF'; + case Fiats.SAR: + return 'SAR'; + case Fiats.SBD: + return 'SBD'; + case Fiats.SCR: + return 'SCR'; + case Fiats.SDG: + return 'SDG'; + case Fiats.SEK: + return 'SEK'; + case Fiats.SGD: + return 'SGD'; + case Fiats.SHP: + return 'SHP'; + case Fiats.SLL: + return 'SLL'; + case Fiats.SOS: + return 'SOS'; + case Fiats.SPL: + return 'SPL'; + case Fiats.SRD: + return 'SRD'; + case Fiats.STN: + return 'STN'; + case Fiats.SVC: + return 'SVC'; + case Fiats.SYP: + return 'SYP'; + case Fiats.SZL: + return 'SZL'; + case Fiats.THB: + return 'THB'; + case Fiats.TJS: + return 'TJS'; + case Fiats.TMT: + return 'TMT'; + case Fiats.TND: + return 'TND'; + case Fiats.TOP: + return 'TOP'; + case Fiats.TRY: + return 'TRY'; + case Fiats.TTD: + return 'TTD'; + case Fiats.TVD: + return 'TVD'; + case Fiats.TWD: + return 'TWD'; + case Fiats.TZS: + return 'TZS'; + case Fiats.UAH: + return 'UAH'; + case Fiats.UGX: + return 'UGX'; + case Fiats.USD: + return 'USD'; + case Fiats.UYU: + return 'UYU'; + case Fiats.UZS: + return 'UZS'; + case Fiats.VEF: + return 'VEF'; + case Fiats.VND: + return 'VND'; + case Fiats.VUV: + return 'VUV'; + case Fiats.WST: + return 'WST'; + case Fiats.XAF: + return 'XAF'; + case Fiats.XCD: + return 'XCD'; + case Fiats.XDR: + return 'XDR'; + case Fiats.XOF: + return 'XOF'; + case Fiats.XPF: + return 'XPF'; + case Fiats.YER: + return 'YER'; + case Fiats.ZAR: + return 'ZAR'; + case Fiats.ZMW: + return 'ZMW'; + case Fiats.ZWD: + return 'ZWD'; + } + } + + String get prettyName { + switch (this) { + case Fiats.AED: + return 'United Arab Emirates Dirham'; + case Fiats.AFN: + return 'Afghanistan Afghani'; + case Fiats.ALL: + return 'Albania Lek'; + case Fiats.AMD: + return 'Armenia Dram'; + case Fiats.ANG: + return 'Netherlands Antilles Guilder'; + case Fiats.AOA: + return 'Angola Kwanza'; + case Fiats.ARS: + return 'Argentina Peso'; + case Fiats.AUD: + return 'Australia Dollar'; + case Fiats.AWG: + return 'Aruba Guilder'; + case Fiats.AZN: + return 'Azerbaijan Manat'; + case Fiats.BAM: + return 'Bosnia and Herzegovina Convertible Mark'; + case Fiats.BBD: + return 'Barbados Dollar'; + case Fiats.BDT: + return 'Bangladesh Taka'; + case Fiats.BGN: + return 'Bulgaria Lev'; + case Fiats.BHD: + return 'Bahrain Dinar'; + case Fiats.BIF: + return 'Burundi Franc'; + case Fiats.BMD: + return 'Bermuda Dollar'; + case Fiats.BND: + return 'Brunei Darussalam Dollar'; + case Fiats.BOB: + return 'Bolivia Bolíviano'; + case Fiats.BRL: + return 'Brazil Real'; + case Fiats.BSD: + return 'Bahamas Dollar'; + case Fiats.BTN: + return 'Bhutan Ngultrum'; + case Fiats.BWP: + return 'Botswana Pula'; + case Fiats.BYN: + return 'Belarus Ruble'; + case Fiats.BZD: + return 'Belize Dollar'; + case Fiats.CAD: + return 'Canada Dollar'; + case Fiats.CDF: + return 'Congo/Kinshasa Franc'; + case Fiats.CHF: + return 'Switzerland Franc'; + case Fiats.CLP: + return 'Chile Peso'; + case Fiats.CNY: + return 'China Yuan Renminbi'; + case Fiats.COP: + return 'Colombia Peso'; + case Fiats.CRC: + return 'Costa Rica Colon'; + case Fiats.CUC: + return 'Cuba Convertible Peso'; + case Fiats.CUP: + return 'Cuba Peso'; + case Fiats.CVE: + return 'Cape Verde Escudo'; + case Fiats.CZK: + return 'Czech Republic Koruna'; + case Fiats.DJF: + return 'Djibouti Franc'; + case Fiats.DKK: + return 'Denmark Krone'; + case Fiats.DOP: + return 'Dominican Republic Peso'; + case Fiats.DZD: + return 'Algeria Dinar'; + case Fiats.EGP: + return 'Egypt Pound'; + case Fiats.ERN: + return 'Eritrea Nakfa'; + case Fiats.ETB: + return 'Ethiopia Birr'; + case Fiats.EUR: + return 'Euro Member Countries'; + case Fiats.FJD: + return 'Fiji Dollar'; + case Fiats.FKP: + return 'Falkland Islands (Malvinas) Pound'; + case Fiats.GBP: + return 'United Kingdom Pound'; + case Fiats.GEL: + return 'Georgia Lari'; + case Fiats.GGP: + return 'Guernsey Pound'; + case Fiats.GHS: + return 'Ghana Cedi'; + case Fiats.GIP: + return 'Gibraltar Pound'; + case Fiats.GMD: + return 'Gambia Dalasi'; + case Fiats.GNF: + return 'Guinea Franc'; + case Fiats.GTQ: + return 'Guatemala Quetzal'; + case Fiats.GYD: + return 'Guyana Dollar'; + case Fiats.HKD: + return 'Hong Kong Dollar'; + case Fiats.HNL: + return 'Honduras Lempira'; + case Fiats.HRK: + return 'Croatia Kuna'; + case Fiats.HTG: + return 'Haiti Gourde'; + case Fiats.HUF: + return 'Hungary Forint'; + case Fiats.IDR: + return 'Indonesia Rupiah'; + case Fiats.ILS: + return 'Israel Shekel'; + case Fiats.IMP: + return 'Isle of Man Pound'; + case Fiats.INR: + return 'India Rupee'; + case Fiats.IQD: + return 'Iraq Dinar'; + case Fiats.IRR: + return 'Iran Rial'; + case Fiats.ISK: + return 'Iceland Krona'; + case Fiats.JEP: + return 'Jersey Pound'; + case Fiats.JMD: + return 'Jamaica Dollar'; + case Fiats.JOD: + return 'Jordan Dinar'; + case Fiats.JPY: + return 'Japan Yen'; + case Fiats.KES: + return 'Kenya Shilling'; + case Fiats.KGS: + return 'Kyrgyzstan Som'; + case Fiats.KHR: + return 'Cambodia Riel'; + case Fiats.KMF: + return 'Comorian Franc'; + case Fiats.KPW: + return 'Korea (North) Won'; + case Fiats.KRW: + return 'Korea (South) Won'; + case Fiats.KWD: + return 'Kuwait Dinar'; + case Fiats.KYD: + return 'Cayman Islands Dollar'; + case Fiats.KZT: + return 'Kazakhstan Tenge'; + case Fiats.LAK: + return 'Laos Kip'; + case Fiats.LBP: + return 'Lebanon Pound'; + case Fiats.LKR: + return 'Sri Lanka Rupee'; + case Fiats.LRD: + return 'Liberia Dollar'; + case Fiats.LSL: + return 'Lesotho Loti'; + case Fiats.LYD: + return 'Libya Dinar'; + case Fiats.MAD: + return 'Morocco Dirham'; + case Fiats.MDL: + return 'Moldova Leu'; + case Fiats.MGA: + return 'Madagascar Ariary'; + case Fiats.MKD: + return 'Macedonia Denar'; + case Fiats.MMK: + return 'Myanmar (Burma) Kyat'; + case Fiats.MNT: + return 'Mongolia Tughrik'; + case Fiats.MOP: + return 'Macau Pataca'; + case Fiats.MRU: + return 'Mauritania Ouguiya'; + case Fiats.MUR: + return 'Mauritius Rupee'; + case Fiats.MVR: + return 'Maldives (Maldive Islands) Rufiyaa'; + case Fiats.MWK: + return 'Malawi Kwacha'; + case Fiats.MXN: + return 'Mexico Peso'; + case Fiats.MYR: + return 'Malaysia Ringgit'; + case Fiats.MZN: + return 'Mozambique Metical'; + case Fiats.NAD: + return 'Namibia Dollar'; + case Fiats.NGN: + return 'Nigeria Naira'; + case Fiats.NIO: + return 'Nicaragua Cordoba'; + case Fiats.NOK: + return 'Norway Krone'; + case Fiats.NPR: + return 'Nepal Rupee'; + case Fiats.NZD: + return 'New Zealand Dollar'; + case Fiats.OMR: + return 'Oman Rial'; + case Fiats.PAB: + return 'Panama Balboa'; + case Fiats.PEN: + return 'Peru Sol'; + case Fiats.PGK: + return 'Papua New Guinea Kina'; + case Fiats.PHP: + return 'Philippines Peso'; + case Fiats.PKR: + return 'Pakistan Rupee'; + case Fiats.PLN: + return 'Poland Zloty'; + case Fiats.PYG: + return 'Paraguay Guarani'; + case Fiats.QAR: + return 'Qatar Riyal'; + case Fiats.RON: + return 'Romania Leu'; + case Fiats.RSD: + return 'Serbia Dinar'; + case Fiats.RUB: + return 'Russia Ruble'; + case Fiats.RWF: + return 'Rwanda Franc'; + case Fiats.SAR: + return 'Saudi Arabia Riyal'; + case Fiats.SBD: + return 'Solomon Islands Dollar'; + case Fiats.SCR: + return 'Seychelles Rupee'; + case Fiats.SDG: + return 'Sudan Pound'; + case Fiats.SEK: + return 'Sweden Krona'; + case Fiats.SGD: + return 'Singapore Dollar'; + case Fiats.SHP: + return 'Saint Helena Pound'; + case Fiats.SLL: + return 'Sierra Leone Leone'; + case Fiats.SOS: + return 'Somalia Shilling'; + case Fiats.SPL: + return 'Seborga Luigino'; + case Fiats.SRD: + return 'Suriname Dollar'; + case Fiats.STN: + return 'São Tomé and Príncipe Dobra'; + case Fiats.SVC: + return 'El Salvador Colon'; + case Fiats.SYP: + return 'Syria Pound'; + case Fiats.SZL: + return 'eSwatini Lilangeni'; + case Fiats.THB: + return 'Thailand Baht'; + case Fiats.TJS: + return 'Tajikistan Somoni'; + case Fiats.TMT: + return 'Turkmenistan Manat'; + case Fiats.TND: + return 'Tunisia Dinar'; + case Fiats.TOP: + return "Tonga Pa'anga"; + case Fiats.TRY: + return 'Turkey Lira'; + case Fiats.TTD: + return 'Trinidad and Tobago Dollar'; + case Fiats.TVD: + return 'Tuvalu Dollar'; + case Fiats.TWD: + return 'Taiwan New Dollar'; + case Fiats.TZS: + return 'Tanzania Shilling'; + case Fiats.UAH: + return 'Ukraine Hryvnia'; + case Fiats.UGX: + return 'Uganda Shilling'; + case Fiats.USD: + return 'United States Dollar'; + case Fiats.UYU: + return 'Uruguay Peso'; + case Fiats.UZS: + return 'Uzbekistan Som'; + case Fiats.VEF: + return 'Venezuela Bolívar'; + case Fiats.VND: + return 'Viet Nam Dong'; + case Fiats.VUV: + return 'Vanuatu Vatu'; + case Fiats.WST: + return 'Samoa Tala'; + case Fiats.XAF: + return 'Communauté Financière Africaine (BEAC) CFA Franc BEAC'; + case Fiats.XCD: + return 'East Caribbean Dollar'; + case Fiats.XDR: + return 'International Monetary Fund (IMF) Special Drawing Rights'; + case Fiats.XOF: + return 'Communauté Financière Africaine (BCEAO) Franc'; + case Fiats.XPF: + return 'Comptoirs Français du Pacifique (CFP) Franc'; + case Fiats.YER: + return 'Yemen Rial'; + case Fiats.ZAR: + return 'South Africa Rand'; + case Fiats.ZMW: + return 'Zambia Kwacha'; + case Fiats.ZWD: + return 'Zimbabwe Dollar'; + } + } +} + +Fiats fiatFromTickerCaseInsensitive(String ticker) { + switch (ticker.toLowerCase()) { + case "aed": + return Fiats.AED; + case "afn": + return Fiats.AFN; + case "all": + return Fiats.ALL; + case "amd": + return Fiats.AMD; + case "ang": + return Fiats.ANG; + case "aoa": + return Fiats.AOA; + case "ars": + return Fiats.ARS; + case "aud": + return Fiats.AUD; + case "awg": + return Fiats.AWG; + case "azn": + return Fiats.AZN; + case "bam": + return Fiats.BAM; + case "bbd": + return Fiats.BBD; + case "bdt": + return Fiats.BDT; + case "bgn": + return Fiats.BGN; + case "bhd": + return Fiats.BHD; + case "bif": + return Fiats.BIF; + case "bmd": + return Fiats.BMD; + case "bnd": + return Fiats.BND; + case "bob": + return Fiats.BOB; + case "brl": + return Fiats.BRL; + case "bsd": + return Fiats.BSD; + case "btn": + return Fiats.BTN; + case "bwp": + return Fiats.BWP; + case "byn": + return Fiats.BYN; + case "bzd": + return Fiats.BZD; + case "cad": + return Fiats.CAD; + case "cdf": + return Fiats.CDF; + case "chf": + return Fiats.CHF; + case "clp": + return Fiats.CLP; + case "cny": + return Fiats.CNY; + case "cop": + return Fiats.COP; + case "crc": + return Fiats.CRC; + case "cuc": + return Fiats.CUC; + case "cup": + return Fiats.CUP; + case "cve": + return Fiats.CVE; + case "czk": + return Fiats.CZK; + case "djf": + return Fiats.DJF; + case "dkk": + return Fiats.DKK; + case "dop": + return Fiats.DOP; + case "dzd": + return Fiats.DZD; + case "egp": + return Fiats.EGP; + case "ern": + return Fiats.ERN; + case "etb": + return Fiats.ETB; + case "eur": + return Fiats.EUR; + case "fjd": + return Fiats.FJD; + case "fkp": + return Fiats.FKP; + case "gbp": + return Fiats.GBP; + case "gel": + return Fiats.GEL; + case "ggp": + return Fiats.GGP; + case "ghs": + return Fiats.GHS; + case "gip": + return Fiats.GIP; + case "gmd": + return Fiats.GMD; + case "gnf": + return Fiats.GNF; + case "gtq": + return Fiats.GTQ; + case "gyd": + return Fiats.GYD; + case "hkd": + return Fiats.HKD; + case "hnl": + return Fiats.HNL; + case "hrk": + return Fiats.HRK; + case "htg": + return Fiats.HTG; + case "huf": + return Fiats.HUF; + case "idr": + return Fiats.IDR; + case "ils": + return Fiats.ILS; + case "imp": + return Fiats.IMP; + case "inr": + return Fiats.INR; + case "iqd": + return Fiats.IQD; + case "irr": + return Fiats.IRR; + case "isk": + return Fiats.ISK; + case "jep": + return Fiats.JEP; + case "jmd": + return Fiats.JMD; + case "jod": + return Fiats.JOD; + case "jpy": + return Fiats.JPY; + case "kes": + return Fiats.KES; + case "kgs": + return Fiats.KGS; + case "khr": + return Fiats.KHR; + case "kmf": + return Fiats.KMF; + case "kpw": + return Fiats.KPW; + case "krw": + return Fiats.KRW; + case "kwd": + return Fiats.KWD; + case "kyd": + return Fiats.KYD; + case "kzt": + return Fiats.KZT; + case "lak": + return Fiats.LAK; + case "lbp": + return Fiats.LBP; + case "lkr": + return Fiats.LKR; + case "lrd": + return Fiats.LRD; + case "lsl": + return Fiats.LSL; + case "lyd": + return Fiats.LYD; + case "mad": + return Fiats.MAD; + case "mdl": + return Fiats.MDL; + case "mga": + return Fiats.MGA; + case "mkd": + return Fiats.MKD; + case "mmk": + return Fiats.MMK; + case "mnt": + return Fiats.MNT; + case "mop": + return Fiats.MOP; + case "mru": + return Fiats.MRU; + case "mur": + return Fiats.MUR; + case "mvr": + return Fiats.MVR; + case "mwk": + return Fiats.MWK; + case "mxn": + return Fiats.MXN; + case "myr": + return Fiats.MYR; + case "mzn": + return Fiats.MZN; + case "nad": + return Fiats.NAD; + case "ngn": + return Fiats.NGN; + case "nio": + return Fiats.NIO; + case "nok": + return Fiats.NOK; + case "npr": + return Fiats.NPR; + case "nzd": + return Fiats.NZD; + case "omr": + return Fiats.OMR; + case "pab": + return Fiats.PAB; + case "pen": + return Fiats.PEN; + case "pgk": + return Fiats.PGK; + case "php": + return Fiats.PHP; + case "pkr": + return Fiats.PKR; + case "pln": + return Fiats.PLN; + case "pyg": + return Fiats.PYG; + case "qar": + return Fiats.QAR; + case "ron": + return Fiats.RON; + case "rsd": + return Fiats.RSD; + case "rub": + return Fiats.RUB; + case "rwf": + return Fiats.RWF; + case "sar": + return Fiats.SAR; + case "sbd": + return Fiats.SBD; + case "scr": + return Fiats.SCR; + case "sdg": + return Fiats.SDG; + case "sek": + return Fiats.SEK; + case "sgd": + return Fiats.SGD; + case "shp": + return Fiats.SHP; + case "sll": + return Fiats.SLL; + case "sos": + return Fiats.SOS; + case "spl": + return Fiats.SPL; + case "srd": + return Fiats.SRD; + case "stn": + return Fiats.STN; + case "svc": + return Fiats.SVC; + case "syp": + return Fiats.SYP; + case "szl": + return Fiats.SZL; + case "thb": + return Fiats.THB; + case "tjs": + return Fiats.TJS; + case "tmt": + return Fiats.TMT; + case "tnd": + return Fiats.TND; + case "top": + return Fiats.TOP; + case "try": + return Fiats.TRY; + case "ttd": + return Fiats.TTD; + case "tvd": + return Fiats.TVD; + case "twd": + return Fiats.TWD; + case "tzs": + return Fiats.TZS; + case "uah": + return Fiats.UAH; + case "ugx": + return Fiats.UGX; + case "usd": + return Fiats.USD; + case "uyu": + return Fiats.UYU; + case "uzs": + return Fiats.UZS; + case "vef": + return Fiats.VEF; + case "vnd": + return Fiats.VND; + case "vuv": + return Fiats.VUV; + case "wst": + return Fiats.WST; + case "xaf": + return Fiats.XAF; + case "xcd": + return Fiats.XCD; + case "xdr": + return Fiats.XDR; + case "xof": + return Fiats.XOF; + case "xpf": + return Fiats.XPF; + case "yer": + return Fiats.YER; + case "zar": + return Fiats.ZAR; + case "zmw": + return Fiats.ZMW; + case "zwd": + return Fiats.ZWD; + default: + throw ArgumentError.value( + ticker, "name", "No Fiat enum value with that ticker"); + } +} diff --git a/lib/utilities/enums/log_level_enum.dart b/lib/utilities/enums/log_level_enum.dart index d426adc3d..b9b5fd69f 100644 --- a/lib/utilities/enums/log_level_enum.dart +++ b/lib/utilities/enums/log_level_enum.dart @@ -1,13 +1,8 @@ -import 'package:isar/isar.dart'; - // Used in Isar db and stored there as int indexes so adding/removing values // in this definition should be done extremely carefully in production -enum LogLevel with IsarEnum { +enum LogLevel { Info, Warning, Error, Fatal; - - @override - String get value => name; } diff --git a/lib/utilities/eth_commons.dart b/lib/utilities/eth_commons.dart new file mode 100644 index 000000000..466a4aff0 --- /dev/null +++ b/lib/utilities/eth_commons.dart @@ -0,0 +1,81 @@ +import 'package:bip32/bip32.dart' as bip32; +import 'package:bip39/bip39.dart' as bip39; +import 'package:decimal/decimal.dart'; +import "package:hex/hex.dart"; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; + +class GasTracker { + final Decimal average; + final Decimal fast; + final Decimal slow; + + final int numberOfBlocksFast; + final int numberOfBlocksAverage; + final int numberOfBlocksSlow; + + final String lastBlock; + + const GasTracker({ + required this.average, + required this.fast, + required this.slow, + required this.numberOfBlocksFast, + required this.numberOfBlocksAverage, + required this.numberOfBlocksSlow, + required this.lastBlock, + }); + + factory GasTracker.fromJson(Map json) { + final targetTime = Constants.targetBlockTimeInSeconds(Coin.ethereum); + return GasTracker( + fast: Decimal.parse(json["FastGasPrice"].toString()), + average: Decimal.parse(json["ProposeGasPrice"].toString()), + slow: Decimal.parse(json["SafeGasPrice"].toString()), + // TODO fix hardcoded + numberOfBlocksFast: 30 ~/ targetTime, + numberOfBlocksAverage: 180 ~/ targetTime, + numberOfBlocksSlow: 240 ~/ targetTime, + lastBlock: json["LastBlock"] as String, + ); + } +} + +const hdPathEthereum = "m/44'/60'/0'/0"; + +// equal to "0x${keccak256("Transfer(address,address,uint256)".toUint8ListFromUtf8).toHex}"; +const kTransferEventSignature = + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"; + +String getPrivateKey(String mnemonic, String mnemonicPassphrase) { + final isValidMnemonic = bip39.validateMnemonic(mnemonic); + if (!isValidMnemonic) { + throw 'Invalid mnemonic'; + } + + final seed = bip39.mnemonicToSeed(mnemonic, passphrase: mnemonicPassphrase); + final root = bip32.BIP32.fromSeed(seed); + const index = 0; + final addressAtIndex = root.derivePath("$hdPathEthereum/$index"); + + return HEX.encode(addressAtIndex.privateKey as List); +} + +Amount estimateFee(int feeRate, int gasLimit, int decimals) { + final gweiAmount = feeRate.toDecimal() / (Decimal.ten.pow(9).toDecimal()); + final fee = gasLimit.toDecimal() * + gweiAmount.toDecimal( + scaleOnInfinitePrecision: Coin.ethereum.decimals, + ); + + //Convert gwei to ETH + final feeInWei = fee * Decimal.ten.pow(9).toDecimal(); + final ethAmount = feeInWei / Decimal.ten.pow(decimals).toDecimal(); + return Amount.fromDecimal( + ethAmount.toDecimal( + scaleOnInfinitePrecision: Coin.ethereum.decimals, + ), + fractionDigits: decimals, + ); +} diff --git a/lib/utilities/extensions/extensions.dart b/lib/utilities/extensions/extensions.dart new file mode 100644 index 000000000..792ebe0b3 --- /dev/null +++ b/lib/utilities/extensions/extensions.dart @@ -0,0 +1,3 @@ +export 'impl/big_int.dart'; +export 'impl/string.dart'; +export 'impl/uint8_list.dart'; diff --git a/lib/utilities/extensions/impl/big_int.dart b/lib/utilities/extensions/impl/big_int.dart new file mode 100644 index 000000000..c9b78ab55 --- /dev/null +++ b/lib/utilities/extensions/impl/big_int.dart @@ -0,0 +1,33 @@ +import 'dart:typed_data'; + +extension BigIntExtensions on BigInt { + String get toHex { + if (this < BigInt.zero) { + throw Exception("BigInt value is negative"); + } + + final String hex = toRadixString(16); + if (hex.length % 2 == 0) { + return hex; + } else { + return "0$hex"; + } + } + + String get toHexUppercase => toHex.toUpperCase(); + + Uint8List get toBytes { + if (this < BigInt.zero) { + throw Exception("BigInt value is negative"); + } + BigInt number = this; + int bytes = (number.bitLength + 7) >> 3; + final b256 = BigInt.from(256); + final result = Uint8List(bytes); + for (int i = 0; i < bytes; i++) { + result[bytes - 1 - i] = number.remainder(b256).toInt(); + number = number >> 8; + } + return result; + } +} diff --git a/lib/utilities/extensions/impl/box_shadow.dart b/lib/utilities/extensions/impl/box_shadow.dart new file mode 100644 index 000000000..c5c53794d --- /dev/null +++ b/lib/utilities/extensions/impl/box_shadow.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; + +// todo: delete this map (example) +final map = { + "name": "Dark", + "coinColors": { + "bitcoin": "0xFF267352", + }, + "assets": { + "circleLock": "svg/somerandomnamecreatedbythemecreator.svg", + }, + "colors": { + "background": "0xFF848383", + }, + "gradientBackground": { + "gradientType": "linear", + "begin": { + "x": 0.0, + "y": 1.0, + }, + "end": { + "x": -1.0, + "y": 1.0, + }, + "colors": [ + "0xFF638227", + "0xFF632827", + ] + } +}; + +extension BoxShadowExt on BoxShadow { + static BoxShadow fromJson(Map json) => BoxShadow( + color: Color(int.parse(json["color"] as String)), + spreadRadius: json["spread_radius"] as double, + blurRadius: json["blur_radius"] as double, + ); +} diff --git a/lib/utilities/extensions/impl/contract_abi.dart b/lib/utilities/extensions/impl/contract_abi.dart new file mode 100644 index 000000000..61827b32d --- /dev/null +++ b/lib/utilities/extensions/impl/contract_abi.dart @@ -0,0 +1,129 @@ +import 'dart:convert'; + +import 'package:stackwallet/utilities/logger.dart'; +import 'package:web3dart/web3dart.dart'; + +extension ContractAbiExtensions on ContractAbi { + static ContractAbi fromJsonList({ + required String name, + required String jsonList, + }) { + try { + final List functions = []; + final List events = []; + + final list = + List>.from(jsonDecode(jsonList) as List); + + for (final json in list) { + final type = json["type"] as String; + final name = json["name"] as String? ?? ""; + + if (type == "event") { + final anonymous = json["anonymous"] as bool? ?? false; + final List> components = []; + + if (json["inputs"] is List) { + for (final input in json["inputs"] as List) { + components.add( + EventComponent( + _parseParam(input as Map), + input['indexed'] as bool? ?? false, + ), + ); + } + } + + events.add(ContractEvent(anonymous, name, components)); + } else { + final mutability = _mutabilityNames[json['stateMutability']]; + final parsedType = _functionTypeNames[json['type']]; + if (parsedType != null) { + final inputs = _parseParams(json['inputs'] as List?); + final outputs = _parseParams(json['outputs'] as List?); + + functions.add( + ContractFunction( + name, + inputs, + outputs: outputs, + type: parsedType, + mutability: mutability ?? StateMutability.nonPayable, + ), + ); + } + } + } + + return ContractAbi(name, functions, events); + } catch (e, s) { + Logging.instance.log( + "Failed to parse ABI for $name: $e\n$s", + level: LogLevel.Error, + ); + rethrow; + } + } + + static const Map _functionTypeNames = { + 'function': ContractFunctionType.function, + 'constructor': ContractFunctionType.constructor, + 'fallback': ContractFunctionType.fallback, + }; + + static const Map _mutabilityNames = { + 'pure': StateMutability.pure, + 'view': StateMutability.view, + 'nonpayable': StateMutability.nonPayable, + 'payable': StateMutability.payable, + }; + + static List> _parseParams(List? data) { + if (data == null || data.isEmpty) return []; + + final elements = >[]; + for (final entry in data) { + elements.add(_parseParam(entry as Map)); + } + + return elements; + } + + static FunctionParameter _parseParam(Map entry) { + final name = entry['name'] as String; + final typeName = entry['type'] as String; + + if (typeName.contains('tuple')) { + final components = entry['components'] as List; + return _parseTuple(name, typeName, _parseParams(components)); + } else { + final type = parseAbiType(entry['type'] as String); + return FunctionParameter(name, type); + } + } + + static CompositeFunctionParameter _parseTuple(String name, String typeName, + List> components) { + // The type will have the form tuple[3][]...[1], where the indices after the + // tuple indicate that the type is part of an array. + assert(RegExp(r'^tuple(?:\[\d*\])*$').hasMatch(typeName), + '$typeName is an invalid tuple type'); + + final arrayLengths = []; + var remainingName = typeName; + + while (remainingName != 'tuple') { + final arrayMatch = RegExp(r'^(.*)\[(\d*)\]$').firstMatch(remainingName)!; + remainingName = arrayMatch.group(1)!; + + final insideSquareBrackets = arrayMatch.group(2)!; + if (insideSquareBrackets.isEmpty) { + arrayLengths.insert(0, null); + } else { + arrayLengths.insert(0, int.parse(insideSquareBrackets)); + } + } + + return CompositeFunctionParameter(name, components, arrayLengths); + } +} diff --git a/lib/utilities/extensions/impl/gradient.dart b/lib/utilities/extensions/impl/gradient.dart new file mode 100644 index 000000000..8af06d421 --- /dev/null +++ b/lib/utilities/extensions/impl/gradient.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/utilities/extensions/impl/string.dart'; + +extension GradientExt on Gradient { + static Gradient fromJson(Map json) { + switch (json["background"]["type"]) { + case "Linear": + final colorStrings = + List.from(json["background"]["colors"] as List); + return LinearGradient( + begin: Alignment( + json["background"]["begin"]["x"] as double, + json["background"]["begin"]["y"] as double, + ), + end: Alignment( + json["background"]["end"]["x"] as double, + json["background"]["end"]["y"] as double, + ), + colors: colorStrings + .map( + (e) => Color( + e.toBigIntFromHex.toInt(), + ), + ) + .toList(), + ); + + default: + throw ArgumentError("Invalid json gradient: $json"); + } + } +} diff --git a/lib/utilities/extensions/impl/string.dart b/lib/utilities/extensions/impl/string.dart new file mode 100644 index 000000000..e5021e3f1 --- /dev/null +++ b/lib/utilities/extensions/impl/string.dart @@ -0,0 +1,20 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:dart_bs58/dart_bs58.dart'; +import 'package:dart_bs58check/dart_bs58check.dart'; +import 'package:hex/hex.dart'; +import 'package:stackwallet/utilities/extensions/extensions.dart'; + +extension StringExtensions on String { + Uint8List get toUint8ListFromUtf8 => Uint8List.fromList(utf8.encode(this)); + + Uint8List get toUint8ListFromHex => + Uint8List.fromList(HEX.decode(startsWith("0x") ? substring(2) : this)); + + Uint8List get toUint8ListFromBase58Encoded => bs58.decode(this); + + Uint8List get toUint8ListFromBase58CheckEncoded => bs58check.decode(this); + + BigInt get toBigIntFromHex => toUint8ListFromHex.toBigInt; +} diff --git a/lib/utilities/extensions/impl/uint8_list.dart b/lib/utilities/extensions/impl/uint8_list.dart new file mode 100644 index 000000000..04980f91a --- /dev/null +++ b/lib/utilities/extensions/impl/uint8_list.dart @@ -0,0 +1,39 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +import 'package:dart_bs58/dart_bs58.dart'; +import 'package:dart_bs58check/dart_bs58check.dart'; +import 'package:hex/hex.dart'; + +extension Uint8ListExtensions on Uint8List { + String get toUtf8String => utf8.decode(this); + + String get toHex { + return HEX.encode(this); + } + + String get toBase58Encoded { + return bs58.encode(this); + } + + String get toBase58CheckEncoded { + return bs58check.encode(this); + } + + /// returns copy of byte list in reverse order + Uint8List get reversed { + final reversed = Uint8List(length); + for (final byte in this) { + reversed.insert(0, byte); + } + return reversed; + } + + BigInt get toBigInt { + BigInt number = BigInt.zero; + for (final byte in this) { + number = (number << 8) | BigInt.from(byte & 0xff); + } + return number; + } +} diff --git a/lib/utilities/featured_paynyms.dart b/lib/utilities/featured_paynyms.dart new file mode 100644 index 000000000..a04fa9a7d --- /dev/null +++ b/lib/utilities/featured_paynyms.dart @@ -0,0 +1,12 @@ +abstract class FeaturedPaynyms { + // TODO: replace with actual value + // static const String samouraiWalletDevFund = + // "PM8TJYkuSdYXJnwDBq8ChfinfXv3srxhQrx3eoEwbSw51wMjdo9JJ2DsycwT3gt3zHQ7cV1grvabMmmf1Btj6fY7tgkgSz9B8MZuR3kjYfgMLMURJCXN"; + static const String stackWallet = + "PM8TJdQcNk27JpxGRtNR7Hnh8VkJk4Nf17BthLx89fM3iX3UL2YshyaiTAvKgTCVvpgsAgY1DbojkAaUd3Rcn48NEn4uUBuqkaSddgKL8TPAAEQXNuE6"; + + static Map get featured => { + "Stack Wallet": stackWallet, + // "Samourai Wallet Dev Fund": samouraiWalletDevFund, + }; +} diff --git a/lib/utilities/flutter_secure_storage_interface.dart b/lib/utilities/flutter_secure_storage_interface.dart index 539f17847..3c49ebd28 100644 --- a/lib/utilities/flutter_secure_storage_interface.dart +++ b/lib/utilities/flutter_secure_storage_interface.dart @@ -46,6 +46,8 @@ abstract class SecureStorageInterface { MacOsOptions? mOptions, WindowsOptions? wOptions, }); + + Future> get keys; } class DesktopSecureStore { @@ -60,6 +62,7 @@ class DesktopSecureStore { directory: (await StackFileSystem.applicationIsarDirectory()).path, inspector: false, name: "desktopStore", + maxSizeMiB: 512, ); } @@ -110,6 +113,10 @@ class DesktopSecureStore { await isar.encryptedStringValues.deleteByKey(key); }); } + + Future> get keys async { + return await isar.encryptedStringValues.where().keyProperty().findAll(); + } } /// all *Options params ignored on desktop @@ -229,6 +236,15 @@ class SecureStorageWrapper implements SecureStorageInterface { ); } } + + @override + Future> get keys async { + if (_isDesktop) { + return (_store as DesktopSecureStore).keys; + } else { + return (await (_store as FlutterSecureStorage).readAll()).keys.toList(); + } + } } // Mock class for testing purposes @@ -305,4 +321,7 @@ class FakeSecureStorage implements SecureStorageInterface { @override dynamic get store => throw UnimplementedError(); + + @override + Future> get keys => Future(() => _store.keys.toList()); } diff --git a/lib/utilities/format.dart b/lib/utilities/format.dart index 136ec5b95..56d7059b7 100644 --- a/lib/utilities/format.dart +++ b/lib/utilities/format.dart @@ -1,37 +1,43 @@ import 'dart:typed_data'; -import 'package:decimal/decimal.dart'; -import 'package:intl/number_symbols.dart'; -import 'package:intl/number_symbols_data.dart' show numberFormatSymbols; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; abstract class Format { - static Decimal satoshisToAmount(int sats, {required Coin coin}) { - return (Decimal.fromInt(sats) / - Decimal.fromInt(Constants.satsPerCoin(coin))) - .toDecimal( - scaleOnInfinitePrecision: Constants.decimalPlacesForCoin(coin)); + static String shorten(String value, int beginCount, int endCount) { + return "${value.substring(0, beginCount)}...${value.substring(value.length - endCount)}"; } - /// - static String satoshiAmountToPrettyString( - int sats, String locale, Coin coin) { - final amount = satoshisToAmount(sats, coin: coin); - return localizedStringAsFixed( - value: amount, - locale: locale, - decimalPlaces: Constants.decimalPlacesForCoin(coin), - ); - } - - static int decimalAmountToSatoshis(Decimal amount, Coin coin) { - final value = (Decimal.fromInt(Constants.satsPerCoin(coin)) * amount) - .floor() - .toBigInt(); - return value.toInt(); - } + // static Decimal satoshisToAmount(int sats, {required Coin coin}) { + // return (Decimal.fromInt(sats) / + // Decimal.fromInt(Constants.satsPerCoin(coin))) + // .toDecimal( + // scaleOnInfinitePrecision: Constants.decimalPlacesForCoin(coin)); + // } + // + // static Decimal satoshisToEthTokenAmount(int sats, int decimalPlaces) { + // return (Decimal.fromInt(sats) / + // Decimal.fromInt(pow(10, decimalPlaces).toInt())) + // .toDecimal(scaleOnInfinitePrecision: decimalPlaces); + // } + // + // /// + // static String satoshiAmountToPrettyString( + // int sats, String locale, Coin coin) { + // final amount = satoshisToAmount(sats, coin: coin); + // return localizedStringAsFixed( + // value: amount, + // locale: locale, + // decimalPlaces: Constants.decimalPlacesForCoin(coin), + // ); + // } + // + // static int decimalAmountToSatoshis(Decimal amount, Coin coin) { + // final value = (Decimal.fromInt(Constants.satsPerCoin(coin)) * amount) + // .floor() + // .toBigInt(); + // return value.toInt(); + // } // format date string from unix timestamp static String extractDateFrom(int timestamp, {bool localized = true}) { @@ -46,26 +52,26 @@ abstract class Format { return "${date.day} ${Constants.monthMapShort[date.month]} ${date.year}, ${date.hour}:$minutes"; } - static String localizedStringAsFixed({ - required Decimal value, - required String locale, - int decimalPlaces = 0, - }) { - assert(decimalPlaces >= 0); - - final String separator = - (numberFormatSymbols[locale] as NumberSymbols?)?.DECIMAL_SEP ?? - (numberFormatSymbols[locale.substring(0, 2)] as NumberSymbols?) - ?.DECIMAL_SEP ?? - "."; - - final intValue = value.truncate(); - final fraction = value - intValue; - - return intValue.toStringAsFixed(0) + - separator + - fraction.toStringAsFixed(decimalPlaces).substring(2); - } + // static String localizedStringAsFixed({ + // required Decimal value, + // required String locale, + // int decimalPlaces = 0, + // }) { + // assert(decimalPlaces >= 0); + // + // final String separator = + // (numberFormatSymbols[locale] as NumberSymbols?)?.DECIMAL_SEP ?? + // (numberFormatSymbols[locale.substring(0, 2)] as NumberSymbols?) + // ?.DECIMAL_SEP ?? + // "."; + // + // final intValue = value.truncate(); + // final fraction = value - intValue; + // + // return intValue.toStringAsFixed(0) + + // separator + + // fraction.toStringAsFixed(decimalPlaces).substring(2); + // } /// format date string as dd/mm/yy from DateTime object static String formatDate(DateTime date) { diff --git a/lib/utilities/logger.dart b/lib/utilities/logger.dart index 6a5083cbc..c6e04f0ef 100644 --- a/lib/utilities/logger.dart +++ b/lib/utilities/logger.dart @@ -35,6 +35,7 @@ class Logging { isar = await Isar.open( [LogSchema], inspector: false, + maxSizeMiB: 512, ); } @@ -49,9 +50,16 @@ class Logging { Logger.print(object, normalLength: !printFullLength); return; } + String message = object.toString(); + + // random value to check max size of message before storing in db + if (message.length > 20000) { + message = "${message.substring(0, 20000)}..."; + } + final now = core.DateTime.now().toUtc(); final log = Log() - ..message = object.toString() + ..message = message ..logLevel = level ..timestampInMillisUTC = now.millisecondsSinceEpoch; if (level == LogLevel.Error || level == LogLevel.Fatal) { diff --git a/lib/utilities/paynym_is_api.dart b/lib/utilities/paynym_is_api.dart new file mode 100644 index 000000000..edbd4f416 --- /dev/null +++ b/lib/utilities/paynym_is_api.dart @@ -0,0 +1,590 @@ +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import 'package:http/http.dart' as http; +import 'package:stackwallet/models/paynym/created_paynym.dart'; +import 'package:stackwallet/models/paynym/paynym_account.dart'; +import 'package:stackwallet/models/paynym/paynym_claim.dart'; +import 'package:stackwallet/models/paynym/paynym_follow.dart'; +import 'package:stackwallet/models/paynym/paynym_response.dart'; +import 'package:stackwallet/models/paynym/paynym_unfollow.dart'; +import 'package:tuple/tuple.dart'; + +// todo: better error message parsing (from response itself?) + +class PaynymIsApi { + static const String baseURL = "https://paynym.is/api"; + static const String version = "/v1"; + + Future, int>> _post( + String endpoint, + Map body, [ + Map additionalHeaders = const {}, + ]) async { + String url = baseURL + + version + + (endpoint.startsWith("/") ? endpoint : "/$endpoint"); + final uri = Uri.parse(url); + final headers = { + 'Content-Type': 'application/json; charset=UTF-8', + }..addAll(additionalHeaders); + final response = await http.post( + uri, + headers: headers, + body: jsonEncode(body), + ); + + debugPrint("Paynym request uri: $uri"); + debugPrint("Paynym request body: $body"); + debugPrint("Paynym request headers: $headers"); + debugPrint("Paynym response code: ${response.statusCode}"); + debugPrint("Paynym response body: ${response.body}"); + + return Tuple2( + jsonDecode(response.body) as Map, + response.statusCode, + ); + } + + // ### `/api/v1/create` + // + // Create a new PayNym entry in the database. + // + // + // + // **Request** + // + // ```json + // POST /api/v1/create + // content-type: application/json + // + // { + // "code":"PM8T..." + // } + // + // ``` + // + // | Value | Key | + // | ----- | -------------------- | + // | code | A valid payment code | + // + // + // + // **Response** (201) + // + // ```json + // { + // "claimed": false, + // "nymID": "v9pJm...", + // "nymName": "snowysea", + // "segwit": true, + // "token": "IlBNOF...", + // } + // ``` + // + // | Code | Meaning | + // | ---- | --------------------------- | + // | 201 | PayNym created successfully | + // | 200 | PayNym already exists | + // | 400 | Bad request | + // + // + // + // ------ + Future> create(String code) async { + final result = await _post("/create", {"code": code}); + + String message; + CreatedPaynym? value; + + switch (result.item2) { + case 201: + message = "PayNym created successfully"; + value = CreatedPaynym.fromMap(result.item1); + break; + case 200: + message = "PayNym already exists"; + value = CreatedPaynym.fromMap(result.item1); + break; + case 400: + message = "Bad request"; + break; + default: + message = result.item1["message"] as String? ?? "Unknown error"; + } + return PaynymResponse(value, result.item2, message); + } + + // ### `/api/v1/token` + // + // Update the verification token in the database. A token is valid for 24 hours and only for a single authenticated call. The payment code must be in the database or the request will return `404` + // + // + // + // **Request** + // + // ```json + // POST /api/v1/token/ + // content-type: application/json + // + // {"code":"PM8T..."} + // ``` + // + // | Value | Key | + // | ----- | -------------------- | + // | code | A valid payment code | + // + // + // + // **Response** (200) + // + // ```json + // { + // "token": "DP7S3w..." + // } + // ``` + // + // | Code | Meaning | + // | ---- | ------------------------------ | + // | 200 | Token was successfully updated | + // | 404 | Payment code was not found | + // | 400 | Bad request | + // + // + // + // ------ + Future> token(String code) async { + final result = await _post("/token", {"code": code}); + + String message; + String? value; + + switch (result.item2) { + case 200: + message = "Token was successfully updated"; + value = result.item1["token"] as String; + break; + case 404: + message = "Payment code was not found"; + break; + case 400: + message = "Bad request"; + break; + default: + message = result.item1["message"] as String? ?? "Unknown error"; + } + return PaynymResponse(value, result.item2, message); + } + + // ### `/api/v1/nym` + // + // Returns all known information about a PayNym account including any other payment codes associated with this Nym. + // + // + // + // **Request** + // + // ```json + // POST /api/v1/nym/ + // content-type: application/json + // + // {"nym":"PM8T..."} + // ``` + // + // | Value | Key | + // | ----- | ---------------------------------------- | + // | nym | A valid payment `code`, `nymID`, or `nymName` | + // + // + // + // **Response** (200) + // + // ```json + // { + // "codes": [ + // { + // "claimed": true, + // "segwit": true, + // "code": "PM8T..." + // } + // ], + // "followers": [ + // { + // "nymId": "5iEpU..." + // } + // ], + // "following": [], + // "nymID": "wXGgdC...", + // "nymName": "littlevoice" + // } + // ``` + // + // If the `compact=true` parameter is added to the URL, follower and following will not returned. This can achieve faster requests. + // + // | Code | Meaning | + // | ---- | ---------------------- | + // | 200 | Nym found and returned | + // | 404 | Nym not found | + // | 400 | Bad request | + Future> nym(String code, + [bool compact = false]) async { + final Map requestBody = {"nym": code}; + if (compact) { + requestBody["compact"] = true; + } + + String message; + PaynymAccount? value; + int statusCode; + + try { + final result = await _post("/nym", requestBody); + + statusCode = result.item2; + + switch (result.item2) { + case 200: + message = "Nym found and returned"; + value = PaynymAccount.fromMap(result.item1); + break; + case 404: + message = "Nym not found"; + break; + case 400: + message = "Bad request"; + break; + default: + message = result.item1["message"] as String? ?? "Unknown error"; + } + } catch (e) { + value = null; + message = e.toString(); + statusCode = -1; + } + return PaynymResponse(value, statusCode, message); + } + + // ## Authenticated Requests + // + // + // + // ### Making authenticated requests + // + // 1. Set an `auth-token` header containing the `token` + // 2. Sign the `token` with the private key of the notification address of the primary payment code + // 3. Add the `signature` to the body of the request. + // 4. A token can only be used once per authenticated request. A new `token` will be returned in the response of a successful authenticated request + // + + // ### `/api/v1/claim` + // + // Claim ownership of a payment code added to a newly created PayNym identity. + // + // + // + // **Request** + // + // ```json + // POST /api/v1/claim + // content-type: application/json + // auth-token: IlBNOFRKWmt... + // + // + // {"signature":"..."} + // ``` + // + // | Value | Key | + // | --------- | ---------------------------------------- | + // | signature | The `token` signed by the BIP47 notification address | + // + // + // + // **Response** (200) + // + // ```json + // { + // "claimed" : "PM8T...", + // "token" : "IlBNOFRKSmt..." + // } + // ``` + // + // | Code | Meaning | + // | ---- | --------------------------------- | + // | 200 | Payment code successfully claimed | + // | 400 | Bad request | + // + // ------ + Future> claim( + String token, + String signature, + ) async { + final result = await _post( + "/claim", + {"signature": signature}, + {"auth-token": token}, + ); + + String message; + PaynymClaim? value; + + switch (result.item2) { + case 200: + message = "Payment code successfully claimed"; + value = PaynymClaim.fromMap(result.item1); + break; + case 400: + message = "Bad request"; + break; + default: + message = result.item1["message"] as String? ?? "Unknown error"; + } + return PaynymResponse(value, result.item2, message); + } + + // ### `/api/v1/follow` + // + // Follow another PayNym account. + // + // + // + // **Request** + // + // ```json + // POST /api/v1/follow/ + // content-type: application/json + // auth-token: IlBNOFRKWmt... + // + // { + // "target": "wXGgdC...", + // "signature":"..." + // } + // ``` + // + // | Key | Value | + // | --------- | ---------------------------------------- | + // | target | The payment code to follow | + // | signature | The `token` signed by the BIP47 notification address | + // + // **Response** (200) + // + // ```json + // { + // "follower": "5iEpU...", + // "following": "wXGgdC...", + // "token" : "IlBNOFRKSmt..." + // } + // ``` + // + // | Code | Meaning | + // | ---- | ---------------------------------------- | + // | 200 | Added to followers | + // | 404 | Payment code not found | + // | 400 | Bad request | + // | 401 | Unauthorized token or signature or Unclaimed payment code | + // + // ------ + Future> follow( + String token, + String signature, + String target, + ) async { + final result = await _post( + "/follow", + { + "target": target, + "signature": signature, + }, + { + "auth-token": token, + }, + ); + + String message; + PaynymFollow? value; + + switch (result.item2) { + case 200: + message = "Added to followers"; + value = PaynymFollow.fromMap(result.item1); + break; + case 404: + message = "Payment code not found"; + break; + case 400: + message = "Bad request"; + break; + case 401: + message = "Unauthorized token or signature or Unclaimed payment code"; + break; + default: + message = result.item1["message"] as String? ?? "Unknown error"; + } + return PaynymResponse(value, result.item2, message); + } + + // ### `/api/v1/unfollow` + // + // Unfollow another PayNym account. + // + // + // + // **Request** + // + // ```json + // POST /api/v1/unfollow/ + // content-type: application/json + // auth-token: IlBNOFRKWmt... + // + // { + // "target": "wXGgdC...", + // "signature":"..." + // } + // ``` + // + // | Key | Value | + // | --------- | ---------------------------------------- | + // | target | The payment code to unfollow | + // | signature | The `token` signed by the BIP47 notification address | + // + // **Response** (200) + // + // ```json + // { + // "follower": "5iEpU...", + // "unfollowing": "wXGgdC...", + // "token" : "IlBNOFRKSmt..." + // } + // ``` + // + // | Code | Meaning | + // | ---- | ---------------------------------------- | + // | 200 | Unfollowed successfully | + // | 404 | Payment code not found | + // | 400 | Bad request | + // | 401 | Unauthorized token or signature or Unclaimed payment code | + // + // ------ + Future> unfollow( + String token, + String signature, + String target, + ) async { + final result = await _post( + "/unfollow", + { + "target": target, + "signature": signature, + }, + { + "auth-token": token, + }, + ); + + String message; + PaynymUnfollow? value; + + switch (result.item2) { + case 200: + message = "Unfollowed successfully"; + value = PaynymUnfollow.fromMap(result.item1); + break; + case 404: + message = "Payment code not found"; + break; + case 400: + message = "Bad request"; + break; + case 401: + message = "Unauthorized token or signature or Unclaimed payment code"; + break; + default: + message = result.item1["message"] as String? ?? "Unknown error"; + } + return PaynymResponse(value, result.item2, message); + } + + // ### `/api/v1/nym/add` + // + // Add a new payment code to an existing Nym + // + // + // + // **Request** + // + // ```json + // POST /api/v1/nym/add + // content-type: application/json + // auth-token: IlBNOFRKWmt... + // + // { + // "nym": "wXGgdC...", + // "code":"PM8T...", + // "signature":"..." + // } + // ``` + // + // | Key | Value | + // | --------- | ------------------------------------------------------------ | + // | nym | A valid payment `code`, `nymID`, or `nymName` | + // | code | A valid payment code | + // | signature | The `token` signed by the BIP47 notification address of the primary payment code. | + // + // **Response** (200) + // + // ```json + // { + // "code":"PM8T...", + // "segwit": true, + // "token" : "IlBNOFRKSmt..." + // } + // ``` + // + // | Code | Meaning | + // | ---- | --------------------------------------------------------- | + // | 200 | Nym updated successfully | + // | 404 | Nym not found | + // | 400 | Bad request | + // | 401 | Unauthorized token or signature or Unclaimed payment code | + // + // ------ + Future> add( + String token, + String signature, + String nym, + String code, + ) async { + final result = await _post( + "/nym/add", + { + "nym": nym, + "code": code, + "signature": signature, + }, + { + "auth-token": token, + }, + ); + + String message; + bool value = false; + + switch (result.item2) { + case 200: + message = "Code added successfully"; + value = true; + break; + case 400: + message = "Bad request"; + break; + case 401: + message = "Unauthorized token or signature or Unclaimed payment code"; + break; + case 404: + message = "Nym not found"; + break; + default: + message = result.item1["message"] as String? ?? "Unknown error"; + } + return PaynymResponse(value, result.item2, message); + } +} diff --git a/lib/utilities/prefs.dart b/lib/utilities/prefs.dart index 6b4b9821a..a57e08755 100644 --- a/lib/utilities/prefs.dart +++ b/lib/utilities/prefs.dart @@ -1,10 +1,10 @@ import 'package:flutter/cupertino.dart'; -import 'package:stackwallet/hive/db.dart'; -import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart'; +import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart'; import 'package:stackwallet/utilities/enums/languages_enum.dart'; import 'package:stackwallet/utilities/enums/sync_type_enum.dart'; +import 'package:uuid/uuid.dart'; class Prefs extends ChangeNotifier { Prefs._(); @@ -17,7 +17,8 @@ class Prefs extends ChangeNotifier { Future init() async { if (!_initialized) { _currency = await _getPreferredCurrency(); - _exchangeRateType = await _getExchangeRateType(); + // _exchangeRateType = await _getExchangeRateType(); + _randomizePIN = await _getRandomizePIN(); _useBiometrics = await _getUseBiometrics(); _hasPin = await _getHasPin(); _language = await _getPreferredLanguage(); @@ -38,6 +39,13 @@ class Prefs extends ChangeNotifier { _startupWalletId = await _getStartupWalletId(); _externalCalls = await _getHasExternalCalls(); _familiarity = await _getHasFamiliarity(); + _userId = await _getUserId(); + _signupEpoch = await _getSignupEpoch(); + _enableCoinControl = await _getEnableCoinControl(); + _enableSystemBrightness = await _getEnableSystemBrightness(); + _themeId = await _getThemeId(); + _systemBrightnessLightThemeId = await _getSystemBrightnessLightThemeId(); + _systemBrightnessDarkThemeId = await _getSystemBrightnessDarkTheme(); _initialized = true; } @@ -248,43 +256,64 @@ class Prefs extends ChangeNotifier { // exchange rate type - ExchangeRateType _exchangeRateType = ExchangeRateType.estimated; + // ExchangeRateType _exchangeRateType = ExchangeRateType.estimated; + // + // ExchangeRateType get exchangeRateType => _exchangeRateType; + // + // set exchangeRateType(ExchangeRateType exchangeRateType) { + // if (_exchangeRateType != exchangeRateType) { + // switch (exchangeRateType) { + // case ExchangeRateType.estimated: + // DB.instance.put( + // boxName: DB.boxNamePrefs, + // key: "exchangeRateType", + // value: "estimated"); + // break; + // case ExchangeRateType.fixed: + // DB.instance.put( + // boxName: DB.boxNamePrefs, + // key: "exchangeRateType", + // value: "fixed"); + // break; + // } + // _exchangeRateType = exchangeRateType; + // notifyListeners(); + // } + // } + // + // Future _getExchangeRateType() async { + // String? rate = await DB.instance.get( + // boxName: DB.boxNamePrefs, key: "exchangeRateType") as String?; + // rate ??= "estimated"; + // switch (rate) { + // case "estimated": + // return ExchangeRateType.estimated; + // case "fixed": + // return ExchangeRateType.fixed; + // default: + // throw Exception("Invalid exchange rate type found in prefs!"); + // } + // } - ExchangeRateType get exchangeRateType => _exchangeRateType; + // randomize PIN - set exchangeRateType(ExchangeRateType exchangeRateType) { - if (_exchangeRateType != exchangeRateType) { - switch (exchangeRateType) { - case ExchangeRateType.estimated: - DB.instance.put( - boxName: DB.boxNamePrefs, - key: "exchangeRateType", - value: "estimated"); - break; - case ExchangeRateType.fixed: - DB.instance.put( - boxName: DB.boxNamePrefs, - key: "exchangeRateType", - value: "fixed"); - break; - } - _exchangeRateType = exchangeRateType; + bool _randomizePIN = false; + + bool get randomizePIN => _randomizePIN; + + set randomizePIN(bool randomizePIN) { + if (_randomizePIN != randomizePIN) { + DB.instance.put( + boxName: DB.boxNamePrefs, key: "randomizePIN", value: randomizePIN); + _randomizePIN = randomizePIN; notifyListeners(); } } - Future _getExchangeRateType() async { - String? rate = await DB.instance.get( - boxName: DB.boxNamePrefs, key: "exchangeRateType") as String?; - rate ??= "estimated"; - switch (rate) { - case "estimated": - return ExchangeRateType.estimated; - case "fixed": - return ExchangeRateType.fixed; - default: - throw Exception("Invalid exchange rate type found in prefs!"); - } + Future _getRandomizePIN() async { + return await DB.instance.get( + boxName: DB.boxNamePrefs, key: "randomizePIN") as bool? ?? + false; } // use biometrics @@ -602,4 +631,169 @@ class Prefs extends ChangeNotifier { } return true; } + + String? _userId; + String? get userID => _userId; + + Future _getUserId() async { + String? userID = await DB.instance + .get(boxName: DB.boxNamePrefs, key: "userID") as String?; + if (userID == null) { + userID = const Uuid().v4(); + await saveUserID(userID); + } + return userID; + } + + Future saveUserID(String userId) async { + _userId = userId; + await DB.instance + .put(boxName: DB.boxNamePrefs, key: "userID", value: _userId); + // notifyListeners(); + } + + int? _signupEpoch; + int? get signupEpoch => _signupEpoch; + + Future _getSignupEpoch() async { + int? signupEpoch = await DB.instance + .get(boxName: DB.boxNamePrefs, key: "signupEpoch") as int?; + if (signupEpoch == null) { + signupEpoch = DateTime.now().millisecondsSinceEpoch ~/ + Duration.millisecondsPerSecond; + await saveSignupEpoch(signupEpoch); + } + return signupEpoch; + } + + Future saveSignupEpoch(int signupEpoch) async { + _signupEpoch = signupEpoch; + await DB.instance.put( + boxName: DB.boxNamePrefs, key: "signupEpoch", value: _signupEpoch); + // notifyListeners(); + } + + // show testnet coins + + bool _enableCoinControl = false; + + bool get enableCoinControl => _enableCoinControl; + + set enableCoinControl(bool enableCoinControl) { + if (_enableCoinControl != enableCoinControl) { + DB.instance.put( + boxName: DB.boxNamePrefs, + key: "enableCoinControl", + value: enableCoinControl); + _enableCoinControl = enableCoinControl; + notifyListeners(); + } + } + + Future _getEnableCoinControl() async { + return await DB.instance.get( + boxName: DB.boxNamePrefs, key: "enableCoinControl") as bool? ?? + false; + } + + // follow system brightness + + bool _enableSystemBrightness = false; + + bool get enableSystemBrightness => _enableSystemBrightness; + + set enableSystemBrightness(bool enableSystemBrightness) { + if (_enableSystemBrightness != enableSystemBrightness) { + DB.instance.put( + boxName: DB.boxNamePrefs, + key: "enableSystemBrightness", + value: enableSystemBrightness); + _enableSystemBrightness = enableSystemBrightness; + notifyListeners(); + } + } + + Future _getEnableSystemBrightness() async { + return await DB.instance.get( + boxName: DB.boxNamePrefs, key: "enableSystemBrightness") as bool? ?? + false; + } + + // current theme id + + String _themeId = "light"; + + String get themeId => _themeId; + + set themeId(String themeId) { + if (this.themeId != themeId) { + DB.instance.put( + boxName: DB.boxNamePrefs, + key: "themeId", + value: themeId, + ); + _themeId = themeId; + notifyListeners(); + } + } + + Future _getThemeId() async { + return await DB.instance.get( + boxName: DB.boxNamePrefs, + key: "themeId", + ) as String? ?? + "light"; + } + + // current system brightness light theme id + + String _systemBrightnessLightThemeId = "light"; + + String get systemBrightnessLightThemeId => _systemBrightnessLightThemeId; + + set systemBrightnessLightThemeId(String systemBrightnessLightThemeId) { + if (this.systemBrightnessLightThemeId != systemBrightnessLightThemeId) { + DB.instance.put( + boxName: DB.boxNamePrefs, + key: "systemBrightnessLightThemeId", + value: systemBrightnessLightThemeId, + ); + _systemBrightnessLightThemeId = systemBrightnessLightThemeId; + notifyListeners(); + } + } + + Future _getSystemBrightnessLightThemeId() async { + return await DB.instance.get( + boxName: DB.boxNamePrefs, + key: "systemBrightnessLightThemeId", + ) as String? ?? + "light"; + } + + // current system brightness dark theme id + + String _systemBrightnessDarkThemeId = "dark"; + + String get systemBrightnessDarkThemeId => _systemBrightnessDarkThemeId; + + set systemBrightnessDarkThemeId(String systemBrightnessDarkThemeId) { + if (this.systemBrightnessDarkThemeId != systemBrightnessDarkThemeId) { + DB.instance.put( + boxName: DB.boxNamePrefs, + key: "systemBrightnessDarkThemeId", + value: systemBrightnessDarkThemeId, + ); + _systemBrightnessDarkThemeId = systemBrightnessDarkThemeId; + notifyListeners(); + } + } + + Future _getSystemBrightnessDarkTheme() async { + return await DB.instance.get( + boxName: DB.boxNamePrefs, + key: "systemBrightnessDarkThemeId", + ) as String? ?? + "dark"; + } } diff --git a/lib/utilities/show_loading.dart b/lib/utilities/show_loading.dart new file mode 100644 index 000000000..d24770663 --- /dev/null +++ b/lib/utilities/show_loading.dart @@ -0,0 +1,43 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/widgets/custom_loading_overlay.dart'; + +Future showLoading({ + required Future whileFuture, + required BuildContext context, + required String message, + String? subMessage, + bool isDesktop = false, + bool opaqueBG = false, +}) async { + unawaited( + showDialog( + context: context, + barrierDismissible: false, + builder: (_) => WillPopScope( + onWillPop: () async => false, + child: Container( + color: Theme.of(context) + .extension()! + .overlay + .withOpacity(opaqueBG ? 1.0 : 0.6), + child: CustomLoadingOverlay( + message: message, + subMessage: subMessage, + eventBus: null, + ), + ), + ), + ), + ); + + final result = await whileFuture; + + if (context.mounted) { + Navigator.of(context, rootNavigator: isDesktop).pop(); + } + + return result; +} diff --git a/lib/utilities/stack_file_system.dart b/lib/utilities/stack_file_system.dart index 34d82f691..b3821db11 100644 --- a/lib/utilities/stack_file_system.dart +++ b/lib/utilities/stack_file_system.dart @@ -62,4 +62,17 @@ abstract class StackFileSystem { return root; } } + + static Future applicationThemesDirectory() async { + final root = await applicationRootDirectory(); + // if (Util.isDesktop) { + final dir = Directory("${root.path}/themes"); + if (!dir.existsSync()) { + await dir.create(); + } + return dir; + // } else { + // return root; + // } + } } diff --git a/lib/utilities/text_styles.dart b/lib/utilities/text_styles.dart index 56c313219..9d2f55eda 100644 --- a/lib/utilities/text_styles.dart +++ b/lib/utilities/text_styles.dart @@ -1,27 +1,31 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; -import 'package:stackwallet/utilities/theme/color_theme.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; class STextStyles { static StackColors _theme(BuildContext context) => Theme.of(context).extension()!; + static TextStyle sectionLabelMedium12(BuildContext context) { + switch (_theme(context).themeId) { + case "forest": + return GoogleFonts.inter( + color: _theme(context).textDark3, + fontWeight: FontWeight.w500, + fontSize: 12, + ); + default: + return GoogleFonts.inter( + color: _theme(context).textDark3, + fontWeight: FontWeight.w500, + fontSize: 14, + ); + } + } + static TextStyle pageTitleH1(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w600, - fontSize: 20, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w600, - fontSize: 20, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark, fontWeight: FontWeight.w600, @@ -31,20 +35,8 @@ class STextStyles { } static TextStyle pageTitleH2(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w600, - fontSize: 18, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w600, - fontSize: 18, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark, fontWeight: FontWeight.w600, @@ -54,20 +46,8 @@ class STextStyles { } static TextStyle navBarTitle(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w600, - fontSize: 16, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w600, - fontSize: 16, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark, fontWeight: FontWeight.w600, @@ -77,66 +57,19 @@ class STextStyles { } static TextStyle titleBold12(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark, fontWeight: FontWeight.w600, fontSize: 16, ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w600, - fontSize: 16, - ); - case ThemeType.dark: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w600, - fontSize: 16, - ); - } - } - - static TextStyle titleBold12_400(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w400, - fontSize: 16, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w400, - fontSize: 16, - ); - case ThemeType.dark: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w400, - fontSize: 16, - ); } } static TextStyle subtitle(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w400, - fontSize: 16, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w400, - fontSize: 16, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark, fontWeight: FontWeight.w400, @@ -146,20 +79,8 @@ class STextStyles { } static TextStyle subtitle500(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w500, - fontSize: 16, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w500, - fontSize: 16, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark, fontWeight: FontWeight.w500, @@ -169,20 +90,8 @@ class STextStyles { } static TextStyle subtitle600(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w600, - fontSize: 16, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w600, - fontSize: 16, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark, fontWeight: FontWeight.w600, @@ -192,20 +101,8 @@ class STextStyles { } static TextStyle button(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).buttonTextPrimary, - fontWeight: FontWeight.w500, - fontSize: 16, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).buttonTextPrimary, - fontWeight: FontWeight.w500, - fontSize: 16, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).buttonTextPrimary, fontWeight: FontWeight.w500, @@ -215,20 +112,8 @@ class STextStyles { } static TextStyle largeMedium14(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w500, - fontSize: 16, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w500, - fontSize: 16, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark, fontWeight: FontWeight.w500, @@ -238,20 +123,8 @@ class STextStyles { } static TextStyle smallMed14(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark3, - fontWeight: FontWeight.w500, - fontSize: 16, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark3, - fontWeight: FontWeight.w500, - fontSize: 16, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark3, fontWeight: FontWeight.w500, @@ -261,20 +134,8 @@ class STextStyles { } static TextStyle smallMed12(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark3, - fontWeight: FontWeight.w500, - fontSize: 14, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark3, - fontWeight: FontWeight.w500, - fontSize: 14, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark3, fontWeight: FontWeight.w500, @@ -284,20 +145,8 @@ class STextStyles { } static TextStyle label(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textSubtitle1, - fontWeight: FontWeight.w500, - fontSize: 12, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textSubtitle1, - fontWeight: FontWeight.w500, - fontSize: 12, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textSubtitle1, fontWeight: FontWeight.w500, @@ -307,22 +156,8 @@ class STextStyles { } static TextStyle labelExtraExtraSmall(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textFieldActiveSearchIconRight, - fontWeight: FontWeight.w500, - fontSize: 14, - height: 14 / 14, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textFieldActiveSearchIconRight, - fontWeight: FontWeight.w500, - fontSize: 14, - height: 14 / 14, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textFieldActiveSearchIconRight, fontWeight: FontWeight.w500, @@ -333,20 +168,8 @@ class STextStyles { } static TextStyle label700(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textSubtitle1, - fontWeight: FontWeight.w700, - fontSize: 12, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textSubtitle1, - fontWeight: FontWeight.w700, - fontSize: 12, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textSubtitle1, fontWeight: FontWeight.w700, @@ -356,20 +179,8 @@ class STextStyles { } static TextStyle itemSubtitle(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).infoItemLabel, - fontWeight: FontWeight.w500, - fontSize: 14, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).infoItemLabel, - fontWeight: FontWeight.w500, - fontSize: 14, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).infoItemLabel, fontWeight: FontWeight.w500, @@ -379,43 +190,8 @@ class STextStyles { } static TextStyle itemSubtitle12(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w500, - fontSize: 14, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w500, - fontSize: 14, - ); - case ThemeType.dark: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w500, - fontSize: 14, - ); - } - } - - static TextStyle itemSubtitle12_600(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w500, - fontSize: 14, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w500, - fontSize: 14, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark, fontWeight: FontWeight.w500, @@ -425,22 +201,8 @@ class STextStyles { } static TextStyle fieldLabel(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textSubtitle2, - fontWeight: FontWeight.w500, - fontSize: 14, - height: 1.5, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textSubtitle2, - fontWeight: FontWeight.w500, - fontSize: 14, - height: 1.5, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textSubtitle2, fontWeight: FontWeight.w500, @@ -451,22 +213,8 @@ class STextStyles { } static TextStyle field(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w500, - fontSize: 14, - height: 1.5, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w500, - fontSize: 14, - height: 1.5, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark, fontWeight: FontWeight.w500, @@ -477,20 +225,8 @@ class STextStyles { } static TextStyle baseXS(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w400, - fontSize: 14, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w400, - fontSize: 14, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark, fontWeight: FontWeight.w400, @@ -500,20 +236,8 @@ class STextStyles { } static TextStyle link(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).accentColorRed, - fontWeight: FontWeight.w500, - fontSize: 14, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).accentColorRed, - fontWeight: FontWeight.w500, - fontSize: 14, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).accentColorRed, fontWeight: FontWeight.w500, @@ -523,20 +247,14 @@ class STextStyles { } static TextStyle link2(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: + switch (_theme(context).themeId) { + case "oled_black": return GoogleFonts.inter( - color: _theme(context).infoItemIcons, + color: _theme(context).checkboxBGChecked, fontWeight: FontWeight.w500, fontSize: 14, ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).infoItemIcons, - fontWeight: FontWeight.w500, - fontSize: 14, - ); - case ThemeType.dark: + default: return GoogleFonts.inter( color: _theme(context).infoItemIcons, fontWeight: FontWeight.w500, @@ -546,20 +264,8 @@ class STextStyles { } static TextStyle richLink(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).accentColorBlue, - fontWeight: FontWeight.w500, - fontSize: 12, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).accentColorBlue, - fontWeight: FontWeight.w500, - fontSize: 12, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).accentColorBlue, fontWeight: FontWeight.w500, @@ -568,44 +274,76 @@ class STextStyles { } } - static TextStyle w600_10(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: + static TextStyle w600_12(BuildContext context) { + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark, fontWeight: FontWeight.w600, fontSize: 12, ); - case ThemeType.oceanBreeze: + } + } + + static TextStyle w600_14(BuildContext context) { + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark, fontWeight: FontWeight.w600, + fontSize: 14, + ); + } + } + + static TextStyle w500_14(BuildContext context) { + switch (_theme(context).themeId) { + default: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, + fontSize: 14, + ); + } + } + + static TextStyle w500_12(BuildContext context) { + switch (_theme(context).themeId) { + default: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, fontSize: 12, ); - case ThemeType.dark: + } + } + + static TextStyle w500_10(BuildContext context) { + switch (_theme(context).themeId) { + default: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, + fontSize: 10, + ); + } + } + + static TextStyle w600_20(BuildContext context) { + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark, fontWeight: FontWeight.w600, - fontSize: 12, + fontSize: 20, + height: 30 / 20, ); } } static TextStyle syncPercent(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w500, - fontSize: 12, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w500, - fontSize: 12, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark, fontWeight: FontWeight.w500, @@ -615,20 +353,14 @@ class STextStyles { } static TextStyle buttonSmall(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: + switch (_theme(context).themeId) { + case "fruit_sorbet": return GoogleFonts.inter( - color: _theme(context).textDark, + color: _theme(context).bottomNavIconIcon, fontWeight: FontWeight.w500, fontSize: 12, ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w500, - fontSize: 12, - ); - case ThemeType.dark: + default: return GoogleFonts.inter( color: _theme(context).textDark, fontWeight: FontWeight.w500, @@ -638,20 +370,8 @@ class STextStyles { } static TextStyle errorSmall(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textError, - fontWeight: FontWeight.w500, - fontSize: 10, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textError, - fontWeight: FontWeight.w500, - fontSize: 10, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textError, fontWeight: FontWeight.w500, @@ -661,20 +381,8 @@ class STextStyles { } static TextStyle infoSmall(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textSubtitle1, - fontWeight: FontWeight.w500, - fontSize: 10, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textSubtitle1, - fontWeight: FontWeight.w500, - fontSize: 10, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textSubtitle1, fontWeight: FontWeight.w500, @@ -686,22 +394,8 @@ class STextStyles { // Desktop static TextStyle desktopH1(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w600, - fontSize: 40, - height: 40 / 40, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w600, - fontSize: 40, - height: 40 / 40, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark, fontWeight: FontWeight.w600, @@ -712,22 +406,8 @@ class STextStyles { } static TextStyle desktopH2(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w600, - fontSize: 32, - height: 32 / 32, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w600, - fontSize: 32, - height: 32 / 32, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark, fontWeight: FontWeight.w600, @@ -738,22 +418,8 @@ class STextStyles { } static TextStyle desktopH3(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w600, - fontSize: 24, - height: 24 / 24, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w600, - fontSize: 24, - height: 24 / 24, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark, fontWeight: FontWeight.w600, @@ -763,23 +429,21 @@ class STextStyles { } } + static TextStyle w500_24(BuildContext context) { + switch (_theme(context).themeId) { + default: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, + fontSize: 24, + height: 24 / 24, + ); + } + } + static TextStyle desktopTextMedium(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w500, - fontSize: 20, - height: 30 / 20, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w500, - fontSize: 20, - height: 30 / 20, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark, fontWeight: FontWeight.w500, @@ -790,22 +454,8 @@ class STextStyles { } static TextStyle desktopTextMediumRegular(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w400, - fontSize: 20, - height: 30 / 20, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w400, - fontSize: 20, - height: 30 / 20, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark, fontWeight: FontWeight.w400, @@ -816,22 +466,8 @@ class STextStyles { } static TextStyle desktopSubtitleH2(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w400, - fontSize: 20, - height: 28 / 20, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w400, - fontSize: 20, - height: 28 / 20, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark, fontWeight: FontWeight.w400, @@ -842,22 +478,8 @@ class STextStyles { } static TextStyle desktopSubtitleH1(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w400, - fontSize: 24, - height: 33 / 24, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w400, - fontSize: 24, - height: 33 / 24, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark, fontWeight: FontWeight.w400, @@ -868,22 +490,8 @@ class STextStyles { } static TextStyle desktopButtonEnabled(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).buttonTextPrimary, - fontWeight: FontWeight.w500, - fontSize: 20, - height: 26 / 20, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).buttonTextPrimary, - fontWeight: FontWeight.w500, - fontSize: 20, - height: 26 / 20, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).buttonTextPrimary, fontWeight: FontWeight.w500, @@ -894,22 +502,8 @@ class STextStyles { } static TextStyle desktopButtonDisabled(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).buttonTextPrimaryDisabled, - fontWeight: FontWeight.w500, - fontSize: 20, - height: 26 / 20, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).buttonTextPrimaryDisabled, - fontWeight: FontWeight.w500, - fontSize: 20, - height: 26 / 20, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).buttonTextPrimaryDisabled, fontWeight: FontWeight.w500, @@ -920,22 +514,8 @@ class STextStyles { } static TextStyle desktopButtonSecondaryEnabled(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).buttonTextSecondary, - fontWeight: FontWeight.w500, - fontSize: 20, - height: 26 / 20, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).buttonTextSecondary, - fontWeight: FontWeight.w500, - fontSize: 20, - height: 26 / 20, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).buttonTextSecondary, fontWeight: FontWeight.w500, @@ -946,22 +526,8 @@ class STextStyles { } static TextStyle desktopButtonSecondaryDisabled(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).buttonTextSecondaryDisabled, - fontWeight: FontWeight.w500, - fontSize: 20, - height: 26 / 20, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).buttonTextSecondaryDisabled, - fontWeight: FontWeight.w500, - fontSize: 20, - height: 26 / 20, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).buttonTextSecondaryDisabled, fontWeight: FontWeight.w500, @@ -972,74 +538,49 @@ class STextStyles { } static TextStyle desktopTextSmall(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w500, - fontSize: 18, - height: 27 / 18, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w500, - fontSize: 18, - height: 27 / 18, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + case "dark": return GoogleFonts.inter( color: _theme(context).buttonTextPrimaryDisabled, fontWeight: FontWeight.w500, fontSize: 18, height: 27 / 18, ); + + default: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w500, + fontSize: 18, + height: 27 / 18, + ); } } static TextStyle desktopTextSmallBold(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w700, - fontSize: 18, - height: 27 / 18, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w700, - fontSize: 18, - height: 27 / 18, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + case "dark": + case "oled_black": return GoogleFonts.inter( color: _theme(context).buttonTextPrimaryDisabled, fontWeight: FontWeight.w700, fontSize: 18, height: 27 / 18, ); + + default: + return GoogleFonts.inter( + color: _theme(context).textDark, + fontWeight: FontWeight.w700, + fontSize: 18, + height: 27 / 18, + ); } } static TextStyle desktopTextExtraSmall(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).buttonTextPrimaryDisabled, - fontWeight: FontWeight.w500, - fontSize: 16, - height: 24 / 16, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).buttonTextPrimaryDisabled, - fontWeight: FontWeight.w500, - fontSize: 16, - height: 24 / 16, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).buttonTextPrimaryDisabled, fontWeight: FontWeight.w500, @@ -1050,22 +591,8 @@ class STextStyles { } static TextStyle desktopTextExtraExtraSmall(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textSubtitle1, - fontWeight: FontWeight.w500, - fontSize: 14, - height: 21 / 14, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textSubtitle1, - fontWeight: FontWeight.w500, - fontSize: 14, - height: 21 / 14, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textSubtitle1, fontWeight: FontWeight.w500, @@ -1076,22 +603,8 @@ class STextStyles { } static TextStyle desktopTextExtraExtraSmall600(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w600, - fontSize: 14, - height: 21 / 14, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w600, - fontSize: 14, - height: 21 / 14, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark, fontWeight: FontWeight.w600, @@ -1102,22 +615,8 @@ class STextStyles { } static TextStyle desktopButtonSmallSecondaryEnabled(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).buttonTextSecondary, - fontWeight: FontWeight.w500, - fontSize: 16, - height: 24 / 16, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).buttonTextSecondary, - fontWeight: FontWeight.w500, - fontSize: 16, - height: 24 / 16, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).buttonTextSecondary, fontWeight: FontWeight.w500, @@ -1128,22 +627,8 @@ class STextStyles { } static TextStyle desktopTextFieldLabel(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textSubtitle2, - fontWeight: FontWeight.w500, - fontSize: 20, - height: 30 / 20, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textSubtitle2, - fontWeight: FontWeight.w500, - fontSize: 20, - height: 30 / 20, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textSubtitle2, fontWeight: FontWeight.w500, @@ -1154,22 +639,8 @@ class STextStyles { } static TextStyle desktopMenuItem(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark.withOpacity(0.8), - fontWeight: FontWeight.w500, - fontSize: 16, - height: 20.8 / 16, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark.withOpacity(0.8), - fontWeight: FontWeight.w500, - fontSize: 16, - height: 20.8 / 16, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark.withOpacity(0.8), fontWeight: FontWeight.w500, @@ -1180,22 +651,8 @@ class STextStyles { } static TextStyle desktopMenuItemSelected(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w500, - fontSize: 16, - height: 20.8 / 16, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w500, - fontSize: 16, - height: 20.8 / 16, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark, fontWeight: FontWeight.w500, @@ -1206,22 +663,8 @@ class STextStyles { } static TextStyle settingsMenuItem(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark.withOpacity(0.5), - fontWeight: FontWeight.w500, - fontSize: 16, - height: 20.8 / 16, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark.withOpacity(0.5), - fontWeight: FontWeight.w500, - fontSize: 16, - height: 20.8 / 16, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark.withOpacity(0.5), fontWeight: FontWeight.w500, @@ -1232,22 +675,8 @@ class STextStyles { } static TextStyle settingsMenuItemSelected(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w500, - fontSize: 16, - height: 20.8 / 16, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - color: _theme(context).textDark, - fontWeight: FontWeight.w500, - fontSize: 16, - height: 20.8 / 16, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( color: _theme(context).textDark, fontWeight: FontWeight.w500, @@ -1258,20 +687,8 @@ class STextStyles { } static TextStyle stepIndicator(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.roboto( - color: _theme(context).textDark, - fontWeight: FontWeight.w600, - fontSize: 8, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.roboto( - color: _theme(context).textDark, - fontWeight: FontWeight.w600, - fontSize: 8, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.roboto( color: _theme(context).textDark, fontWeight: FontWeight.w600, @@ -1281,20 +698,8 @@ class STextStyles { } static TextStyle numberDefault(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.roboto( - color: _theme(context).numberTextDefault, - fontWeight: FontWeight.w400, - fontSize: 26, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.roboto( - color: _theme(context).numberTextDefault, - fontWeight: FontWeight.w400, - fontSize: 26, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.roboto( color: _theme(context).numberTextDefault, fontWeight: FontWeight.w400, @@ -1304,22 +709,8 @@ class STextStyles { } static TextStyle datePicker400(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - letterSpacing: 0.5, - color: _theme(context).accentColorDark, - fontWeight: FontWeight.w400, - fontSize: 12, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - letterSpacing: 0.5, - color: _theme(context).accentColorDark, - fontWeight: FontWeight.w400, - fontSize: 12, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( letterSpacing: 0.5, color: _theme(context).accentColorDark, @@ -1330,22 +721,8 @@ class STextStyles { } static TextStyle datePicker600(BuildContext context) { - switch (_theme(context).themeType) { - case ThemeType.light: - return GoogleFonts.inter( - letterSpacing: 0.5, - color: _theme(context).accentColorDark, - fontWeight: FontWeight.w600, - fontSize: 16, - ); - case ThemeType.oceanBreeze: - return GoogleFonts.inter( - letterSpacing: 0.5, - color: _theme(context).accentColorDark, - fontWeight: FontWeight.w600, - fontSize: 16, - ); - case ThemeType.dark: + switch (_theme(context).themeId) { + default: return GoogleFonts.inter( letterSpacing: 0.5, color: _theme(context).accentColorDark, diff --git a/lib/utilities/theme/color_theme.dart b/lib/utilities/theme/color_theme.dart deleted file mode 100644 index 43012ffe6..000000000 --- a/lib/utilities/theme/color_theme.dart +++ /dev/null @@ -1,254 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/theme/dark_colors.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/ocean_breeze_colors.dart'; - -enum ThemeType { - light, - dark, - oceanBreeze, -} - -extension ThemeTypeExt on ThemeType { - StackColorTheme get colorTheme { - switch (this) { - case ThemeType.light: - return LightColors(); - case ThemeType.dark: - return DarkColors(); - case ThemeType.oceanBreeze: - return OceanBreezeColors(); - } - } - - String get prettyName { - switch (this) { - case ThemeType.light: - return "Light"; - case ThemeType.dark: - return "Dark"; - case ThemeType.oceanBreeze: - return "Ocean Breeze"; - } - } -} - -abstract class StackColorTheme { - ThemeType get themeType; - - Color get background; - Color get backgroundAppBar; - - Gradient? get gradientBackground; - - Color get overlay; - - Color get accentColorBlue; - Color get accentColorGreen; - Color get accentColorYellow; - Color get accentColorRed; - Color get accentColorOrange; - Color get accentColorDark; - - Color get shadow; - - Color get textDark; - Color get textDark2; - Color get textDark3; - Color get textSubtitle1; - Color get textSubtitle2; - Color get textSubtitle3; - Color get textSubtitle4; - Color get textSubtitle5; - Color get textSubtitle6; - Color get textWhite; - Color get textFavoriteCard; - Color get textError; - -// button background - Color get buttonBackPrimary; - Color get buttonBackSecondary; - Color get buttonBackPrimaryDisabled; - Color get buttonBackSecondaryDisabled; - Color get buttonBackBorder; - Color get buttonBackBorderDisabled; - Color get numberBackDefault; - Color get numpadBackDefault; - Color get bottomNavBack; - -// button text/element - Color get buttonTextPrimary; - Color get buttonTextSecondary; - Color get buttonTextPrimaryDisabled; - Color get buttonTextSecondaryDisabled; - Color get buttonTextBorder; - Color get buttonTextDisabled; - Color get buttonTextBorderless; - Color get buttonTextBorderlessDisabled; - Color get numberTextDefault; - Color get numpadTextDefault; - Color get bottomNavText; - -// switch background - Color get switchBGOn; - Color get switchBGOff; - Color get switchBGDisabled; - -// switch circle - Color get switchCircleOn; - Color get switchCircleOff; - Color get switchCircleDisabled; - -// step indicator background - Color get stepIndicatorBGCheck; - Color get stepIndicatorBGNumber; - Color get stepIndicatorBGInactive; - Color get stepIndicatorBGLines; - Color get stepIndicatorBGLinesInactive; - Color get stepIndicatorIconText; - Color get stepIndicatorIconNumber; - Color get stepIndicatorIconInactive; - -// checkbox - Color get checkboxBGChecked; - Color get checkboxBorderEmpty; - Color get checkboxBGDisabled; - Color get checkboxIconChecked; - Color get checkboxIconDisabled; - Color get checkboxTextLabel; - -// snack bar - Color get snackBarBackSuccess; - Color get snackBarBackError; - Color get snackBarBackInfo; - Color get snackBarTextSuccess; - Color get snackBarTextError; - Color get snackBarTextInfo; - -// icons - Color get bottomNavIconBack; - Color get bottomNavIconIcon; - Color get topNavIconPrimary; - Color get topNavIconGreen; - Color get topNavIconYellow; - Color get topNavIconRed; - Color get settingsIconBack; - Color get settingsIconIcon; - Color get settingsIconBack2; - Color get settingsIconElement; - -// text field - Color get textFieldActiveBG; - Color get textFieldDefaultBG; - Color get textFieldErrorBG; - Color get textFieldSuccessBG; - Color get textFieldActiveSearchIconLeft; - Color get textFieldDefaultSearchIconLeft; - Color get textFieldErrorSearchIconLeft; - Color get textFieldSuccessSearchIconLeft; - Color get textFieldActiveText; - Color get textFieldDefaultText; - Color get textFieldErrorText; - Color get textFieldSuccessText; - Color get textFieldActiveLabel; - Color get textFieldErrorLabel; - Color get textFieldSuccessLabel; - Color get textFieldActiveSearchIconRight; - Color get textFieldDefaultSearchIconRight; - Color get textFieldErrorSearchIconRight; - Color get textFieldSuccessSearchIconRight; - -// settings item level2 - Color get settingsItem2ActiveBG; - Color get settingsItem2ActiveText; - Color get settingsItem2ActiveSub; - -// radio buttons - Color get radioButtonIconBorder; - Color get radioButtonIconBorderDisabled; - Color get radioButtonBorderEnabled; - Color get radioButtonBorderDisabled; - Color get radioButtonIconCircle; - Color get radioButtonIconEnabled; - Color get radioButtonTextEnabled; - Color get radioButtonTextDisabled; - Color get radioButtonLabelEnabled; - Color get radioButtonLabelDisabled; - -// info text - Color get infoItemBG; - Color get infoItemLabel; - Color get infoItemText; - Color get infoItemIcons; - -// popup - Color get popupBG; - -// currency list - Color get currencyListItemBG; - -// bottom nav - Color get stackWalletBG; - Color get stackWalletMid; - Color get stackWalletBottom; - Color get bottomNavShadow; - - Color get favoriteStarActive; - Color get favoriteStarInactive; - - Color get splash; - Color get highlight; - Color get warningForeground; - Color get warningBackground; - - Color get loadingOverlayTextColor; - Color get myStackContactIconBG; - Color get textConfirmTotalAmount; - Color get textSelectedWordTableItem; -} - -class CoinThemeColor { - const CoinThemeColor(); - - Color get bitcoin => const Color(0xFFFCC17B); - Color get litecoin => const Color(0xFF7FA6E1); - Color get bitcoincash => const Color(0xFF7BCFB8); - Color get firo => const Color(0xFFFF897A); - Color get dogecoin => const Color(0xFFFFE079); - Color get epicCash => const Color(0xFFC5C7CB); - Color get monero => const Color(0xFFFF9E6B); - Color get namecoin => const Color(0xFF91B1E1); - Color get wownero => const Color(0xFFED80C1); - Color get particl => const Color(0xFF8175BD); - - Color forCoin(Coin coin) { - switch (coin) { - case Coin.bitcoin: - case Coin.bitcoinTestNet: - return bitcoin; - case Coin.litecoin: - case Coin.litecoinTestNet: - return litecoin; - case Coin.bitcoincash: - case Coin.bitcoincashTestnet: - return bitcoincash; - case Coin.dogecoin: - case Coin.dogecoinTestNet: - return dogecoin; - case Coin.epicCash: - return epicCash; - case Coin.firo: - case Coin.firoTestNet: - return firo; - case Coin.monero: - return monero; - case Coin.namecoin: - return namecoin; - case Coin.wownero: - return wownero; - case Coin.particl: - return particl; - } - } -} diff --git a/lib/utilities/theme/dark_colors.dart b/lib/utilities/theme/dark_colors.dart deleted file mode 100644 index d55581921..000000000 --- a/lib/utilities/theme/dark_colors.dart +++ /dev/null @@ -1,311 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stackwallet/utilities/theme/color_theme.dart'; - -class DarkColors extends StackColorTheme { - @override - ThemeType get themeType => ThemeType.dark; - - @override - Color get background => const Color(0xFF2A2D34); - @override - Color get backgroundAppBar => background; - @override - Gradient? get gradientBackground => null; - - @override - Color get overlay => const Color(0xFF111215); - - @override - Color get accentColorBlue => const Color(0xFF4C86E9); - @override - Color get accentColorGreen => const Color(0xFF4CC0A0); - @override - Color get accentColorYellow => const Color(0xFFF7D65D); - @override - Color get accentColorRed => const Color(0xFFD34E50); - @override - Color get accentColorOrange => const Color(0xFFFEA68D); - @override - Color get accentColorDark => const Color(0xFFF3F3F3); - - @override - Color get shadow => const Color(0x0F2D3132); - - @override - Color get textDark => const Color(0xFFF3F3F3); - @override - Color get textDark2 => const Color(0xFFDBDBDB); - @override - Color get textDark3 => const Color(0xFFEEEFF1); - @override - Color get textSubtitle1 => const Color(0xFF9E9E9E); - @override - Color get textSubtitle2 => const Color(0xFF969696); - @override - Color get textSubtitle3 => const Color(0xFFA9ACAC); - @override - Color get textSubtitle4 => const Color(0xFF8E9192); - @override - Color get textSubtitle5 => const Color(0xFF747778); - @override - Color get textSubtitle6 => const Color(0xFF414141); - @override - Color get textWhite => const Color(0xFF232323); - @override - Color get textFavoriteCard => const Color(0xFF232323); - @override - Color get textError => const Color(0xFFF37475); - - // button background - @override - Color get buttonBackPrimary => const Color(0xFF4C86E9); - @override - Color get buttonBackSecondary => const Color(0xFF444E5C); - @override - Color get buttonBackPrimaryDisabled => const Color(0xFF38517C); - @override - Color get buttonBackSecondaryDisabled => const Color(0xFF3B3F46); - @override - Color get buttonBackBorder => const Color(0xFF4C86E9); - @override - Color get buttonBackBorderDisabled => const Color(0xFF314265); - - @override - Color get numberBackDefault => const Color(0xFF484B51); - @override - Color get numpadBackDefault => const Color(0xFF4C86E9); - @override - Color get bottomNavBack => const Color(0xFF3E4148); - - // button text/element - @override - Color get buttonTextPrimary => const Color(0xFFFFFFFF); - @override - Color get buttonTextSecondary => const Color(0xFFFFFFFF); - @override - Color get buttonTextPrimaryDisabled => const Color(0xFFFFFFFF); - @override - Color get buttonTextSecondaryDisabled => const Color(0xFF6A6C71); - @override - Color get buttonTextBorder => const Color(0xFF4C86E9); - @override - Color get buttonTextDisabled => const Color(0xFF314265); - @override - Color get buttonTextBorderless => const Color(0xFF4C86E9); - @override - Color get buttonTextBorderlessDisabled => const Color(0xFFB6B6B6); - @override - Color get numberTextDefault => const Color(0xFFFFFFFF); - @override - Color get numpadTextDefault => const Color(0xFFFFFFFF); - @override - Color get bottomNavText => const Color(0xFFFFFFFF); - - // switch - @override - Color get switchBGOn => const Color(0xFF4C86E9); - @override - Color get switchBGOff => const Color(0xFFC1D9FF); - @override - Color get switchBGDisabled => const Color(0xFFB5B7BA); - @override - Color get switchCircleOn => const Color(0xFFC9DDFF); - @override - Color get switchCircleOff => const Color(0xFFFFFFFF); - @override - Color get switchCircleDisabled => const Color(0xFFFFFFFF); - - // step indicator background - @override - Color get stepIndicatorBGCheck => const Color(0xFF4C86E9); - @override - Color get stepIndicatorBGNumber => const Color(0xFF4C86E9); - @override - Color get stepIndicatorBGInactive => const Color(0xFF3B3F46); - @override - Color get stepIndicatorBGLines => const Color(0xFF4C86E9); - @override - Color get stepIndicatorBGLinesInactive => const Color(0xFF3B3F46); - @override - Color get stepIndicatorIconText => const Color(0xFFFFFFFF); - @override - Color get stepIndicatorIconNumber => const Color(0xFFFFFFFF); - @override - Color get stepIndicatorIconInactive => const Color(0xFF747474); - - // checkbox - @override - Color get checkboxBGChecked => const Color(0xFF4C86E9); - @override - Color get checkboxBorderEmpty => const Color(0xFF8E9192); - @override - Color get checkboxBGDisabled => const Color(0xFFADC7EC); - @override - Color get checkboxIconChecked => const Color(0xFFFFFFFF); - @override - Color get checkboxIconDisabled => const Color(0xFFFFFFFF); - @override - Color get checkboxTextLabel => const Color(0xFFFFFFFF); - - // snack bar - @override - Color get snackBarBackSuccess => const Color(0xFF8EF5C3); - @override - Color get snackBarBackError => const Color(0xFFFFB4A9); - @override - Color get snackBarBackInfo => const Color(0xFFB4C4FF); - @override - Color get snackBarTextSuccess => const Color(0xFF003921); - @override - Color get snackBarTextError => const Color(0xFF690001); - @override - Color get snackBarTextInfo => const Color(0xFF00297A); - - // icons - @override - Color get bottomNavIconBack => const Color(0xFF7F8185); - @override - Color get bottomNavIconIcon => const Color(0xFFFFFFFF); - - @override - Color get topNavIconPrimary => const Color(0xFFFFFFFF); - @override - Color get topNavIconGreen => const Color(0xFF4CC0A0); - @override - Color get topNavIconYellow => const Color(0xFFF7D65D); - @override - Color get topNavIconRed => const Color(0xFFD34E50); - - @override - Color get settingsIconBack => const Color(0xFFE0E3E3); - @override - Color get settingsIconIcon => const Color(0xFF232323); - @override - Color get settingsIconBack2 => const Color(0xFF94D6C4); - @override - Color get settingsIconElement => const Color(0xFF00A578); - - // text field - @override - Color get textFieldActiveBG => const Color(0xFF4C5360); - @override - Color get textFieldDefaultBG => const Color(0xFF444953); - @override - Color get textFieldErrorBG => const Color(0xFFFFB4A9); - @override - Color get textFieldSuccessBG => const Color(0xFF8EF5C3); - - @override - Color get textFieldActiveSearchIconLeft => const Color(0xFFA9ACAC); - @override - Color get textFieldDefaultSearchIconLeft => const Color(0xFFA9ACAC); - @override - Color get textFieldErrorSearchIconLeft => const Color(0xFF690001); - @override - Color get textFieldSuccessSearchIconLeft => const Color(0xFF003921); - - @override - Color get textFieldActiveText => const Color(0xFFFFFFFF); - @override - Color get textFieldDefaultText => const Color(0xFFA9ACAC); - @override - Color get textFieldErrorText => const Color(0xFF000000); - @override - Color get textFieldSuccessText => const Color(0xFF000000); - - @override - Color get textFieldActiveLabel => const Color(0xFFA9ACAC); - @override - Color get textFieldErrorLabel => const Color(0xFF690001); - @override - Color get textFieldSuccessLabel => const Color(0xFF003921); - - @override - Color get textFieldActiveSearchIconRight => const Color(0xFFC4C7C7); - @override - Color get textFieldDefaultSearchIconRight => const Color(0xFF747778); - @override - Color get textFieldErrorSearchIconRight => const Color(0xFF690001); - @override - Color get textFieldSuccessSearchIconRight => const Color(0xFF003921); - - // settings item level2 - @override - Color get settingsItem2ActiveBG => const Color(0xFF484B51); - @override - Color get settingsItem2ActiveText => const Color(0xFFFFFFFF); - @override - Color get settingsItem2ActiveSub => const Color(0xFF9E9E9E); - - // radio buttons - @override - Color get radioButtonIconBorder => const Color(0xFF4C86E9); - @override - Color get radioButtonIconBorderDisabled => const Color(0xFF9E9E9E); - @override - Color get radioButtonBorderEnabled => const Color(0xFF4C86E9); - @override - Color get radioButtonBorderDisabled => const Color(0xFFCDCDCD); - @override - Color get radioButtonIconCircle => const Color(0xFF9E9E9E); - @override - Color get radioButtonIconEnabled => const Color(0xFF4C86E9); - @override - Color get radioButtonTextEnabled => const Color(0xFF44464E); - @override - Color get radioButtonTextDisabled => const Color(0xFF44464E); - @override - Color get radioButtonLabelEnabled => const Color(0xFF8E9192); - @override - Color get radioButtonLabelDisabled => const Color(0xFF8E9192); - - // info text - @override - Color get infoItemBG => const Color(0xFF333942); - @override - Color get infoItemLabel => const Color(0xFF9E9E9E); - @override - Color get infoItemText => const Color(0xFFFFFFFF); - @override - Color get infoItemIcons => const Color(0xFF4C86E9); - - // popup - @override - Color get popupBG => const Color(0xFF333942); - - // currency list - @override - Color get currencyListItemBG => const Color(0xFF484B51); - - // bottom nav - @override - Color get stackWalletBG => const Color(0xFF35383D); - @override - Color get stackWalletMid => const Color(0xFF292D34); - @override - Color get stackWalletBottom => const Color(0xFFFFFFFF); - @override - Color get bottomNavShadow => const Color(0xFF282E33); - - @override - Color get favoriteStarActive => accentColorYellow; - @override - Color get favoriteStarInactive => textSubtitle2; - - @override - Color get splash => const Color(0x358E9192); - @override - Color get highlight => const Color(0x44A9ACAC); - @override - Color get warningForeground => snackBarTextError; - @override - Color get warningBackground => const Color(0xFFFFB4A9); - @override - Color get loadingOverlayTextColor => const Color(0xFFF7F7F7); - @override - Color get myStackContactIconBG => const Color(0x88747778); - @override - Color get textConfirmTotalAmount => const Color(0xFF003921); - @override - Color get textSelectedWordTableItem => const Color(0xFF00297A); -} diff --git a/lib/utilities/theme/light_colors.dart b/lib/utilities/theme/light_colors.dart deleted file mode 100644 index 1303d0b75..000000000 --- a/lib/utilities/theme/light_colors.dart +++ /dev/null @@ -1,311 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stackwallet/utilities/theme/color_theme.dart'; - -class LightColors extends StackColorTheme { - @override - ThemeType get themeType => ThemeType.light; - - @override - Color get background => const Color(0xFFF7F7F7); - @override - Color get backgroundAppBar => background; - @override - Gradient? get gradientBackground => null; - - @override - Color get overlay => const Color(0xFF111215); - - @override - Color get accentColorBlue => const Color(0xFF0052DF); - @override - Color get accentColorGreen => const Color(0xFF4CC0A0); - @override - Color get accentColorYellow => const Color(0xFFF7D65D); - @override - Color get accentColorRed => const Color(0xFFD34E50); - @override - Color get accentColorOrange => const Color(0xFFFEA68D); - @override - Color get accentColorDark => const Color(0xFF232323); - - @override - Color get shadow => const Color(0x0F2D3132); - - @override - Color get textDark => const Color(0xFF232323); - @override - Color get textDark2 => const Color(0xFF414141); - @override - Color get textDark3 => const Color(0xFF747778); - @override - Color get textSubtitle1 => const Color(0xFF8E9192); - @override - Color get textSubtitle2 => const Color(0xFFA9ACAC); - @override - Color get textSubtitle3 => const Color(0xFFC4C7C7); - @override - Color get textSubtitle4 => const Color(0xFFE0E3E3); - @override - Color get textSubtitle5 => const Color(0xFFEEEFF1); - @override - Color get textSubtitle6 => const Color(0xFFF5F5F5); - @override - Color get textWhite => const Color(0xFFFFFFFF); - @override - Color get textFavoriteCard => const Color(0xFF232323); - @override - Color get textError => const Color(0xFF930006); - - // button background - @override - Color get buttonBackPrimary => const Color(0xFF232323); - @override - Color get buttonBackSecondary => const Color(0xFFE0E3E3); - @override - Color get buttonBackPrimaryDisabled => const Color(0xFFD7D7D7); - @override - Color get buttonBackSecondaryDisabled => const Color(0xFFF0F1F1); - @override - Color get buttonBackBorder => const Color(0xFF232323); - @override - Color get buttonBackBorderDisabled => const Color(0xFFB6B6B6); - - @override - Color get numberBackDefault => const Color(0xFFFFFFFF); - @override - Color get numpadBackDefault => const Color(0xFF232323); - @override - Color get bottomNavBack => const Color(0xFFFFFFFF); - - // button text/element - @override - Color get buttonTextPrimary => const Color(0xFFFFFFFF); - @override - Color get buttonTextSecondary => const Color(0xFF232323); - @override - Color get buttonTextPrimaryDisabled => const Color(0xFFF8F8F8); - @override - Color get buttonTextSecondaryDisabled => const Color(0xFFB7B7B7); - @override - Color get buttonTextBorder => const Color(0xFF232323); - @override - Color get buttonTextDisabled => const Color(0xFFB6B6B6); - @override - Color get buttonTextBorderless => const Color(0xFF0052DF); - @override - Color get buttonTextBorderlessDisabled => const Color(0xFFB6B6B6); - @override - Color get numberTextDefault => const Color(0xFF232323); - @override - Color get numpadTextDefault => const Color(0xFFFFFFFF); - @override - Color get bottomNavText => const Color(0xFF232323); - - // switch - @override - Color get switchBGOn => const Color(0xFF0052DF); - @override - Color get switchBGOff => const Color(0xFFD8E4FB); - @override - Color get switchBGDisabled => const Color(0xFFC5C6C9); - @override - Color get switchCircleOn => const Color(0xFFDAE2FF); - @override - Color get switchCircleOff => const Color(0xFFFBFCFF); - @override - Color get switchCircleDisabled => const Color(0xFFFBFCFF); - - // step indicator background - @override - Color get stepIndicatorBGCheck => const Color(0xFFD9E2FF); - @override - Color get stepIndicatorBGNumber => const Color(0xFFD9E2FF); - @override - Color get stepIndicatorBGInactive => const Color(0xFFCDCDCD); - @override - Color get stepIndicatorBGLines => const Color(0xFF0056D2); - @override - Color get stepIndicatorBGLinesInactive => const Color(0xFFCDCDCD); - @override - Color get stepIndicatorIconText => const Color(0xFF0056D2); - @override - Color get stepIndicatorIconNumber => const Color(0xFF0056D2); - @override - Color get stepIndicatorIconInactive => const Color(0xFFF7F7F7); - - // checkbox - @override - Color get checkboxBGChecked => const Color(0xFF0056D2); - @override - Color get checkboxBorderEmpty => const Color(0xFF8E9192); - @override - Color get checkboxBGDisabled => const Color(0xFFADC7EC); - @override - Color get checkboxIconChecked => const Color(0xFFFFFFFF); - @override - Color get checkboxIconDisabled => const Color(0xFFFFFFFF); - @override - Color get checkboxTextLabel => const Color(0xFF232323); - - // snack bar - @override - Color get snackBarBackSuccess => const Color(0xFFB9E9D4); - @override - Color get snackBarBackError => const Color(0xFFFFDAD4); - @override - Color get snackBarBackInfo => const Color(0xFFDAE2FF); - @override - Color get snackBarTextSuccess => const Color(0xFF006C4D); - @override - Color get snackBarTextError => const Color(0xFF930006); - @override - Color get snackBarTextInfo => const Color(0xFF002A78); - - // icons - @override - Color get bottomNavIconBack => const Color(0xFFA2A2A2); - @override - Color get bottomNavIconIcon => const Color(0xFF232323); - - @override - Color get topNavIconPrimary => const Color(0xFF232323); - @override - Color get topNavIconGreen => const Color(0xFF00A578); - @override - Color get topNavIconYellow => const Color(0xFFF4C517); - @override - Color get topNavIconRed => const Color(0xFFC00205); - - @override - Color get settingsIconBack => const Color(0xFFE0E3E3); - @override - Color get settingsIconIcon => const Color(0xFF232323); - @override - Color get settingsIconBack2 => const Color(0xFF94D6C4); - @override - Color get settingsIconElement => const Color(0xFF00A578); - - // text field - @override - Color get textFieldActiveBG => const Color(0xFFEEEFF1); - @override - Color get textFieldDefaultBG => const Color(0xFFEEEFF1); - @override - Color get textFieldErrorBG => const Color(0xFFFFDAD4); - @override - Color get textFieldSuccessBG => const Color(0xFFB9E9D4); - - @override - Color get textFieldActiveSearchIconLeft => const Color(0xFFA9ACAC); - @override - Color get textFieldDefaultSearchIconLeft => const Color(0xFFA9ACAC); - @override - Color get textFieldErrorSearchIconLeft => const Color(0xFF930006); - @override - Color get textFieldSuccessSearchIconLeft => const Color(0xFF006C4D); - - @override - Color get textFieldActiveText => const Color(0xFF232323); - @override - Color get textFieldDefaultText => const Color(0xFFA9ACAC); - @override - Color get textFieldErrorText => const Color(0xFF000000); - @override - Color get textFieldSuccessText => const Color(0xFF000000); - - @override - Color get textFieldActiveLabel => const Color(0xFFA9ACAC); - @override - Color get textFieldErrorLabel => const Color(0xFF930006); - @override - Color get textFieldSuccessLabel => const Color(0xFF006C4D); - - @override - Color get textFieldActiveSearchIconRight => const Color(0xFF747778); - @override - Color get textFieldDefaultSearchIconRight => const Color(0xFF747778); - @override - Color get textFieldErrorSearchIconRight => const Color(0xFF930006); - @override - Color get textFieldSuccessSearchIconRight => const Color(0xFF006C4D); - - // settings item level2 - @override - Color get settingsItem2ActiveBG => const Color(0xFFFFFFFF); - @override - Color get settingsItem2ActiveText => const Color(0xFF232323); - @override - Color get settingsItem2ActiveSub => const Color(0xFF8E9192); - - // radio buttons - @override - Color get radioButtonIconBorder => const Color(0xFF0056D2); - @override - Color get radioButtonIconBorderDisabled => const Color(0xFF8F909A); - @override - Color get radioButtonBorderEnabled => const Color(0xFF0056D2); - @override - Color get radioButtonBorderDisabled => const Color(0xFF8F909A); - @override - Color get radioButtonIconCircle => const Color(0xFF0056D2); - @override - Color get radioButtonIconEnabled => const Color(0xFF0056D2); - @override - Color get radioButtonTextEnabled => const Color(0xFF44464E); - @override - Color get radioButtonTextDisabled => const Color(0xFF44464E); - @override - Color get radioButtonLabelEnabled => const Color(0xFF8E9192); - @override - Color get radioButtonLabelDisabled => const Color(0xFF8E9192); - - // info text - @override - Color get infoItemBG => const Color(0xFFFFFFFF); - @override - Color get infoItemLabel => const Color(0xFF8E9192); - @override - Color get infoItemText => const Color(0xFF232323); - @override - Color get infoItemIcons => const Color(0xFF0056D2); - - // popup - @override - Color get popupBG => const Color(0xFFFFFFFF); - - // currency list - @override - Color get currencyListItemBG => const Color(0xFFF9F9FC); - - // bottom nav - @override - Color get stackWalletBG => const Color(0xFFFFFFFF); - @override - Color get stackWalletMid => const Color(0xFFFFFFFF); - @override - Color get stackWalletBottom => const Color(0xFF232323); - @override - Color get bottomNavShadow => const Color(0xFF282E33); - - @override - Color get favoriteStarActive => infoItemIcons; - @override - Color get favoriteStarInactive => textSubtitle3; - - @override - Color get splash => const Color(0x358E9192); - @override - Color get highlight => const Color(0x44A9ACAC); - @override - Color get warningForeground => textDark; - @override - Color get warningBackground => const Color(0xFFFFDAD3); - @override - Color get loadingOverlayTextColor => const Color(0xFFF7F7F7); - @override - Color get myStackContactIconBG => textFieldDefaultBG; - @override - Color get textConfirmTotalAmount => const Color(0xFF232323); - @override - Color get textSelectedWordTableItem => const Color(0xFF232323); -} diff --git a/lib/utilities/theme/ocean_breeze_colors.dart b/lib/utilities/theme/ocean_breeze_colors.dart deleted file mode 100644 index 8c4259bb9..000000000 --- a/lib/utilities/theme/ocean_breeze_colors.dart +++ /dev/null @@ -1,318 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stackwallet/utilities/theme/color_theme.dart'; - -class OceanBreezeColors extends StackColorTheme { - @override - ThemeType get themeType => ThemeType.oceanBreeze; - - @override - Color get background => Colors.transparent; - @override - Color get backgroundAppBar => const Color(0xFFF3F7FA); - @override - Gradient? get gradientBackground => const LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Color(0xFFF3F7FA), - Color(0xFFE8F2F9), - ], - ); - - @override - Color get overlay => const Color(0xFF111215); - - @override - Color get accentColorBlue => const Color(0xFF077CBE); - @override - Color get accentColorGreen => const Color(0xFF00A591); - @override - Color get accentColorYellow => const Color(0xFFF4C517); - @override - Color get accentColorRed => const Color(0xFFD1382D); - @override - Color get accentColorOrange => const Color(0xFFFF985F); - @override - Color get accentColorDark => const Color(0xFF227386); - - @override - Color get shadow => const Color(0x0F2D3132); - - @override - Color get textDark => const Color(0xFF232323); - @override - Color get textDark2 => const Color(0xFF333333); - @override - Color get textDark3 => const Color(0xFF696B6C); - @override - Color get textSubtitle1 => const Color(0xFF7E8284); - @override - Color get textSubtitle2 => const Color(0xFF919393); - @override - Color get textSubtitle3 => const Color(0xFFB0B2B2); - @override - Color get textSubtitle4 => const Color(0xFFD1D3D3); - @override - Color get textSubtitle5 => const Color(0xFFDEDFE1); - @override - Color get textSubtitle6 => const Color(0xFFF1F1F1); - @override - Color get textWhite => const Color(0xFFFFFFFF); - @override - Color get textFavoriteCard => const Color(0xFF232323); - @override - Color get textError => const Color(0xFF8D0006); - - // button background - @override - Color get buttonBackPrimary => const Color(0xFF227386); - @override - Color get buttonBackSecondary => const Color(0xFFC2DAE2); - @override - Color get buttonBackPrimaryDisabled => const Color(0xFFBDD5DB); - @override - Color get buttonBackSecondaryDisabled => const Color(0xFFBDBDBD); - @override - Color get buttonBackBorder => const Color(0xFF227386); - @override - Color get buttonBackBorderDisabled => const Color(0xFFBDD5DB); - - @override - Color get numberBackDefault => const Color(0xFFFFFFFF); - @override - Color get numpadBackDefault => const Color(0xFF227386); - @override - Color get bottomNavBack => const Color(0xFFFFFFFF); - - // button text/element - @override - Color get buttonTextPrimary => const Color(0xFFFFFFFF); - @override - Color get buttonTextSecondary => const Color(0xFF232323); - @override - Color get buttonTextPrimaryDisabled => const Color(0xFFFFFFFF); - @override - Color get buttonTextSecondaryDisabled => const Color(0xFFBDD5DB); - @override - Color get buttonTextBorder => const Color(0xFF227386); - @override - Color get buttonTextDisabled => const Color(0xFFFFFFFF); - @override - Color get buttonTextBorderless => const Color(0xFF056EC6); - @override - Color get buttonTextBorderlessDisabled => const Color(0xFFB6B6B6); - @override - Color get numberTextDefault => const Color(0xFF232323); - @override - Color get numpadTextDefault => const Color(0xFFFFFFFF); - @override - Color get bottomNavText => const Color(0xFF232323); - - // switch - @override - Color get switchBGOn => const Color(0xFF056EC6); - @override - Color get switchBGOff => const Color(0xFFCCDBF9); - @override - Color get switchBGDisabled => const Color(0xFFC5C6C9); - @override - Color get switchCircleOn => const Color(0xFFDAE2FF); - @override - Color get switchCircleOff => const Color(0xFFFBFCFF); - @override - Color get switchCircleDisabled => const Color(0xFFFBFCFF); - - // step indicator background - @override - Color get stepIndicatorBGCheck => const Color(0xFFCDD9FF); - @override - Color get stepIndicatorBGNumber => const Color(0xFFCDD9FF); - @override - Color get stepIndicatorBGInactive => const Color(0xFFA6C7D1); - @override - Color get stepIndicatorBGLines => const Color(0xFF90B8DC); - @override - Color get stepIndicatorBGLinesInactive => const Color(0xFFBCD4EA); - @override - Color get stepIndicatorIconText => const Color(0xFF005BAF); - @override - Color get stepIndicatorIconNumber => const Color(0xFF005BAF); - @override - Color get stepIndicatorIconInactive => const Color(0xFFD4DFFF); - - // checkbox - @override - Color get checkboxBGChecked => const Color(0xFF056EC6); - @override - Color get checkboxBorderEmpty => const Color(0xFF8C8F90); - @override - Color get checkboxBGDisabled => const Color(0xFFB0C9ED); - @override - Color get checkboxIconChecked => const Color(0xFFFFFFFF); - @override - Color get checkboxIconDisabled => const Color(0xFFFFFFFF); - @override - Color get checkboxTextLabel => const Color(0xFF232323); - - // snack bar - @override - Color get snackBarBackSuccess => const Color(0xFFADD6D2); - @override - Color get snackBarBackError => const Color(0xFFF6C7C3); - @override - Color get snackBarBackInfo => const Color(0xFFCCD7FF); - @override - Color get snackBarTextSuccess => const Color(0xFF075547); - @override - Color get snackBarTextError => const Color(0xFF8D0006); - @override - Color get snackBarTextInfo => const Color(0xFF002569); - - // icons - @override - Color get bottomNavIconBack => const Color(0xFFA7C7CF); - @override - Color get bottomNavIconIcon => const Color(0xFF227386); - - @override - Color get topNavIconPrimary => const Color(0xFF227386); - @override - Color get topNavIconGreen => const Color(0xFF00A591); - @override - Color get topNavIconYellow => const Color(0xFFFDD33A); - @override - Color get topNavIconRed => const Color(0xFFEA4649); - - @override - Color get settingsIconBack => const Color(0xFFE0E3E3); - @override - Color get settingsIconIcon => const Color(0xFF232323); - @override - Color get settingsIconBack2 => const Color(0xFF80D2C8); - @override - Color get settingsIconElement => const Color(0xFF00A591); - - // text field - @override - Color get textFieldActiveBG => const Color(0xFFD3E3E7); - @override - Color get textFieldDefaultBG => const Color(0xFFD8E7EB); - @override - Color get textFieldErrorBG => const Color(0xFFF6C7C3); - @override - Color get textFieldSuccessBG => const Color(0xFFADD6D2); - - @override - Color get textFieldActiveSearchIconLeft => const Color(0xFF86898C); - @override - Color get textFieldDefaultSearchIconLeft => const Color(0xFF86898C); - @override - Color get textFieldErrorSearchIconLeft => const Color(0xFF8D0006); - @override - Color get textFieldSuccessSearchIconLeft => const Color(0xFF006C4D); - - @override - Color get textFieldActiveText => const Color(0xFF232323); - @override - Color get textFieldDefaultText => const Color(0xFF86898C); - @override - Color get textFieldErrorText => const Color(0xFF000000); - @override - Color get textFieldSuccessText => const Color(0xFF000000); - - @override - Color get textFieldActiveLabel => const Color(0xFF86898C); - @override - Color get textFieldErrorLabel => const Color(0xFF8D0006); - @override - Color get textFieldSuccessLabel => const Color(0xFF077C6E); - - @override - Color get textFieldActiveSearchIconRight => const Color(0xFF388192); - @override - Color get textFieldDefaultSearchIconRight => const Color(0xFF388192); - @override - Color get textFieldErrorSearchIconRight => const Color(0xFF8D0006); - @override - Color get textFieldSuccessSearchIconRight => const Color(0xFF077C6E); - - // settings item level2 - @override - Color get settingsItem2ActiveBG => const Color(0xFFFFFFFF); - @override - Color get settingsItem2ActiveText => const Color(0xFF232323); - @override - Color get settingsItem2ActiveSub => const Color(0xFF8C8F90); - - // radio buttons - @override - Color get radioButtonIconBorder => const Color(0xFF056EC6); - @override - Color get radioButtonIconBorderDisabled => const Color(0xFF8C8D97); - @override - Color get radioButtonBorderEnabled => const Color(0xFF056EC6); - @override - Color get radioButtonBorderDisabled => const Color(0xFF8C8D97); - @override - Color get radioButtonIconCircle => const Color(0xFF056EC6); - @override - Color get radioButtonIconEnabled => const Color(0xFF056EC6); - @override - Color get radioButtonTextEnabled => const Color(0xFF42444B); - @override - Color get radioButtonTextDisabled => const Color(0xFF42444B); - @override - Color get radioButtonLabelEnabled => const Color(0xFF8C8F90); - @override - Color get radioButtonLabelDisabled => const Color(0xFF8C8F90); - - // info text - @override - Color get infoItemBG => const Color(0xFFFFFFFF); - @override - Color get infoItemLabel => const Color(0xFF838788); - @override - Color get infoItemText => const Color(0xFF232323); - @override - Color get infoItemIcons => const Color(0xFF056EC6); - - // popup - @override - Color get popupBG => const Color(0xFFFFFFFF); - - // currency list - @override - Color get currencyListItemBG => const Color(0xFFF0F5F7); - - // bottom nav - @override - Color get stackWalletBG => const Color(0xFFFFFFFF); - @override - Color get stackWalletMid => const Color(0xFFFFFFFF); - @override - Color get stackWalletBottom => const Color(0xFF232323); - @override - Color get bottomNavShadow => const Color(0xFF388192); - - @override - Color get favoriteStarActive => const Color(0xFFF4C517); - @override - Color get favoriteStarInactive => const Color(0xFFB0B2B2); - - @override - Color get splash => const Color(0xFF8E9192); - @override - Color get highlight => const Color(0xFFA9ACAC); - @override - Color get warningForeground => const Color(0xFF232323); - @override - Color get warningBackground => const Color(0xFFF6C7C3); - @override - Color get loadingOverlayTextColor => const Color(0xFFF7F7F7); - @override - Color get myStackContactIconBG => const Color(0xFFD8E7EB); - @override - Color get textConfirmTotalAmount => const Color(0xFF232323); - @override - Color get textSelectedWordTableItem => const Color(0xFF232323); -} diff --git a/lib/utilities/util.dart b/lib/utilities/util.dart index 2940b6d40..6a31fae04 100644 --- a/lib/utilities/util.dart +++ b/lib/utilities/util.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/material.dart'; abstract class Util { @@ -22,6 +23,14 @@ abstract class Util { return Platform.isLinux || Platform.isMacOS || Platform.isWindows; } + static Future get isIPad async { + final deviceInfo = (await DeviceInfoPlugin().deviceInfo); + if (deviceInfo is IosDeviceInfo) { + return (deviceInfo).name?.toLowerCase().contains("ipad") == true; + } + return false; + } + static MaterialColor createMaterialColor(Color color) { List strengths = [.05]; final swatch = {}; diff --git a/lib/widgets/address_book_card.dart b/lib/widgets/address_book_card.dart index dfa655f86..ee4f9c2db 100644 --- a/lib/widgets/address_book_card.dart +++ b/lib/widgets/address_book_card.dart @@ -1,14 +1,17 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/models/contact.dart'; +import 'package:stackwallet/models/isar/models/contact_entry.dart'; import 'package:stackwallet/pages/address_book_views/subviews/contact_popup.dart'; import 'package:stackwallet/providers/global/address_book_service_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/expandable.dart'; @@ -46,7 +49,7 @@ class _AddressBookCardState extends ConsumerState { @override Widget build(BuildContext context) { // provider hack to prevent trying to update widget with deleted contact - Contact? _contact; + ContactEntry? _contact; try { _contact = ref.watch(addressBookServiceProvider .select((value) => value.getContactById(contactId))); @@ -79,7 +82,7 @@ class _AddressBookCardState extends ConsumerState { width: 32, height: 32, decoration: BoxDecoration( - color: contact.id == "default" + color: contact.customId == "default" ? Theme.of(context) .extension()! .myStackContactIconBG @@ -88,10 +91,16 @@ class _AddressBookCardState extends ConsumerState { .textFieldDefaultBG, borderRadius: BorderRadius.circular(32), ), - child: contact.id == "default" + child: contact.customId == "default" ? Center( - child: SvgPicture.asset( - Assets.svg.stackIcon(context), + child: SvgPicture.file( + File( + ref.watch( + themeProvider.select( + (value) => value.assets.stackIcon, + ), + ), + ), width: 20, ), ) @@ -170,7 +179,7 @@ class _AddressBookCardState extends ConsumerState { useSafeArea: true, barrierDismissible: true, builder: (_) => ContactPopUp( - contactId: contact.id, + contactId: contact.customId, ), ); }, diff --git a/lib/widgets/animated_widgets/rotate_icon.dart b/lib/widgets/animated_widgets/rotate_icon.dart new file mode 100644 index 000000000..d93f6b36a --- /dev/null +++ b/lib/widgets/animated_widgets/rotate_icon.dart @@ -0,0 +1,76 @@ +import 'package:flutter/widgets.dart'; + +class RotateIconController { + VoidCallback? forward; + VoidCallback? reverse; + VoidCallback? reset; +} + +class RotateIcon extends StatefulWidget { + const RotateIcon({ + Key? key, + required this.icon, + required this.curve, + this.controller, + this.animationDurationMultiplier = 1.0, + this.rotationPercent = 0.5, + }) : super(key: key); + + final Widget icon; + final Curve curve; + final RotateIconController? controller; + final double animationDurationMultiplier; + final double rotationPercent; + + @override + State createState() => _RotateIconState(); +} + +class _RotateIconState extends State + with SingleTickerProviderStateMixin { + late final AnimationController animationController; + late final Animation animation; + late final Duration duration; + + @override + void initState() { + duration = Duration( + milliseconds: (500 * widget.animationDurationMultiplier).toInt(), + ); + animationController = AnimationController( + vsync: this, + duration: duration, + ); + animation = Tween( + begin: 0.0, + end: widget.rotationPercent, + ).animate( + CurvedAnimation( + curve: widget.curve, + parent: animationController, + ), + ); + + widget.controller?.forward = animationController.forward; + widget.controller?.reverse = animationController.reverse; + widget.controller?.reset = animationController.reset; + + super.initState(); + } + + @override + void dispose() { + animationController.dispose(); + widget.controller?.forward = null; + widget.controller?.reverse = null; + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return RotationTransition( + turns: animation, + child: widget.icon, + ); + } +} diff --git a/lib/widgets/animated_widgets/rotating_arrows.dart b/lib/widgets/animated_widgets/rotating_arrows.dart new file mode 100644 index 000000000..1d0e2562e --- /dev/null +++ b/lib/widgets/animated_widgets/rotating_arrows.dart @@ -0,0 +1,87 @@ +import 'package:flutter/material.dart'; +import 'package:lottie/lottie.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; + +class RotatingArrowsController { + VoidCallback? forward; + VoidCallback? repeat; + VoidCallback? stop; +} + +class RotatingArrows extends StatefulWidget { + const RotatingArrows({ + Key? key, + required this.height, + required this.width, + this.controller, + this.color, + this.spinByDefault = true, + }) : super(key: key); + + final double height; + final double width; + final RotatingArrowsController? controller; + final Color? color; + final bool spinByDefault; + + @override + State createState() => _RotatingArrowsState(); +} + +class _RotatingArrowsState extends State + with SingleTickerProviderStateMixin { + late final AnimationController animationController; + + @override + void initState() { + animationController = AnimationController(vsync: this); + + widget.controller?.forward = animationController.forward; + widget.controller?.repeat = animationController.repeat; + widget.controller?.stop = animationController.stop; + + super.initState(); + } + + @override + void dispose() { + animationController.dispose(); + widget.controller?.forward = null; + widget.controller?.repeat = null; + widget.controller?.stop = null; + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Lottie.asset( + Assets.lottie.arrowRotate, + controller: animationController, + height: widget.height, + width: widget.width, + delegates: LottieDelegates( + values: [ + ValueDelegate.color( + const ["**"], + value: widget.color ?? + Theme.of(context).extension()!.accentColorDark, + ), + ValueDelegate.strokeColor( + const ["**"], + value: widget.color ?? + Theme.of(context).extension()!.accentColorDark, + ), + ], + ), + onLoaded: (composition) { + animationController.duration = composition.duration; + + // if controller was not set just assume continuous repeat + if (widget.spinByDefault) { + animationController.repeat(); + } + }, + ); + } +} diff --git a/lib/widgets/app_bar_field.dart b/lib/widgets/app_bar_field.dart new file mode 100644 index 000000000..d579dc3db --- /dev/null +++ b/lib/widgets/app_bar_field.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; + +class AppBarSearchField extends StatefulWidget { + const AppBarSearchField({ + Key? key, + required this.controller, + this.focusNode, + }) : super(key: key); + + final TextEditingController? controller; + final FocusNode? focusNode; + + @override + State createState() => _AppBarSearchFieldState(); +} + +class _AppBarSearchFieldState extends State { + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox( + width: 16, + ), + Expanded( + child: TextField( + autofocus: true, + focusNode: widget.focusNode, + controller: widget.controller, + style: STextStyles.field(context), + decoration: InputDecoration( + fillColor: Colors.transparent, + hintText: "Search...", + hintStyle: STextStyles.fieldLabel(context), + border: InputBorder.none, + enabledBorder: InputBorder.none, + focusedBorder: InputBorder.none, + ), + ), + ), + ], + ); + } +} diff --git a/lib/widgets/background.dart b/lib/widgets/background.dart index 67ff44f55..91332c291 100644 --- a/lib/widgets/background.dart +++ b/lib/widgets/background.dart @@ -1,11 +1,13 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/theme/color_theme.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; -class Background extends StatelessWidget { +class Background extends ConsumerWidget { const Background({ Key? key, required this.child, @@ -14,20 +16,29 @@ class Background extends StatelessWidget { final Widget child; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { Color? color; - switch (Theme.of(context).extension()!.themeType) { - case ThemeType.light: - case ThemeType.dark: - color = Theme.of(context).extension()!.background; - break; - case ThemeType.oceanBreeze: + bool shouldPad = false; + + switch (Theme.of(context).extension()!.themeId) { + case "ocean_breeze": + shouldPad = true; color = null; break; + case "fruit_sorbet": + color = null; + break; + default: + color = Theme.of(context).extension()!.background; + break; } - final bgAsset = Assets.svg.background(context); + final bgAsset = ref.watch( + themeProvider.select( + (value) => value.assets.background, + ), + ); return Container( decoration: BoxDecoration( @@ -41,12 +52,16 @@ class Background extends StatelessWidget { children: [ Positioned.fill( child: Padding( - padding: EdgeInsets.only( - top: MediaQuery.of(context).size.height * (1 / 8), - bottom: MediaQuery.of(context).size.height * (1 / 12), - ), - child: SvgPicture.asset( - bgAsset!, + padding: shouldPad + ? EdgeInsets.only( + top: MediaQuery.of(context).size.height * (1 / 8), + bottom: MediaQuery.of(context).size.height * (1 / 12), + ) + : const EdgeInsets.all(0), + child: SvgPicture.file( + File( + bgAsset!, + ), fit: BoxFit.fill, ), ), diff --git a/lib/widgets/choose_coin_view.dart b/lib/widgets/choose_coin_view.dart new file mode 100644 index 000000000..ee67d8cee --- /dev/null +++ b/lib/widgets/choose_coin_view.dart @@ -0,0 +1,142 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/background.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class ChooseCoinView extends ConsumerStatefulWidget { + const ChooseCoinView({ + Key? key, + required this.title, + required this.coinAdditional, + required this.nextRouteName, + }) : super(key: key); + + static const String routeName = "/chooseCoin"; + + final String title; + final String coinAdditional; + final String nextRouteName; + + @override + ConsumerState createState() => _ChooseCoinViewState(); +} + +class _ChooseCoinViewState extends ConsumerState { + List _coins = [...Coin.values]; + + @override + void initState() { + _coins = _coins.toList(); + _coins.remove(Coin.firoTestNet); + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + bool showTestNet = ref.watch( + prefsChangeNotifierProvider.select((value) => value.showTestNetCoins), + ); + + List coins = showTestNet + ? _coins + : _coins.sublist(0, _coins.length - kTestNetCoinCount); + + return Background( + child: Scaffold( + backgroundColor: Theme.of(context).extension()!.background, + appBar: AppBar( + leading: AppBarBackButton( + onPressed: () { + Navigator.of(context).pop(); + }, + ), + title: Text( + widget.title, + style: STextStyles.navBarTitle(context), + ), + ), + body: Padding( + padding: const EdgeInsets.only( + top: 12, + left: 12, + right: 12, + ), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + ...coins.map( + (coin) { + return Padding( + padding: const EdgeInsets.all(4), + child: RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: RawMaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + onPressed: () { + Navigator.of(context).pushNamed( + widget.nextRouteName, + arguments: coin, + ); + }, + child: Padding( + padding: const EdgeInsets.all(12), + child: Row( + children: [ + SvgPicture.file( + File( + ref.watch(coinIconProvider(coin)), + ), + width: 24, + height: 24, + ), + const SizedBox( + width: 12, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "${coin.prettyName} ${widget.coinAdditional}", + style: STextStyles.titleBold12(context), + ), + ], + ) + ], + ), + ), + ), + ), + ); + }, + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/widgets/crypto_notifications.dart b/lib/widgets/crypto_notifications.dart new file mode 100644 index 000000000..8dab9ff31 --- /dev/null +++ b/lib/widgets/crypto_notifications.dart @@ -0,0 +1,98 @@ +import 'dart:async'; + +import 'package:event_bus/event_bus.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/services/notifications_api.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; + +abstract class CryptoNotificationsEventBus { + static final instance = EventBus(); +} + +class CryptoNotificationEvent { + final String title; + final String walletId; + final String walletName; + final DateTime date; + final bool shouldWatchForUpdates; + final Coin coin; + final String? txid; + final int? confirmations; + final int? requiredConfirmations; + final String? changeNowId; + final String? payload; + + CryptoNotificationEvent({ + required this.title, + required this.walletId, + required this.walletName, + required this.date, + required this.shouldWatchForUpdates, + required this.coin, + this.txid, + this.confirmations, + this.requiredConfirmations, + this.changeNowId, + this.payload, + }); +} + +class CryptoNotifications extends ConsumerStatefulWidget { + const CryptoNotifications({ + Key? key, + required this.child, + }) : super(key: key); + + final Widget child; + + @override + ConsumerState createState() => + _CryptoNotificationsState(); +} + +class _CryptoNotificationsState extends ConsumerState { + late final StreamSubscription? _streamSubscription; + + Future _showNotification(CryptoNotificationEvent event) async { + await NotificationApi.showNotification( + title: event.title, + body: event.walletName, + walletId: event.walletId, + iconAssetName: ref.read(coinIconProvider(event.coin)), + date: event.date, + shouldWatchForUpdates: event.shouldWatchForUpdates, + coinName: event.coin.name, + txid: event.txid, + confirmations: event.confirmations, + requiredConfirmations: event.requiredConfirmations, + changeNowId: event.changeNowId, + payload: event.payload, + ); + } + + @override + void initState() { + _streamSubscription = CryptoNotificationsEventBus.instance + .on() + .listen( + (event) async { + unawaited(_showNotification(event)); + }, + ); + + super.initState(); + } + + @override + void dispose() { + _streamSubscription?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return widget.child; + } +} diff --git a/lib/widgets/custom_buttons/app_bar_icon_button.dart b/lib/widgets/custom_buttons/app_bar_icon_button.dart index 9edc1ca5f..0753c490c 100644 --- a/lib/widgets/custom_buttons/app_bar_icon_button.dart +++ b/lib/widgets/custom_buttons/app_bar_icon_button.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; class AppBarIconButton extends StatelessWidget { @@ -13,6 +13,7 @@ class AppBarIconButton extends StatelessWidget { // this.circularBorderRadius = 10.0, this.size = 36.0, this.shadows = const [], + this.semanticsLabel = "Button", }) : super(key: key); final Widget icon; @@ -21,28 +22,33 @@ class AppBarIconButton extends StatelessWidget { // final double circularBorderRadius; final double size; final List shadows; + final String semanticsLabel; @override Widget build(BuildContext context) { return Container( - height: size, - width: size, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(1000), - color: color ?? Theme.of(context).extension()!.background, - boxShadow: shadows, - ), - child: MaterialButton( - splashColor: Theme.of(context).extension()!.highlight, - padding: EdgeInsets.zero, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( + height: size, + width: size, + decoration: BoxDecoration( borderRadius: BorderRadius.circular(1000), + color: + color ?? Theme.of(context).extension()!.background, + boxShadow: shadows, ), - onPressed: onPressed, - child: icon, - ), - ); + child: Semantics( + excludeSemantics: true, + label: semanticsLabel, + child: MaterialButton( + splashColor: Theme.of(context).extension()!.highlight, + padding: EdgeInsets.zero, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(1000), + ), + onPressed: onPressed, + child: icon, + ), + )); } } @@ -53,42 +59,45 @@ class AppBarBackButton extends StatelessWidget { this.isCompact = false, this.size, this.iconSize, + this.semanticsLabel = "Back Button. Takes Back To Previous Page.", }) : super(key: key); final VoidCallback? onPressed; final bool isCompact; final double? size; final double? iconSize; + final String semanticsLabel; @override Widget build(BuildContext context) { final isDesktop = Util.isDesktop; return Padding( - padding: isDesktop - ? const EdgeInsets.symmetric( - vertical: 20, - horizontal: 24, - ) - : const EdgeInsets.all(10), - child: AppBarIconButton( - size: size ?? - (isDesktop - ? isCompact - ? 42 - : 56 - : 32), - color: isDesktop - ? Theme.of(context).extension()!.textFieldDefaultBG - : Theme.of(context).extension()!.background, - shadows: const [], - icon: SvgPicture.asset( - Assets.svg.arrowLeft, - width: iconSize ?? (isCompact ? 18 : 24), - height: iconSize ?? (isCompact ? 18 : 24), - color: Theme.of(context).extension()!.topNavIconPrimary, - ), - onPressed: onPressed ?? Navigator.of(context).pop, - ), - ); + padding: isDesktop + ? const EdgeInsets.symmetric( + vertical: 20, + horizontal: 24, + ) + : const EdgeInsets.all(10), + child: AppBarIconButton( + semanticsLabel: semanticsLabel, + size: size ?? + (isDesktop + ? isCompact + ? 42 + : 56 + : 32), + color: isDesktop + ? Theme.of(context).extension()!.textFieldDefaultBG + : Theme.of(context).extension()!.background, + shadows: const [], + icon: SvgPicture.asset( + Assets.svg.arrowLeft, + width: iconSize ?? (isCompact ? 18 : 24), + height: iconSize ?? (isCompact ? 18 : 24), + color: + Theme.of(context).extension()!.topNavIconPrimary, + ), + onPressed: onPressed ?? Navigator.of(context).pop, + )); } } diff --git a/lib/widgets/custom_buttons/blue_text_button.dart b/lib/widgets/custom_buttons/blue_text_button.dart index 7877ddcde..4034ac935 100644 --- a/lib/widgets/custom_buttons/blue_text_button.dart +++ b/lib/widgets/custom_buttons/blue_text_button.dart @@ -1,17 +1,17 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/providers/ui/color_theme_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; -class BlueTextButton extends ConsumerStatefulWidget { - const BlueTextButton({ +class _CustomTextButton extends StatefulWidget { + const _CustomTextButton({ Key? key, required this.text, + required this.enabledColor, + required this.disabledColor, this.onTap, this.enabled = true, this.textSize, @@ -21,12 +21,14 @@ class BlueTextButton extends ConsumerStatefulWidget { final VoidCallback? onTap; final bool enabled; final double? textSize; + final Color enabledColor; + final Color disabledColor; @override - ConsumerState createState() => _BlueTextButtonState(); + State<_CustomTextButton> createState() => _CustomTextButtonState(); } -class _BlueTextButtonState extends ConsumerState +class _CustomTextButtonState extends State<_CustomTextButton> with SingleTickerProviderStateMixin { AnimationController? controller; Animation? animation; @@ -37,18 +39,14 @@ class _BlueTextButtonState extends ConsumerState @override void initState() { if (widget.enabled) { - color = ref.read(colorThemeProvider.state).state.buttonTextBorderless; + color = widget.enabledColor; controller = AnimationController( vsync: this, duration: const Duration(milliseconds: 100), ); animation = ColorTween( - begin: ref.read(colorThemeProvider.state).state.buttonTextBorderless, - end: ref - .read(colorThemeProvider.state) - .state - .buttonTextBorderless - .withOpacity(0.4), + begin: widget.enabledColor, + end: widget.enabledColor.withOpacity(0.4), ).animate(controller!); animation!.addListener(() { @@ -57,7 +55,7 @@ class _BlueTextButtonState extends ConsumerState }); }); } else { - color = ref.read(colorThemeProvider.state).state.textSubtitle1; + color = widget.disabledColor; } super.initState(); @@ -66,6 +64,7 @@ class _BlueTextButtonState extends ConsumerState @override void dispose() { controller?.dispose(); + controller = null; super.dispose(); } @@ -116,3 +115,35 @@ class _BlueTextButtonState extends ConsumerState ); } } + +class CustomTextButton extends StatelessWidget { + const CustomTextButton({ + Key? key, + required this.text, + this.onTap, + this.enabled = true, + this.textSize, + }) : super(key: key); + + final String text; + final VoidCallback? onTap; + final bool enabled; + final double? textSize; + + @override + Widget build(BuildContext context) { + return _CustomTextButton( + key: UniqueKey(), + text: text, + enabledColor: Theme.of(context) + .extension()! + .customTextButtonEnabledText, + disabledColor: Theme.of(context) + .extension()! + .customTextButtonDisabledText, + enabled: enabled, + textSize: textSize, + onTap: onTap, + ); + } +} diff --git a/lib/widgets/custom_buttons/draggable_switch_button.dart b/lib/widgets/custom_buttons/draggable_switch_button.dart index 33dfe9860..b7582211c 100644 --- a/lib/widgets/custom_buttons/draggable_switch_button.dart +++ b/lib/widgets/custom_buttons/draggable_switch_button.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; class DraggableSwitchButton extends StatefulWidget { const DraggableSwitchButton({ diff --git a/lib/widgets/custom_buttons/dropdown_button.dart b/lib/widgets/custom_buttons/dropdown_button.dart new file mode 100644 index 000000000..0d5a2a738 --- /dev/null +++ b/lib/widgets/custom_buttons/dropdown_button.dart @@ -0,0 +1,374 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/animated_widgets/rotate_icon.dart'; +import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; + +class JDropdownButton extends StatefulWidget { + const JDropdownButton({ + Key? key, + this.label, + required this.items, + this.width, + this.onSelectionChanged, + this.groupValue, + this.redrawOnScreenSizeChanged = false, + this.showIcon = false, + }) : super(key: key); + + final String? label; + final double? width; + final void Function(T?)? onSelectionChanged; + final T? groupValue; + final Set items; + final bool showIcon; + + /// setting this to true should be done carefully + final bool redrawOnScreenSizeChanged; + + @override + State> createState() => _JDropdownButtonState(); +} + +class _JDropdownButtonState extends State> { + final _key = GlobalKey(); + final _rotateIconController = RotateIconController(); + + bool _isOpen = false; + + OverlayEntry? _entry; + + void close() { + if (_isOpen) { + _rotateIconController.reverse?.call(); + _entry?.remove(); + _isOpen = false; + } + } + + void open() { + final size = (_key.currentContext!.findRenderObject() as RenderBox).size; + _entry = OverlayEntry( + builder: (_) { + final position = (_key.currentContext!.findRenderObject() as RenderBox) + .localToGlobal(Offset.zero); + + if (widget.redrawOnScreenSizeChanged) { + // trigger rebuild + MediaQuery.of(context).size; + } + + return GestureDetector( + onTap: close, + child: _JDropdownButtonMenu( + size: size, + position: position, + items: widget.items + .map( + (e) => _JDropdownButtonItem( + value: e, + groupValue: widget.groupValue, + onSelected: (T value) { + widget.onSelectionChanged?.call(value); + close(); + }, + ), + ) + .toList(), + ), + ); + }, + ); + _rotateIconController.forward?.call(); + Overlay.of(context, rootOverlay: true).insert(_entry!); + _isOpen = true; + } + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + if (widget.redrawOnScreenSizeChanged && _isOpen) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _entry?.markNeedsBuild(); + }); + } + return SecondaryButton( + key: _key, + buttonHeight: ButtonHeight.l, + trailingIcon: widget.showIcon + ? RotateIcon( + icon: SvgPicture.asset( + Assets.svg.chevronDown, + width: 10, + color: Theme.of(context) + .extension()! + .buttonTextSecondary, + ), + curve: Curves.easeInOutCubic, + controller: _rotateIconController, + animationDurationMultiplier: 0.1, + ) + : null, + width: widget.width, + label: widget.label ?? widget.groupValue.toString(), + onPressed: _isOpen ? close : open, + ); + } +} + +class JDropdownIconButton extends StatefulWidget { + const JDropdownIconButton({ + Key? key, + required this.items, + required this.displayPrefix, + this.onSelectionChanged, + this.groupValue, + this.redrawOnScreenSizeChanged = false, + this.mobileAppBar = false, + }) : super(key: key); + + final String displayPrefix; + final void Function(T?)? onSelectionChanged; + final T? groupValue; + final Set items; + final bool mobileAppBar; + + /// setting this to true should be done carefully + final bool redrawOnScreenSizeChanged; + + @override + State> createState() => _JDropdownIconButtonState(); +} + +class _JDropdownIconButtonState extends State> { + final _key = GlobalKey(); + + bool _isOpen = false; + + OverlayEntry? _entry; + + void close() { + if (_isOpen) { + _entry?.remove(); + _isOpen = false; + } + } + + void open() { + final size = (_key.currentContext!.findRenderObject() as RenderBox).size; + _entry = OverlayEntry( + builder: (_) { + final position = (_key.currentContext!.findRenderObject() as RenderBox) + .localToGlobal(Offset.zero); + + if (widget.redrawOnScreenSizeChanged) { + // trigger rebuild + MediaQuery.of(context).size; + } + + return GestureDetector( + onTap: close, + child: _JDropdownButtonMenu( + size: Size(200, size.height), + position: Offset(position.dx - 144, position.dy), + items: widget.items + .map( + (e) => _JDropdownButtonItem( + value: e, + groupValue: widget.groupValue, + displayPrefix: widget.displayPrefix, + onSelected: (T value) { + widget.onSelectionChanged?.call(value); + close(); + }, + ), + ) + .toList(), + ), + ); + }, + ); + Overlay.of(context, rootOverlay: true).insert(_entry!); + _isOpen = true; + } + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + if (widget.redrawOnScreenSizeChanged && _isOpen) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _entry?.markNeedsBuild(); + }); + } + + if (widget.mobileAppBar) { + return AppBarIconButton( + key: _key, + size: 36, + icon: SvgPicture.asset( + Assets.svg.list, + width: 20, + height: 20, + color: Theme.of(context).extension()!.topNavIconPrimary, + ), + onPressed: _isOpen ? close : open, + ); + } else { + return SizedBox( + key: _key, + height: 56, + width: 56, + child: TextButton( + style: Theme.of(context) + .extension()! + .getSecondaryEnabledButtonStyle(context) + ?.copyWith( + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + side: BorderSide( + color: Theme.of(context) + .extension()! + .buttonBackBorderSecondary, + width: 1, + ), + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + ), + ), + onPressed: _isOpen ? close : open, + child: SvgPicture.asset( + Assets.svg.list, + width: 20, + height: 20, + ), + ), + ); + } + } +} + +// ============================================================================= + +class _JDropdownButtonMenu extends StatefulWidget { + const _JDropdownButtonMenu( + {Key? key, + required this.items, + required this.size, + required this.position}) + : super(key: key); + + final List<_JDropdownButtonItem> items; + final Size size; + final Offset position; + + @override + State<_JDropdownButtonMenu> createState() => _JDropdownButtonMenuState(); +} + +class _JDropdownButtonMenuState extends State<_JDropdownButtonMenu> { + @override + Widget build(BuildContext context) { + return Material( + color: Colors.transparent, + child: Stack( + children: [ + Container( + color: Colors.black.withOpacity(0.2), + // child: widget.content, + ), + Positioned( + top: widget.size.height + widget.position.dy + 10, + left: widget.position.dx, + width: widget.size.width, + child: RoundedWhiteContainer( + padding: EdgeInsets.zero, + radiusMultiplier: 2.5, + boxShadow: [ + Theme.of(context).extension()!.standardBoxShadow, + ], + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox( + height: 20, + ), + ...widget.items, + const SizedBox( + height: 20, + ), + ], + ), + ), + ), + ], + ), + ); + } +} + +// ============================================================================= + +class _JDropdownButtonItem extends StatelessWidget { + const _JDropdownButtonItem({ + Key? key, + required this.value, + required this.groupValue, + required this.onSelected, + this.height = 53, + this.displayPrefix, + }) : super(key: key); + + final T value; + final T? groupValue; + final double height; + final void Function(T) onSelected; + final String? displayPrefix; + + @override + Widget build(BuildContext context) { + return RawMaterialButton( + fillColor: groupValue == value + ? Theme.of(context).extension()!.textFieldDefaultBG + : Colors.transparent, + elevation: 0, + focusElevation: 0, + hoverElevation: 0, + highlightElevation: 0, + disabledElevation: 0, + padding: EdgeInsets.zero, + onPressed: () => onSelected(value), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + displayPrefix == null + ? value.toString() + : "$displayPrefix ${value.toString().toLowerCase()}", + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context).extension()!.textDark, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/custom_buttons/favorite_toggle.dart b/lib/widgets/custom_buttons/favorite_toggle.dart index 83834a3ee..b74827cd9 100644 --- a/lib/widgets/custom_buttons/favorite_toggle.dart +++ b/lib/widgets/custom_buttons/favorite_toggle.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/providers/ui/color_theme_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; class FavoriteToggle extends ConsumerStatefulWidget { const FavoriteToggle({ @@ -37,10 +37,9 @@ class _FavoriteToggleState extends ConsumerState { @override void initState() { - on = widget.on ?? - ref.read(colorThemeProvider.state).state.favoriteStarActive; - off = widget.off ?? - ref.read(colorThemeProvider.state).state.favoriteStarInactive; + on = widget.on ?? ref.read(themeProvider.state).state.favoriteStarActive; + off = + widget.off ?? ref.read(themeProvider.state).state.favoriteStarInactive; _isActive = widget.initialState; _color = _isActive ? on : off; _onChanged = widget.onChanged; diff --git a/lib/widgets/custom_buttons/paynym_follow_toggle_button.dart b/lib/widgets/custom_buttons/paynym_follow_toggle_button.dart new file mode 100644 index 000000000..3c999a84c --- /dev/null +++ b/lib/widgets/custom_buttons/paynym_follow_toggle_button.dart @@ -0,0 +1,309 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/paynym/paynym_account_lite.dart'; +import 'package:stackwallet/models/paynym/paynym_response.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/providers/global/paynym_api_provider.dart'; +import 'package:stackwallet/providers/global/wallets_provider.dart'; +import 'package:stackwallet/providers/wallet/my_paynym_account_state_provider.dart'; +import 'package:stackwallet/services/mixins/paynym_wallet_interface.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/loading_indicator.dart'; + +enum PaynymFollowToggleButtonStyle { + primary, + detailsPopup, + detailsDesktop, +} + +class PaynymFollowToggleButton extends ConsumerStatefulWidget { + const PaynymFollowToggleButton({ + Key? key, + required this.walletId, + required this.paymentCodeStringToFollow, + this.style = PaynymFollowToggleButtonStyle.primary, + }) : super(key: key); + + final String walletId; + final String paymentCodeStringToFollow; + final PaynymFollowToggleButtonStyle style; + + @override + ConsumerState createState() => + _PaynymFollowToggleButtonState(); +} + +class _PaynymFollowToggleButtonState + extends ConsumerState { + final isDesktop = Util.isDesktop; + + Future follow() async { + bool loadingPopped = false; + unawaited( + showDialog( + context: context, + builder: (context) => const LoadingIndicator( + width: 200, + ), + ).then( + (_) => loadingPopped = true, + ), + ); + + final manager = + ref.read(walletsChangeNotifierProvider).getManager(widget.walletId); + + // get wallet to access paynym calls + final wallet = manager.wallet as PaynymWalletInterface; + + final followedAccount = await ref + .read(paynymAPIProvider) + .nym(widget.paymentCodeStringToFollow, true); + + final myPCode = await wallet.getPaymentCode(isSegwit: false); + + PaynymResponse token = + await ref.read(paynymAPIProvider).token(myPCode.toString()); + + // sign token with notification private key + String signature = await wallet.signStringWithNotificationKey(token.value!); + + var result = await ref.read(paynymAPIProvider).follow(token.value!, + signature, followedAccount.value!.nonSegwitPaymentCode.code); + + int i = 0; + for (; + i < 10 && + result.statusCode == 401; //"401 Unauthorized - Bad signature"; + i++) { + token = await ref.read(paynymAPIProvider).token(myPCode.toString()); + + // sign token with notification private key + signature = await wallet.signStringWithNotificationKey(token.value!); + + result = await ref.read(paynymAPIProvider).follow(token.value!, signature, + followedAccount.value!.nonSegwitPaymentCode.code); + await Future.delayed(const Duration(milliseconds: 200)); + + print("RRR result: $result"); + } + + print("Follow result: $result on try $i"); + + if (result.value!.following == followedAccount.value!.nymID) { + if (!loadingPopped && mounted) { + Navigator.of(context, rootNavigator: isDesktop).pop(); + } + + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "You are following ${followedAccount.value!.nymName}", + context: context, + ), + ); + + final myAccount = ref.read(myPaynymAccountStateProvider.state).state!; + + myAccount.following.add( + PaynymAccountLite( + followedAccount.value!.nymID, + followedAccount.value!.nymName, + followedAccount.value!.nonSegwitPaymentCode.code, + followedAccount.value!.segwit, + ), + ); + + ref.read(myPaynymAccountStateProvider.state).state = myAccount.copyWith(); + + setState(() { + isFollowing = true; + }); + + return true; + } else { + if (!loadingPopped && mounted) { + Navigator.of(context, rootNavigator: isDesktop).pop(); + } + + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Failed to follow ${followedAccount.value!.nymName}", + context: context, + ), + ); + + return false; + } + } + + Future unfollow() async { + bool loadingPopped = false; + unawaited( + showDialog( + context: context, + builder: (context) => const LoadingIndicator( + width: 200, + ), + ).then( + (_) => loadingPopped = true, + ), + ); + + final manager = + ref.read(walletsChangeNotifierProvider).getManager(widget.walletId); + + final wallet = manager.wallet as PaynymWalletInterface; + + final followedAccount = await ref + .read(paynymAPIProvider) + .nym(widget.paymentCodeStringToFollow, true); + + final myPCode = await wallet.getPaymentCode(isSegwit: false); + + PaynymResponse token = + await ref.read(paynymAPIProvider).token(myPCode.toString()); + + // sign token with notification private key + String signature = await wallet.signStringWithNotificationKey(token.value!); + + var result = await ref.read(paynymAPIProvider).unfollow(token.value!, + signature, followedAccount.value!.nonSegwitPaymentCode.code); + + int i = 0; + for (; + i < 10 && + result.statusCode == 401; //"401 Unauthorized - Bad signature"; + i++) { + token = await ref.read(paynymAPIProvider).token(myPCode.toString()); + + // sign token with notification private key + signature = await wallet.signStringWithNotificationKey(token.value!); + + result = await ref.read(paynymAPIProvider).unfollow(token.value!, + signature, followedAccount.value!.nonSegwitPaymentCode.code); + await Future.delayed(const Duration(milliseconds: 200)); + print("unfollow RRR result: $result"); + } + + print("Unfollow result: $result on try $i"); + + if (result.value!.unfollowing == followedAccount.value!.nymID) { + if (!loadingPopped && mounted) { + Navigator.of(context, rootNavigator: isDesktop).pop(); + } + + unawaited( + showFloatingFlushBar( + type: FlushBarType.success, + message: "You have unfollowed ${followedAccount.value!.nymName}", + context: context, + ), + ); + + final myAccount = ref.read(myPaynymAccountStateProvider.state).state!; + + myAccount.following + .removeWhere((e) => e.nymId == followedAccount.value!.nymID); + + ref.read(myPaynymAccountStateProvider.state).state = myAccount.copyWith(); + + setState(() { + isFollowing = false; + }); + + return true; + } else { + if (!loadingPopped && mounted) { + Navigator.of(context, rootNavigator: isDesktop).pop(); + } + + unawaited( + showFloatingFlushBar( + type: FlushBarType.warning, + message: "Failed to unfollow ${followedAccount.value!.nymName}", + context: context, + ), + ); + + return false; + } + } + + bool _lock = false; + late bool isFollowing; + + Future _onPressed() async { + if (!_lock) { + _lock = true; + if (isFollowing) { + await unfollow(); + } else { + await follow(); + } + _lock = false; + } + } + + @override + void initState() { + isFollowing = ref + .read(myPaynymAccountStateProvider.state) + .state! + .following + .where((e) => e.code == widget.paymentCodeStringToFollow) + .isNotEmpty; + super.initState(); + } + + @override + Widget build(BuildContext context) { + switch (widget.style) { + case PaynymFollowToggleButtonStyle.primary: + return PrimaryButton( + width: isDesktop ? 120 : 100, + buttonHeight: isDesktop ? ButtonHeight.s : ButtonHeight.xl, + label: isFollowing ? "Unfollow" : "Follow", + onPressed: _onPressed, + ); + + case PaynymFollowToggleButtonStyle.detailsPopup: + return SecondaryButton( + label: isFollowing ? "Unfollow" : "Follow", + buttonHeight: ButtonHeight.xl, + iconSpacing: 8, + icon: SvgPicture.asset( + isFollowing ? Assets.svg.userMinus : Assets.svg.userPlus, + width: 16, + height: 16, + color: + Theme.of(context).extension()!.buttonTextSecondary, + ), + onPressed: _onPressed, + ); + + case PaynymFollowToggleButtonStyle.detailsDesktop: + return SecondaryButton( + label: isFollowing ? "Unfollow" : "Follow", + buttonHeight: ButtonHeight.s, + icon: SvgPicture.asset( + isFollowing ? Assets.svg.userMinus : Assets.svg.userPlus, + width: 16, + height: 16, + color: + Theme.of(context).extension()!.buttonTextSecondary, + ), + iconSpacing: 6, + onPressed: _onPressed, + ); + } + } +} diff --git a/lib/widgets/custom_buttons/simple_copy_button.dart b/lib/widgets/custom_buttons/simple_copy_button.dart new file mode 100644 index 000000000..eab69a25d --- /dev/null +++ b/lib/widgets/custom_buttons/simple_copy_button.dart @@ -0,0 +1,53 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/notifications/show_flush_bar.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; + +class SimpleCopyButton extends StatelessWidget { + const SimpleCopyButton({ + Key? key, + required this.data, + }) : super(key: key); + + final String data; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () async { + await Clipboard.setData(ClipboardData(text: data)); + if (context.mounted) { + unawaited( + showFloatingFlushBar( + type: FlushBarType.info, + message: "Copied to clipboard", + context: context, + ), + ); + } + }, + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.copy, + width: 10, + height: 10, + color: Theme.of(context).extension()!.infoItemIcons, + ), + const SizedBox( + width: 4, + ), + Text( + "Copy", + style: STextStyles.link2(context), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/custom_buttons/simple_edit_button.dart b/lib/widgets/custom_buttons/simple_edit_button.dart new file mode 100644 index 000000000..8260fe659 --- /dev/null +++ b/lib/widgets/custom_buttons/simple_edit_button.dart @@ -0,0 +1,112 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/pages/generic/single_field_edit_view.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:tuple/tuple.dart'; + +import '../desktop/desktop_dialog.dart'; +import '../icon_widgets/pencil_icon.dart'; + +class SimpleEditButton extends StatelessWidget { + const SimpleEditButton({ + Key? key, + this.editValue, + this.editLabel, + this.onValueChanged, + this.onPressedOverride, + }) : assert( + (editLabel != null && editValue != null && onValueChanged != null) || + (editLabel == null && + editValue == null && + onValueChanged == null && + onPressedOverride != null), + ), + super(key: key); + + final String? editValue; + final String? editLabel; + final void Function(String)? onValueChanged; + final VoidCallback? onPressedOverride; + + @override + Widget build(BuildContext context) { + if (Util.isDesktop) { + return SizedBox( + height: 26, + width: 26, + child: RawMaterialButton( + fillColor: + Theme.of(context).extension()!.buttonBackSecondary, + elevation: 0, + hoverElevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + onPressed: onPressedOverride ?? + () async { + final result = await showDialog( + context: context, + builder: (context) { + return DesktopDialog( + maxWidth: 580, + maxHeight: 300, + child: SingleFieldEditView( + initialValue: editValue!, + label: editLabel!, + ), + ); + }, + ); + if (result is String && result != editValue!) { + onValueChanged?.call(result); + } + }, + child: Padding( + padding: const EdgeInsets.all(5), + child: PencilIcon( + width: 16, + height: 16, + color: Theme.of(context).extension()!.textDark, + ), + ), + ), + ); + } else { + return GestureDetector( + onTap: onPressedOverride ?? + () async { + final result = await Navigator.of(context).pushNamed( + SingleFieldEditView.routeName, + arguments: Tuple2( + editValue!, + editLabel!, + ), + ); + if (result is String && result != editValue!) { + onValueChanged?.call(result); + } + }, + child: Row( + children: [ + SvgPicture.asset( + Assets.svg.pencil, + width: 10, + height: 10, + color: Theme.of(context).extension()!.infoItemIcons, + ), + const SizedBox( + width: 4, + ), + Text( + "Edit", + style: STextStyles.link2(context), + ), + ], + ), + ); + } + } +} diff --git a/lib/widgets/custom_loading_overlay.dart b/lib/widgets/custom_loading_overlay.dart index c92d7705d..349448fbc 100644 --- a/lib/widgets/custom_loading_overlay.dart +++ b/lib/widgets/custom_loading_overlay.dart @@ -3,19 +3,27 @@ import 'dart:async'; import 'package:event_bus/event_bus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; class CustomLoadingOverlay extends ConsumerStatefulWidget { const CustomLoadingOverlay({ Key? key, required this.message, + this.subMessage, required this.eventBus, + this.textColor, + this.actionButton, }) : super(key: key); final String message; + final String? subMessage; final EventBus? eventBus; + final Color? textColor; + final Widget? actionButton; @override ConsumerState createState() => @@ -26,6 +34,7 @@ class _CustomLoadingOverlayState extends ConsumerState { double _percent = 0; late final StreamSubscription? subscription; + final bool isDesktop = Util.isDesktop; @override void initState() { @@ -45,49 +54,97 @@ class _CustomLoadingOverlayState extends ConsumerState { @override Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Material( - color: Colors.transparent, - child: Center( - child: Column( - children: [ - Text( - widget.message, - style: STextStyles.pageTitleH2(context).copyWith( - color: Theme.of(context) - .extension()! - .loadingOverlayTextColor, - ), - ), - if (widget.eventBus != null) - const SizedBox( - height: 10, - ), - if (widget.eventBus != null) - Text( - "${(_percent * 100).toStringAsFixed(2)}%", - style: STextStyles.pageTitleH2(context).copyWith( - color: Theme.of(context) - .extension()! - .loadingOverlayTextColor, + return Material( + color: Colors.transparent, + child: ConditionalParent( + condition: widget.actionButton != null, + builder: (child) => Stack( + children: [ + child, + if (isDesktop) + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: SizedBox( + width: 100, + child: widget.actionButton!, ), ), - ], + ], + ), + if (!isDesktop) + Positioned( + bottom: 1, + left: 0, + right: 1, + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Expanded(child: widget.actionButton!), + ], + ), + ), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + widget.message, + textAlign: TextAlign.center, + style: STextStyles.pageTitleH2(context).copyWith( + color: widget.textColor ?? + Theme.of(context) + .extension()! + .loadingOverlayTextColor, + ), ), - ), + if (widget.eventBus != null) + const SizedBox( + height: 10, + ), + if (widget.eventBus != null) + Text( + "${(_percent * 100).toStringAsFixed(2)}%", + style: STextStyles.pageTitleH2(context).copyWith( + color: widget.textColor ?? + Theme.of(context) + .extension()! + .loadingOverlayTextColor, + ), + ), + if (widget.subMessage != null) + const SizedBox( + height: 10, + ), + if (widget.subMessage != null) + Text( + widget.subMessage!, + textAlign: TextAlign.center, + style: STextStyles.pageTitleH2(context).copyWith( + fontSize: 14, + color: widget.textColor ?? + Theme.of(context) + .extension()! + .loadingOverlayTextColor, + ), + ), + const SizedBox( + height: 64, + ), + const Center( + child: LoadingIndicator( + width: 100, + ), + ), + ], ), - const SizedBox( - height: 64, - ), - const Center( - child: LoadingIndicator( - width: 100, - ), - ), - ], + ), ); } } diff --git a/lib/widgets/custom_pin_put/custom_pin_put.dart b/lib/widgets/custom_pin_put/custom_pin_put.dart index 797461e1d..4a4e4a4e4 100644 --- a/lib/widgets/custom_pin_put/custom_pin_put.dart +++ b/lib/widgets/custom_pin_put/custom_pin_put.dart @@ -1,61 +1,68 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:stackwallet/widgets/custom_pin_put/custom_pin_put_state.dart'; +import 'package:stackwallet/widgets/custom_pin_put/pin_keyboard.dart'; class CustomPinPut extends StatefulWidget { - const CustomPinPut( - {Key? key, - required this.fieldsCount, - this.height, - this.width, - this.onSubmit, - this.onSaved, - this.onChanged, - this.controller, - this.focusNode, - this.separator = const SizedBox(width: 15.0), - this.textStyle, - this.submittedFieldDecoration, - this.selectedFieldDecoration, - this.followingFieldDecoration, - this.disabledDecoration, - this.eachFieldWidth, - this.eachFieldHeight, - this.fieldsAlignment = MainAxisAlignment.spaceBetween, - this.eachFieldAlignment = Alignment.center, - this.eachFieldMargin, - this.eachFieldPadding, - this.eachFieldConstraints = - const BoxConstraints(minHeight: 10.0, minWidth: 10.0), - this.inputDecoration = const InputDecoration( - contentPadding: EdgeInsets.zero, - border: InputBorder.none, - counterText: '', - ), - this.animationCurve = Curves.linear, - this.animationDuration = const Duration(milliseconds: 160), - this.pinAnimationType = PinAnimationType.slide, - this.slideTransitionBeginOffset, - this.enabled = true, - this.useNativeKeyboard = true, - this.autofocus = false, - this.autovalidateMode = AutovalidateMode.disabled, - this.keyboardAppearance, - this.inputFormatters, - this.validator, - this.keyboardType = TextInputType.number, - this.obscureText, - this.textCapitalization = TextCapitalization.none, - this.textInputAction, - this.toolbarOptions, - this.mainAxisSize = MainAxisSize.max, - this.autofillHints}) - : assert(fieldsCount > 0), + const CustomPinPut({ + Key? key, + required this.fieldsCount, + required this.isRandom, + this.height, + this.width, + this.onSubmit, + this.onSaved, + this.onChanged, + this.controller, + this.focusNode, + this.separator = const SizedBox(width: 15.0), + this.textStyle, + this.submittedFieldDecoration, + this.selectedFieldDecoration, + this.followingFieldDecoration, + this.disabledDecoration, + this.eachFieldWidth, + this.eachFieldHeight, + this.fieldsAlignment = MainAxisAlignment.spaceBetween, + this.eachFieldAlignment = Alignment.center, + this.eachFieldMargin, + this.eachFieldPadding, + this.eachFieldConstraints = + const BoxConstraints(minHeight: 10.0, minWidth: 10.0), + this.inputDecoration = const InputDecoration( + contentPadding: EdgeInsets.zero, + border: InputBorder.none, + counterText: '', + ), + this.animationCurve = Curves.linear, + this.animationDuration = const Duration(milliseconds: 160), + this.pinAnimationType = PinAnimationType.slide, + this.slideTransitionBeginOffset, + this.enabled = true, + this.useNativeKeyboard = true, + this.autofocus = false, + this.autovalidateMode = AutovalidateMode.disabled, + this.keyboardAppearance, + this.inputFormatters, + this.validator, + this.keyboardType = TextInputType.number, + this.obscureText, + this.textCapitalization = TextCapitalization.none, + this.textInputAction, + this.toolbarOptions, + this.mainAxisSize = MainAxisSize.max, + this.autofillHints, + this.customKey, + }) : assert(fieldsCount > 0), super(key: key); final double? width; final double? height; + final CustomKey? customKey; + + final bool isRandom; + /// Displayed fields count. PIN code length. final int fieldsCount; diff --git a/lib/widgets/custom_pin_put/custom_pin_put_state.dart b/lib/widgets/custom_pin_put/custom_pin_put_state.dart index 35acea5f9..355656638 100644 --- a/lib/widgets/custom_pin_put/custom_pin_put_state.dart +++ b/lib/widgets/custom_pin_put/custom_pin_put_state.dart @@ -32,9 +32,9 @@ class CustomPinPutState extends State } catch (e) { _textControllerValue = ValueNotifier(_controller.value.text); } - if (pin.length == widget.fieldsCount) { - widget.onSubmit?.call(pin); - } + // if (pin.length == widget.fieldsCount) { + // widget.onSubmit?.call(pin); + // } } } @@ -50,6 +50,9 @@ class CustomPinPutState extends State @override Widget build(BuildContext context) { + // final bool randomize = ref + // .read(prefsChangeNotifierProvider) + // .randomizePIN; return SizedBox( width: widget.width, height: widget.height, @@ -69,6 +72,8 @@ class CustomPinPutState extends State ), Center( child: PinKeyboard( + isRandom: widget.isRandom, + customKey: widget.customKey, onNumberKeyPressed: (number) { if (_controller.text.length < widget.fieldsCount) { _controller.text += number; diff --git a/lib/widgets/custom_pin_put/pin_keyboard.dart b/lib/widgets/custom_pin_put/pin_keyboard.dart index 1dcac4d4c..d5874dcb0 100644 --- a/lib/widgets/custom_pin_put/pin_keyboard.dart +++ b/lib/widgets/custom_pin_put/pin_keyboard.dart @@ -1,8 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; class NumberKey extends StatefulWidget { const NumberKey({ @@ -117,38 +119,42 @@ class _BackspaceKeyState extends State { shadows: const [], ), child: MaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: const StadiumBorder(), - onPressed: () { - onPressed.call(); - setState(() { - _color = Theme.of(context) - .extension()! - .numpadBackDefault - .withOpacity(0.8); - }); + // splashColor: Theme.of(context).extension()!.highlight, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: const StadiumBorder(), + onPressed: () { + onPressed.call(); + setState(() { + _color = Theme.of(context) + .extension()! + .numpadBackDefault + .withOpacity(0.8); + }); - Future.delayed(const Duration(milliseconds: 200), () { - if (mounted) { - setState(() { - _color = Theme.of(context) + Future.delayed(const Duration(milliseconds: 200), () { + if (mounted) { + setState(() { + _color = Theme.of(context) + .extension()! + .numpadBackDefault; + }); + } + }); + }, + child: Semantics( + label: "Backspace Button. Deletes The Last Digit.", + excludeSemantics: true, + child: Center( + child: SvgPicture.asset( + Assets.svg.delete, + width: 20, + height: 20, + color: Theme.of(context) .extension()! - .numpadBackDefault; - }); - } - }); - }, - child: Center( - child: SvgPicture.asset( - Assets.svg.delete, - width: 20, - height: 20, - color: - Theme.of(context).extension()!.numpadTextDefault, - ), - ), - ), + .numpadTextDefault, + ), + ), + )), ); } } @@ -192,15 +198,66 @@ class SubmitKey extends StatelessWidget { } } -class PinKeyboard extends StatelessWidget { +class CustomKey extends StatelessWidget { + const CustomKey({ + Key? key, + required this.onPressed, + this.iconAssetName, + this.semanticsLabel = "Button", + }) : super(key: key); + + final VoidCallback onPressed; + final String? iconAssetName; + final String semanticsLabel; + + @override + Widget build(BuildContext context) { + return Container( + height: 72, + width: 72, + decoration: ShapeDecoration( + shape: const StadiumBorder(), + color: Theme.of(context).extension()!.numpadBackDefault, + shadows: const [], + ), + child: Semantics( + label: semanticsLabel, + excludeSemantics: true, + child: MaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: const StadiumBorder(), + onPressed: () { + onPressed.call(); + }, + child: Center( + child: iconAssetName == null + ? null + : SvgPicture.asset( + iconAssetName!, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .numpadTextDefault, + ), + ), + ), + )); + } +} + +class PinKeyboard extends ConsumerWidget { const PinKeyboard({ Key? key, required this.onNumberKeyPressed, required this.onBackPressed, required this.onSubmitPressed, + required this.isRandom, this.backgroundColor, this.width = 264, this.height = 360, + this.customKey, }) : super(key: key); final ValueSetter onNumberKeyPressed; @@ -209,6 +266,8 @@ class PinKeyboard extends StatelessWidget { final Color? backgroundColor; final double? width; final double? height; + final CustomKey? customKey; + final bool isRandom; void _backHandler() { onBackPressed.call(); @@ -220,10 +279,28 @@ class PinKeyboard extends StatelessWidget { void _numberHandler(String number) { onNumberKeyPressed.call(number); + HapticFeedback.lightImpact(); } @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final list = [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "0", + ]; + + // final isRandom = ref.read(prefsChangeNotifierProvider).randomizePIN; + + if (isRandom) list.shuffle(); + return Container( width: width, height: height, @@ -233,21 +310,21 @@ class PinKeyboard extends StatelessWidget { Row( children: [ NumberKey( - number: "1", + number: list[0], onPressed: _numberHandler, ), const SizedBox( width: 24, ), NumberKey( - number: "2", + number: list[1], onPressed: _numberHandler, ), const SizedBox( width: 24, ), NumberKey( - number: "3", + number: list[2], onPressed: _numberHandler, ), ], @@ -258,21 +335,21 @@ class PinKeyboard extends StatelessWidget { Row( children: [ NumberKey( - number: "4", + number: list[3], onPressed: _numberHandler, ), const SizedBox( width: 24, ), NumberKey( - number: "5", + number: list[4], onPressed: _numberHandler, ), const SizedBox( width: 24, ), NumberKey( - number: "6", + number: list[5], onPressed: _numberHandler, ), ], @@ -283,21 +360,21 @@ class PinKeyboard extends StatelessWidget { Row( children: [ NumberKey( - number: "7", + number: list[6], onPressed: _numberHandler, ), const SizedBox( width: 24, ), NumberKey( - number: "8", + number: list[7], onPressed: _numberHandler, ), const SizedBox( width: 24, ), NumberKey( - number: "9", + number: list[8], onPressed: _numberHandler, ), ], @@ -307,26 +384,178 @@ class PinKeyboard extends StatelessWidget { ), Row( children: [ - const SizedBox( - height: 72, - width: 72, - ), - const SizedBox( - width: 24, - ), - NumberKey( - number: "0", - onPressed: _numberHandler, - ), - const SizedBox( - width: 24, - ), BackspaceKey( onPressed: _backHandler, ), - // SubmitKey( - // onPressed: _submitHandler, - // ), + const SizedBox( + width: 24, + ), + NumberKey( + number: list[9], + onPressed: _numberHandler, + ), + const SizedBox( + width: 24, + ), + SubmitKey( + onPressed: _submitHandler, + ), + ], + ) + ], + ), + ); + } +} + +class RandomKeyboard extends StatelessWidget { + const RandomKeyboard({ + Key? key, + required this.onNumberKeyPressed, + required this.onBackPressed, + required this.onSubmitPressed, + this.backgroundColor, + this.width = 264, + this.height = 360, + this.customKey, + }) : super(key: key); + + final ValueSetter onNumberKeyPressed; + final VoidCallback onBackPressed; + final VoidCallback onSubmitPressed; + final Color? backgroundColor; + final double? width; + final double? height; + final CustomKey? customKey; + + void _backHandler() { + onBackPressed.call(); + } + + void _submitHandler() { + onSubmitPressed.call(); + } + + void _numberHandler(String number) { + onNumberKeyPressed.call(number); + HapticFeedback.lightImpact(); + debugPrint("NUMBER: $number"); + } + + @override + Widget build(BuildContext context) { + final list = [ + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "0", + ]; + list.shuffle(); + return Container( + width: width, + height: height, + color: backgroundColor ?? Colors.transparent, + child: Column( + children: [ + Row( + children: [ + NumberKey( + number: list[0], + onPressed: _numberHandler, + ), + const SizedBox( + width: 24, + ), + NumberKey( + number: list[1], + onPressed: _numberHandler, + ), + const SizedBox( + width: 24, + ), + NumberKey( + number: list[2], + onPressed: _numberHandler, + ), + ], + ), + const SizedBox( + height: 24, + ), + Row( + children: [ + NumberKey( + number: list[3], + onPressed: _numberHandler, + ), + const SizedBox( + width: 24, + ), + NumberKey( + number: list[4], + onPressed: _numberHandler, + ), + const SizedBox( + width: 24, + ), + NumberKey( + number: list[5], + onPressed: _numberHandler, + ), + ], + ), + const SizedBox( + height: 24, + ), + Row( + children: [ + NumberKey( + number: list[6], + onPressed: _numberHandler, + ), + const SizedBox( + width: 24, + ), + NumberKey( + number: list[7], + onPressed: _numberHandler, + ), + const SizedBox( + width: 24, + ), + NumberKey( + number: list[8], + onPressed: _numberHandler, + ), + ], + ), + const SizedBox( + height: 24, + ), + Row( + children: [ + BackspaceKey( + onPressed: _backHandler, + ), + const SizedBox( + width: 24, + ), + NumberKey( + number: list[9], + onPressed: _numberHandler, + ), + const SizedBox( + width: 24, + ), + SubmitKey( + onPressed: _submitHandler, + ), ], ) ], diff --git a/lib/widgets/custom_tab_view.dart b/lib/widgets/custom_tab_view.dart new file mode 100644 index 000000000..78e7a1809 --- /dev/null +++ b/lib/widgets/custom_tab_view.dart @@ -0,0 +1,149 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; + +class CustomTabView extends StatefulWidget { + const CustomTabView({ + Key? key, + required this.titles, + required this.children, + this.initialIndex = 0, + this.childPadding, + }) : assert(titles.length == children.length), + super(key: key); + + final List titles; + final List children; + final int initialIndex; + final EdgeInsets? childPadding; + + @override + State createState() => _CustomTabViewState(); +} + +class _CustomTabViewState extends State { + late int _selectedIndex; + + static const duration = Duration(milliseconds: 250); + + @override + void initState() { + _selectedIndex = widget.initialIndex; + super.initState(); + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + for (int i = 0; i < widget.titles.length; i++) + Expanded( + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () => setState(() => _selectedIndex = i), + child: Container( + color: Colors.transparent, + child: Column( + children: [ + const SizedBox( + height: 16, + ), + AnimatedCrossFade( + firstChild: Text( + widget.titles[i], + style: + STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .accentColorBlue, + ), + ), + secondChild: Text( + widget.titles[i], + style: + STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textSubtitle1, + ), + ), + crossFadeState: _selectedIndex == i + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + duration: const Duration(milliseconds: 250), + ), + const SizedBox( + height: 19, + ), + ], + ), + ), + ), + ), + ), + ], + ), + Stack( + children: [ + Container( + height: 2, + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .backgroundAppBar, + ), + ), + AnimatedSlide( + offset: Offset(_selectedIndex.toDouble(), 0), + duration: duration, + child: Container( + height: 2, + width: constraints.maxWidth / widget.titles.length, + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .accentColorBlue, + ), + ), + ), + ], + ), + AnimatedSwitcher( + duration: duration, + transitionBuilder: (child, animation) { + return FadeTransition( + opacity: animation, + child: child, + ); + }, + layoutBuilder: (currentChild, prevChildren) { + return Stack( + alignment: Alignment.topCenter, + children: [ + ...prevChildren, + if (currentChild != null) currentChild, + ], + ); + }, + child: AnimatedAlign( + key: Key("${widget.titles[_selectedIndex]}_customTabKey"), + alignment: Alignment.topCenter, + duration: duration, + child: Padding( + padding: widget.childPadding ?? EdgeInsets.zero, + child: widget.children[_selectedIndex], + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/desktop/delete_button.dart b/lib/widgets/desktop/delete_button.dart index e64c85f34..af13518b8 100644 --- a/lib/widgets/desktop/delete_button.dart +++ b/lib/widgets/desktop/delete_button.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/desktop/custom_text_button.dart'; @@ -64,10 +64,10 @@ class DeleteButton extends StatelessWidget { style: enabled ? Theme.of(context) .extension()! - .getDeleteEnabledButtonColor(context) + .getDeleteEnabledButtonStyle(context) : Theme.of(context) .extension()! - .getDeleteDisabledButtonColor(context), + .getDeleteDisabledButtonStyle(context), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ diff --git a/lib/widgets/desktop/desktop_app_bar.dart b/lib/widgets/desktop/desktop_app_bar.dart index a95a552e5..848d4c0a3 100644 --- a/lib/widgets/desktop/desktop_app_bar.dart +++ b/lib/widgets/desktop/desktop_app_bar.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; const double kDesktopAppBarHeight = 96.0; const double kDesktopAppBarHeightCompact = 82.0; @@ -8,6 +9,7 @@ class DesktopAppBar extends StatelessWidget { Key? key, this.leading, this.center, + this.overlayCenter, this.trailing, this.background = Colors.transparent, required this.isCompactHeight, @@ -16,6 +18,7 @@ class DesktopAppBar extends StatelessWidget { final Widget? leading; final Widget? center; + final Widget? overlayCenter; final Widget? trailing; final Color background; final bool isCompactHeight; @@ -43,16 +46,31 @@ class DesktopAppBar extends StatelessWidget { items.add(trailing!); } - return Container( - decoration: BoxDecoration( - color: background, + return ConditionalParent( + condition: overlayCenter != null, + builder: (child) => Stack( + children: [ + child, + Positioned.fill( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [overlayCenter!], + ), + ), + ], ), - height: - isCompactHeight ? kDesktopAppBarHeightCompact : kDesktopAppBarHeight, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: items, + child: Container( + decoration: BoxDecoration( + color: background, + ), + height: isCompactHeight + ? kDesktopAppBarHeightCompact + : kDesktopAppBarHeight, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: items, + ), ), ); } diff --git a/lib/widgets/desktop/desktop_dialog.dart b/lib/widgets/desktop/desktop_dialog.dart index 3cb369edb..1aa7eda3f 100644 --- a/lib/widgets/desktop/desktop_dialog.dart +++ b/lib/widgets/desktop/desktop_dialog.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; class DesktopDialog extends StatelessWidget { const DesktopDialog({ diff --git a/lib/widgets/desktop/desktop_dialog_close_button.dart b/lib/widgets/desktop/desktop_dialog_close_button.dart index da992d5f6..4199278df 100644 --- a/lib/widgets/desktop/desktop_dialog_close_button.dart +++ b/lib/widgets/desktop/desktop_dialog_close_button.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; class DesktopDialogCloseButton extends StatelessWidget { diff --git a/lib/widgets/desktop/desktop_scaffold.dart b/lib/widgets/desktop/desktop_scaffold.dart index 51fa9f3a5..3486bc013 100644 --- a/lib/widgets/desktop/desktop_scaffold.dart +++ b/lib/widgets/desktop/desktop_scaffold.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/widgets/background.dart'; class DesktopScaffold extends StatelessWidget { diff --git a/lib/widgets/desktop/living_stack_icon.dart b/lib/widgets/desktop/living_stack_icon.dart index 7afc8f8d2..166f30f9c 100644 --- a/lib/widgets/desktop/living_stack_icon.dart +++ b/lib/widgets/desktop/living_stack_icon.dart @@ -1,17 +1,23 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:stackwallet/utilities/assets.dart'; +import 'dart:io'; -class LivingStackIcon extends StatefulWidget { - const LivingStackIcon({Key? key, this.onPressed,}) : super(key: key); +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; + +class LivingStackIcon extends ConsumerStatefulWidget { + const LivingStackIcon({ + Key? key, + this.onPressed, + }) : super(key: key); final VoidCallback? onPressed; @override - State createState() => _LivingStackIconState(); + ConsumerState createState() => _LivingStackIconState(); } -class _LivingStackIconState extends State { +class _LivingStackIconState extends ConsumerState { bool _hovering = false; late final VoidCallback? onPressed; @@ -43,8 +49,14 @@ class _LivingStackIconState extends State { child: AnimatedScale( duration: const Duration(milliseconds: 200), scale: _hovering ? 1.2 : 1, - child: SvgPicture.asset( - Assets.svg.stackIcon(context), + child: SvgPicture.file( + File( + ref.watch( + themeProvider.select( + (value) => value.assets.stackIcon, + ), + ), + ), ), ), ), diff --git a/lib/widgets/desktop/outline_blue_button.dart b/lib/widgets/desktop/outline_blue_button.dart new file mode 100644 index 000000000..406337f84 --- /dev/null +++ b/lib/widgets/desktop/outline_blue_button.dart @@ -0,0 +1,188 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/desktop/custom_text_button.dart'; + +export 'package:stackwallet/widgets/desktop/custom_text_button.dart'; + +class OutlineBlueButton extends StatelessWidget { + const OutlineBlueButton({ + Key? key, + this.width, + this.height, + this.label, + this.icon, + this.onPressed, + this.enabled = true, + this.buttonHeight, + this.iconSpacing = 10, + }) : super(key: key); + + final double? width; + final double? height; + final String? label; + final VoidCallback? onPressed; + final bool enabled; + final Widget? icon; + final ButtonHeight? buttonHeight; + final double? iconSpacing; + + TextStyle getStyle(bool isDesktop, BuildContext context) { + if (isDesktop) { + if (buttonHeight == null) { + return enabled + ? STextStyles.desktopButtonEnabled(context).copyWith( + color: Theme.of(context) + .extension()! + .customTextButtonEnabledText, + ) + : STextStyles.desktopButtonDisabled(context).copyWith( + color: Theme.of(context) + .extension()! + .customTextButtonDisabledText, + ); + } + + switch (buttonHeight!) { + case ButtonHeight.xxs: + case ButtonHeight.xs: + case ButtonHeight.s: + return STextStyles.desktopTextExtraExtraSmall(context).copyWith( + color: enabled + ? Theme.of(context) + .extension()! + .customTextButtonEnabledText + : Theme.of(context) + .extension()! + .customTextButtonDisabledText, + ); + + case ButtonHeight.m: + case ButtonHeight.l: + return STextStyles.desktopTextExtraSmall(context).copyWith( + color: enabled + ? Theme.of(context) + .extension()! + .customTextButtonEnabledText + : Theme.of(context) + .extension()! + .customTextButtonDisabledText, + ); + + case ButtonHeight.xl: + case ButtonHeight.xxl: + return enabled + ? STextStyles.desktopButtonEnabled(context) + : STextStyles.desktopButtonDisabled(context); + } + } else { + if (buttonHeight == ButtonHeight.l) { + return STextStyles.button(context).copyWith( + fontSize: 10, + color: enabled + ? Theme.of(context) + .extension()! + .customTextButtonEnabledText + : Theme.of(context) + .extension()! + .customTextButtonDisabledText, + ); + } + return STextStyles.button(context).copyWith( + color: enabled + ? Theme.of(context) + .extension()! + .customTextButtonEnabledText + : Theme.of(context) + .extension()! + .customTextButtonDisabledText, + ); + } + } + + double? _getHeight() { + if (buttonHeight == null) { + return height; + } + + if (Util.isDesktop) { + switch (buttonHeight!) { + case ButtonHeight.xxs: + return 32; + case ButtonHeight.xs: + return 37; + case ButtonHeight.s: + return 40; + case ButtonHeight.m: + return 48; + case ButtonHeight.l: + return 56; + case ButtonHeight.xl: + return 70; + case ButtonHeight.xxl: + return 96; + } + } else { + switch (buttonHeight!) { + case ButtonHeight.xxs: + case ButtonHeight.xs: + case ButtonHeight.s: + case ButtonHeight.m: + return 28; + case ButtonHeight.l: + return 30; + case ButtonHeight.xl: + return 46; + case ButtonHeight.xxl: + return 56; + } + } + } + + @override + Widget build(BuildContext context) { + final isDesktop = Util.isDesktop; + + return CustomTextButtonBase( + height: _getHeight(), + width: width, + textButton: TextButton( + onPressed: enabled ? onPressed : null, + style: enabled + ? Theme.of(context) + .extension()! + .getOutlineBlueButtonStyle(context) + : Theme.of(context) + .extension()! + .getOutlineBlueButtonDisabledStyle(context), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (icon != null) icon!, + if (icon != null && label != null) + SizedBox( + width: iconSpacing, + ), + if (label != null) + Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + label!, + style: getStyle(isDesktop, context), + ), + if (buttonHeight != null && buttonHeight == ButtonHeight.s) + const SizedBox( + height: 2, + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/desktop/paynym_search_button.dart b/lib/widgets/desktop/paynym_search_button.dart new file mode 100644 index 000000000..8603e0079 --- /dev/null +++ b/lib/widgets/desktop/paynym_search_button.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; + +class PaynymSearchButton extends StatefulWidget { + const PaynymSearchButton({ + Key? key, + required this.onPressed, + }) : super(key: key); + + final VoidCallback onPressed; + + @override + State createState() => _PaynymSearchButtonState(); +} + +class _PaynymSearchButtonState extends State { + @override + Widget build(BuildContext context) { + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: widget.onPressed, + child: RoundedContainer( + width: 56, + height: 56, + color: + Theme.of(context).extension()!.buttonBackSecondary, + child: Center( + child: SvgPicture.asset( + Assets.svg.search, + width: 20, + height: 20, + color: Theme.of(context).extension()!.textDark, + ), + ), + ), + ), + ); + } +} diff --git a/lib/widgets/desktop/primary_button.dart b/lib/widgets/desktop/primary_button.dart index 9441168e7..b1201f354 100644 --- a/lib/widgets/desktop/primary_button.dart +++ b/lib/widgets/desktop/primary_button.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/desktop/custom_text_button.dart'; @@ -16,6 +16,7 @@ class PrimaryButton extends StatelessWidget { this.onPressed, this.enabled = true, this.buttonHeight, + this.iconSpacing = 10, }) : super(key: key); final double? width; @@ -25,6 +26,7 @@ class PrimaryButton extends StatelessWidget { final bool enabled; final Widget? icon; final ButtonHeight? buttonHeight; + final double? iconSpacing; TextStyle getStyle(bool isDesktop, BuildContext context) { if (isDesktop) { @@ -63,6 +65,16 @@ class PrimaryButton extends StatelessWidget { : STextStyles.desktopButtonDisabled(context); } } else { + if (buttonHeight == ButtonHeight.l) { + return STextStyles.button(context).copyWith( + fontSize: 10, + color: enabled + ? Theme.of(context).extension()!.buttonTextPrimary + : Theme.of(context) + .extension()! + .buttonTextPrimaryDisabled, + ); + } return STextStyles.button(context).copyWith( color: enabled ? Theme.of(context).extension()!.buttonTextPrimary @@ -124,22 +136,33 @@ class PrimaryButton extends StatelessWidget { style: enabled ? Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context) + .getPrimaryEnabledButtonStyle(context) : Theme.of(context) .extension()! - .getPrimaryDisabledButtonColor(context), + .getPrimaryDisabledButtonStyle(context), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ if (icon != null) icon!, if (icon != null && label != null) - const SizedBox( - width: 10, + SizedBox( + width: iconSpacing, ), if (label != null) - Text( - label!, - style: getStyle(isDesktop, context), + Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + label!, + style: getStyle(isDesktop, context), + ), + if (buttonHeight != null && buttonHeight == ButtonHeight.s) + const SizedBox( + height: 2, + ), + ], ), ], ), diff --git a/lib/widgets/desktop/secondary_button.dart b/lib/widgets/desktop/secondary_button.dart index 62bd900dd..48370e55c 100644 --- a/lib/widgets/desktop/secondary_button.dart +++ b/lib/widgets/desktop/secondary_button.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/desktop/custom_text_button.dart'; @@ -13,9 +13,12 @@ class SecondaryButton extends StatelessWidget { this.height, this.label, this.icon, + this.trailingIcon, this.onPressed, this.enabled = true, this.buttonHeight, + this.iconSpacing = 10, + this.padding = EdgeInsets.zero, }) : super(key: key); final double? width; @@ -24,7 +27,10 @@ class SecondaryButton extends StatelessWidget { final VoidCallback? onPressed; final bool enabled; final Widget? icon; + final Widget? trailingIcon; final ButtonHeight? buttonHeight; + final double iconSpacing; + final EdgeInsets padding; TextStyle getStyle(bool isDesktop, BuildContext context) { if (isDesktop) { @@ -66,6 +72,26 @@ class SecondaryButton extends StatelessWidget { : STextStyles.desktopButtonSecondaryDisabled(context); } } else { + if (buttonHeight == ButtonHeight.l) { + return STextStyles.button(context).copyWith( + fontSize: 10, + color: enabled + ? Theme.of(context).extension()!.buttonTextSecondary + : Theme.of(context) + .extension()! + .buttonTextSecondaryDisabled, + ); + } + if (buttonHeight == ButtonHeight.xl) { + return STextStyles.button(context).copyWith( + fontSize: 14, + color: enabled + ? Theme.of(context).extension()!.buttonTextSecondary + : Theme.of(context) + .extension()! + .buttonTextSecondaryDisabled, + ); + } return STextStyles.button(context).copyWith( color: enabled ? Theme.of(context).extension()!.buttonTextSecondary @@ -127,24 +153,44 @@ class SecondaryButton extends StatelessWidget { style: enabled ? Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context) + .getSecondaryEnabledButtonStyle(context) : Theme.of(context) .extension()! - .getSecondaryDisabledButtonColor(context), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - if (icon != null) icon!, - if (icon != null && label != null) - const SizedBox( - width: 10, - ), - if (label != null) - Text( - label!, - style: getStyle(isDesktop, context), - ), - ], + .getSecondaryDisabledButtonStyle(context), + child: Padding( + padding: padding, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (icon != null) icon!, + if (icon != null && label != null) + SizedBox( + width: iconSpacing, + ), + if (label != null) + Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + label!, + style: getStyle(isDesktop, context), + ), + if (buttonHeight != null && buttonHeight == ButtonHeight.s) + const SizedBox( + height: 2, + ), + ], + ), + if (trailingIcon != null) + SizedBox( + width: iconSpacing, + ), + if (trailingIcon != null) trailingIcon!, + ], + ), ), ), ); diff --git a/lib/widgets/dialogs/basic_dialog.dart b/lib/widgets/dialogs/basic_dialog.dart new file mode 100644 index 000000000..f6dab30f0 --- /dev/null +++ b/lib/widgets/dialogs/basic_dialog.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; + +class BasicDialog extends StatelessWidget { + const BasicDialog({ + Key? key, + this.leftButton, + this.rightButton, + this.icon, + required this.title, + this.message, + this.desktopHeight = 474, + this.desktopWidth = 641, + this.canPopWithBackButton = false, + }) : super(key: key); + + final Widget? leftButton; + final Widget? rightButton; + + final Widget? icon; + + final String title; + final String? message; + + final double? desktopHeight; + final double desktopWidth; + + final bool canPopWithBackButton; + + @override + Widget build(BuildContext context) { + final isDesktop = Util.isDesktop; + + if (isDesktop) { + return DesktopDialog( + maxHeight: desktopHeight, + maxWidth: desktopWidth, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: STextStyles.desktopH3(context), + ), + const DesktopDialogCloseButton(), + ], + ), + ), + if (message != null) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Text( + message!, + style: STextStyles.desktopTextSmall(context), + ), + ), + if (leftButton != null || rightButton != null) + const SizedBox( + height: 32, + ), + if (leftButton != null || rightButton != null) + Padding( + padding: const EdgeInsets.all(32), + child: Row( + children: [ + leftButton != null + ? Expanded(child: leftButton!) + : const Spacer(), + const SizedBox( + width: 16, + ), + rightButton != null + ? Expanded(child: rightButton!) + : const Spacer(), + ], + ), + ) + ], + ), + ); + } else { + return WillPopScope( + onWillPop: () async { + return canPopWithBackButton; + }, + child: StackDialog( + title: title, + leftButton: leftButton, + rightButton: rightButton, + icon: icon, + message: message, + ), + ); + } + } +} diff --git a/lib/widgets/emoji_select_sheet.dart b/lib/widgets/emoji_select_sheet.dart index ecb6d1a1b..5fc49782c 100644 --- a/lib/widgets/emoji_select_sheet.dart +++ b/lib/widgets/emoji_select_sheet.dart @@ -2,10 +2,10 @@ import 'package:emojis/emoji.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/desktop/secondary_button.dart'; diff --git a/lib/widgets/eth_wallet_radio.dart b/lib/widgets/eth_wallet_radio.dart new file mode 100644 index 000000000..8398a6584 --- /dev/null +++ b/lib/widgets/eth_wallet_radio.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance.dart'; +import 'package:stackwallet/widgets/wallet_info_row/sub_widgets/wallet_info_row_coin_icon.dart'; + +class EthWalletRadio extends ConsumerStatefulWidget { + const EthWalletRadio({ + Key? key, + required this.walletId, + this.selectedWalletId, + }) : super(key: key); + + final String walletId; + final String? selectedWalletId; + + @override + ConsumerState createState() => _EthWalletRadioState(); +} + +class _EthWalletRadioState extends ConsumerState { + @override + Widget build(BuildContext context) { + final manager = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(widget.walletId))); + + return Padding( + padding: EdgeInsets.zero, + child: Container( + color: Colors.transparent, + child: Row( + children: [ + IgnorePointer( + child: Radio( + value: widget.walletId, + groupValue: widget.selectedWalletId, + onChanged: (_) { + // do nothing since changing updating the ui is already + // done elsewhere + }, + ), + ), + const SizedBox( + width: 12, + ), + WalletInfoCoinIcon( + coin: manager.coin, + size: 40, + ), + const SizedBox( + width: 12, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Text( + manager.walletName, + style: STextStyles.desktopTextExtraSmall(context).copyWith( + color: + Theme.of(context).extension()!.textDark, + ), + ), + WalletInfoRowBalance( + walletId: widget.walletId, + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/exchange/trocador/trocador_kyc_icon.dart b/lib/widgets/exchange/trocador/trocador_kyc_icon.dart new file mode 100644 index 000000000..eb0b04834 --- /dev/null +++ b/lib/widgets/exchange/trocador/trocador_kyc_icon.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/widgets/exchange/trocador/trocador_rating_type_enum.dart'; + +class TrocadorKYCIcon extends StatelessWidget { + const TrocadorKYCIcon({ + Key? key, + required this.kycType, + this.width = 18, + this.height = 18, + }) : super(key: key); + + final TrocadorKYCType kycType; + final double width; + final double height; + + String _getAssetName(TrocadorKYCType type) { + switch (type) { + case TrocadorKYCType.a: + return Assets.svg.trocadorRatingA; + case TrocadorKYCType.b: + return Assets.svg.trocadorRatingB; + case TrocadorKYCType.c: + return Assets.svg.trocadorRatingC; + case TrocadorKYCType.d: + return Assets.svg.trocadorRatingD; + } + } + + Color _getColor(TrocadorKYCType type, BuildContext context) { + switch (type) { + case TrocadorKYCType.a: + return Theme.of(context).extension()!.accentColorGreen; + case TrocadorKYCType.b: + return const Color(0xFF7AA500); + case TrocadorKYCType.c: + return Theme.of(context).extension()!.accentColorYellow; + case TrocadorKYCType.d: + return const Color(0xFFF37B58); + } + } + + @override + Widget build(BuildContext context) { + return SvgPicture.asset( + _getAssetName(kycType), + width: width, + height: height, + color: _getColor(kycType, context), + ); + } +} diff --git a/lib/widgets/exchange/trocador/trocador_kyc_info_button.dart b/lib/widgets/exchange/trocador/trocador_kyc_info_button.dart new file mode 100644 index 000000000..785ca9457 --- /dev/null +++ b/lib/widgets/exchange/trocador/trocador_kyc_info_button.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/widgets/exchange/trocador/trocador_kyc_icon.dart'; +import 'package:stackwallet/widgets/exchange/trocador/trocador_rating_type_enum.dart'; +import 'package:stackwallet/widgets/trocador_kyc_rating_info.dart'; + +class TrocadorKYCInfoButton extends StatelessWidget { + const TrocadorKYCInfoButton({ + Key? key, + required this.kycType, + }) : super(key: key); + + final TrocadorKYCType kycType; + + @override + Widget build(BuildContext context) { + return IconButton( + onPressed: () { + showDialog( + context: context, + builder: (context) => const TrocadorKYCRatingInfo(), + ); + }, + icon: TrocadorKYCIcon( + kycType: kycType, + ), + ); + } +} diff --git a/lib/widgets/exchange/trocador/trocador_rating_type_enum.dart b/lib/widgets/exchange/trocador/trocador_rating_type_enum.dart new file mode 100644 index 000000000..538ee8c37 --- /dev/null +++ b/lib/widgets/exchange/trocador/trocador_rating_type_enum.dart @@ -0,0 +1,15 @@ +enum TrocadorKYCType { + a, + b, + c, + d; + + static TrocadorKYCType fromString(String type) { + for (final result in values) { + if (result.name == type.toLowerCase()) { + return result; + } + } + throw ArgumentError("Invalid trocador kyc type: $type"); + } +} diff --git a/lib/widgets/expandable.dart b/lib/widgets/expandable.dart index 737f4ce7d..dbd5c67f6 100644 --- a/lib/widgets/expandable.dart +++ b/lib/widgets/expandable.dart @@ -19,7 +19,11 @@ class Expandable extends StatefulWidget { this.animation, this.animationDurationMultiplier = 1.0, this.onExpandChanged, + this.onExpandWillChange, this.controller, + this.expandOverride, + this.curve = Curves.easeInOut, + this.initialState = ExpandableState.collapsed, }) : super(key: key); final Widget header; @@ -28,7 +32,11 @@ class Expandable extends StatefulWidget { final Animation? animation; final double animationDurationMultiplier; final void Function(ExpandableState)? onExpandChanged; + final void Function(ExpandableState)? onExpandWillChange; final ExpandableController? controller; + final VoidCallback? expandOverride; + final Curve curve; + final ExpandableState initialState; @override State createState() => _ExpandableState(); @@ -40,14 +48,16 @@ class _ExpandableState extends State with TickerProviderStateMixin { late final Duration duration; late final ExpandableController? controller; - ExpandableState _toggleState = ExpandableState.collapsed; + late ExpandableState _toggleState; Future toggle() async { if (animation.isDismissed) { + widget.onExpandWillChange?.call(ExpandableState.expanded); await animationController.forward(); _toggleState = ExpandableState.expanded; widget.onExpandChanged?.call(_toggleState); } else if (animation.isCompleted) { + widget.onExpandWillChange?.call(ExpandableState.collapsed); await animationController.reverse(); _toggleState = ExpandableState.collapsed; widget.onExpandChanged?.call(_toggleState); @@ -57,6 +67,7 @@ class _ExpandableState extends State with TickerProviderStateMixin { @override void initState() { + _toggleState = widget.initialState; controller = widget.controller; controller?.toggle = toggle; @@ -68,10 +79,15 @@ class _ExpandableState extends State with TickerProviderStateMixin { vsync: this, duration: duration, ); + + final tween = _toggleState == ExpandableState.collapsed + ? Tween(begin: 0.0, end: 1.0) + : Tween(begin: 1.0, end: 0.0); + animation = widget.animation ?? - Tween(begin: 0.0, end: 1.0).animate( + tween.animate( CurvedAnimation( - curve: Curves.easeInOut, + curve: widget.curve, parent: animationController, ), ); @@ -92,7 +108,7 @@ class _ExpandableState extends State with TickerProviderStateMixin { MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( - onTap: toggle, + onTap: widget.expandOverride ?? toggle, child: Container( color: Colors.transparent, child: widget.header, diff --git a/lib/widgets/expandable2.dart b/lib/widgets/expandable2.dart new file mode 100644 index 000000000..5ae64e3fc --- /dev/null +++ b/lib/widgets/expandable2.dart @@ -0,0 +1,182 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/constants.dart'; + +enum Expandable2State { + collapsed, + expanded, +} + +class Expandable2Controller { + VoidCallback? toggle; + Expandable2State state = Expandable2State.collapsed; +} + +class Expandable2 extends StatefulWidget { + const Expandable2({ + Key? key, + required this.header, + required this.children, + this.background = Colors.white, + this.border = Colors.black, + this.animationController, + this.animation, + this.animationDurationMultiplier = 1.0, + this.onExpandWillChange, + this.onExpandChanged, + this.controller, + this.expandOverride, + }) : super(key: key); + + final Widget header; + final List children; + final Color background; + final Color border; + final AnimationController? animationController; + final Animation? animation; + final double animationDurationMultiplier; + final void Function(Expandable2State)? onExpandWillChange; + final void Function(Expandable2State)? onExpandChanged; + final Expandable2Controller? controller; + final VoidCallback? expandOverride; + + @override + State createState() => _Expandable2State(); +} + +class _Expandable2State extends State + with TickerProviderStateMixin { + final _key = GlobalKey(); + + late final AnimationController animationController; + late final Animation animation; + late final Duration duration; + late final Expandable2Controller? controller; + + Expandable2State _toggleState = Expandable2State.collapsed; + + void toggle() { + if (animation.isDismissed) { + _toggleState = Expandable2State.expanded; + widget.onExpandWillChange?.call(_toggleState); + animationController + .forward() + .then((_) => widget.onExpandChanged?.call(_toggleState)); + } else if (animation.isCompleted) { + _toggleState = Expandable2State.collapsed; + widget.onExpandWillChange?.call(_toggleState); + animationController + .reverse() + .then((_) => widget.onExpandChanged?.call(_toggleState)); + } + controller?.state = _toggleState; + setState(() {}); + } + + @override + void initState() { + controller = widget.controller; + controller?.toggle = toggle; + + duration = Duration( + milliseconds: (500 * widget.animationDurationMultiplier).toInt(), + ); + animationController = widget.animationController ?? + AnimationController( + vsync: this, + duration: duration, + ); + animation = widget.animation ?? + Tween(begin: 0.0, end: 1.0).animate( + CurvedAnimation( + curve: Curves.easeInOut, + parent: animationController, + ), + ); + super.initState(); + } + + double _top = 0; + + void getHeaderHeight() { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (_key.currentContext?.size?.height != null && + _top != _key.currentContext!.size!.height) { + setState(() { + _top = _key.currentContext!.size!.height; + }); + } + }); + } + + @override + void dispose() { + animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + getHeaderHeight(); + + return AnimatedContainer( + duration: duration, + decoration: _toggleState == Expandable2State.expanded + ? BoxDecoration( + color: widget.background, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + border: Border.all(color: widget.border), + boxShadow: [ + Theme.of(context).extension()!.standardBoxShadow, + ], + ) + : BoxDecoration( + color: widget.background, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + border: Border.all(color: widget.border), + ), + child: Stack( + children: [ + Padding( + padding: EdgeInsets.only(top: _top), + child: SizeTransition( + sizeFactor: animation, + axisAlignment: 1.0, + child: Column( + children: widget.children + .map( + (e) => Column( + children: [ + Container( + height: 1, + width: double.infinity, + color: widget.border, + ), + e, + ], + ), + ) + .toList(), + ), + ), + ), + MouseRegion( + key: _key, + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: widget.expandOverride ?? toggle, + child: Container( + color: Colors.transparent, + child: widget.header, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/hover_text_field.dart b/lib/widgets/hover_text_field.dart index 51d35aaa7..30e018192 100644 --- a/lib/widgets/hover_text_field.dart +++ b/lib/widgets/hover_text_field.dart @@ -5,9 +5,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/providers/global/wallets_provider.dart'; import 'package:stackwallet/providers/global/wallets_service_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; class DesktopWalletNameField extends ConsumerStatefulWidget { diff --git a/lib/widgets/icon_widgets/addressbook_icon.dart b/lib/widgets/icon_widgets/addressbook_icon.dart index 3e9f7b2a5..342c9638b 100644 --- a/lib/widgets/icon_widgets/addressbook_icon.dart +++ b/lib/widgets/icon_widgets/addressbook_icon.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; class AddressBookIcon extends StatelessWidget { const AddressBookIcon({ diff --git a/lib/widgets/icon_widgets/clipboard_icon.dart b/lib/widgets/icon_widgets/clipboard_icon.dart index caafda0a6..56e2bbedb 100644 --- a/lib/widgets/icon_widgets/clipboard_icon.dart +++ b/lib/widgets/icon_widgets/clipboard_icon.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; class ClipboardIcon extends StatelessWidget { const ClipboardIcon({ diff --git a/lib/widgets/icon_widgets/copy_icon.dart b/lib/widgets/icon_widgets/copy_icon.dart index 9f82a8066..b30925152 100644 --- a/lib/widgets/icon_widgets/copy_icon.dart +++ b/lib/widgets/icon_widgets/copy_icon.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; class CopyIcon extends StatelessWidget { const CopyIcon({ diff --git a/lib/widgets/icon_widgets/dice_icon.dart b/lib/widgets/icon_widgets/dice_icon.dart index ca502bfdf..8fa9dfbe9 100644 --- a/lib/widgets/icon_widgets/dice_icon.dart +++ b/lib/widgets/icon_widgets/dice_icon.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; class DiceIcon extends StatelessWidget { const DiceIcon({ diff --git a/lib/widgets/icon_widgets/eth_token_icon.dart b/lib/widgets/icon_widgets/eth_token_icon.dart new file mode 100644 index 000000000..cd5cfbbd1 --- /dev/null +++ b/lib/widgets/icon_widgets/eth_token_icon.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/models/isar/exchange_cache/currency.dart'; +import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; + +class EthTokenIcon extends ConsumerStatefulWidget { + const EthTokenIcon({ + Key? key, + required this.contractAddress, + this.size = 22, + }) : super(key: key); + + final String contractAddress; + final double size; + + @override + ConsumerState createState() => _EthTokenIconState(); +} + +class _EthTokenIconState extends ConsumerState { + late final String? imageUrl; + + @override + void initState() { + imageUrl = ExchangeDataLoadingService.instance.isar.currencies + .where() + .filter() + .tokenContractEqualTo(widget.contractAddress, caseSensitive: false) + .findFirstSync() + ?.image; + super.initState(); + } + + @override + Widget build(BuildContext context) { + if (imageUrl == null || imageUrl!.isEmpty) { + return SvgPicture.asset( + ref.watch(coinIconProvider(Coin.ethereum)), + width: widget.size, + height: widget.size, + ); + } else { + return SvgPicture.network( + imageUrl!, + width: widget.size, + height: widget.size, + ); + } + } +} diff --git a/lib/widgets/icon_widgets/pencil_icon.dart b/lib/widgets/icon_widgets/pencil_icon.dart index cb14f1cbf..cb747f0c2 100644 --- a/lib/widgets/icon_widgets/pencil_icon.dart +++ b/lib/widgets/icon_widgets/pencil_icon.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; class PencilIcon extends StatelessWidget { const PencilIcon({ diff --git a/lib/widgets/icon_widgets/qrcode_icon.dart b/lib/widgets/icon_widgets/qrcode_icon.dart index 598dbcf84..4ef18012e 100644 --- a/lib/widgets/icon_widgets/qrcode_icon.dart +++ b/lib/widgets/icon_widgets/qrcode_icon.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; class QrCodeIcon extends StatelessWidget { const QrCodeIcon({ diff --git a/lib/widgets/icon_widgets/share_icon.dart b/lib/widgets/icon_widgets/share_icon.dart new file mode 100644 index 000000000..58e0a3d32 --- /dev/null +++ b/lib/widgets/icon_widgets/share_icon.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; + +class ShareIcon extends StatelessWidget { + const ShareIcon({ + Key? key, + this.width = 18, + this.height = 18, + this.color, + }) : super(key: key); + + final double width; + final double height; + final Color? color; + + @override + Widget build(BuildContext context) { + return SvgPicture.asset( + Assets.svg.share, + width: width, + height: height, + color: color ?? Theme.of(context).extension()!.textDark3, + ); + } +} diff --git a/lib/widgets/icon_widgets/utxo_status_icon.dart b/lib/widgets/icon_widgets/utxo_status_icon.dart new file mode 100644 index 000000000..e5434921d --- /dev/null +++ b/lib/widgets/icon_widgets/utxo_status_icon.dart @@ -0,0 +1,98 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; + +enum UTXOStatusIconStatus { + confirmed, + unconfirmed; +} + +class UTXOStatusIcon extends StatelessWidget { + const UTXOStatusIcon({ + Key? key, + required this.width, + required this.height, + required this.blocked, + required this.selected, + required this.status, + required this.background, + }) : super(key: key); + + final double width; + final double height; + final bool blocked; + final bool selected; + final UTXOStatusIconStatus status; + final Color background; + + final _availableColor = const Color(0xFFF7931A); + final _blockedColor = const Color(0xFF96B0D6); + + @override + Widget build(BuildContext context) { + return ConditionalParent( + condition: status == UTXOStatusIconStatus.unconfirmed, + builder: (child) => Stack( + children: [ + child, + Positioned( + right: 0, + bottom: 0, + child: Stack( + children: [ + RoundedContainer( + radiusMultiplier: 100, + color: background, + width: width / 2.8, + height: height / 2.8, + ), + Positioned( + right: width / 2.8 - width / 3, + left: width / 2.8 - width / 3, + top: height / 2.8 - height / 3, + child: SvgPicture.asset( + Assets.svg.pending, + width: width / 3, + height: height / 3, + ), + ), + ], + ), + ), + ], + ), + child: Stack( + alignment: Alignment.center, + children: [ + RoundedContainer( + radiusMultiplier: 100, + color: selected + ? Theme.of(context).extension()!.infoItemIcons + : blocked + ? _blockedColor.withOpacity(0.3) + : _availableColor.withOpacity(0.2), + width: width, + height: height, + ), + SvgPicture.asset( + selected + ? Assets.svg.coinControl.selected + : blocked + ? Assets.svg.coinControl.blocked + : Assets.svg.coinControl.unBlocked, + width: 20, + height: 20, + color: selected + ? Colors.white + : blocked + ? _blockedColor + : _availableColor, + ), + ], + ), + ); + } +} diff --git a/lib/widgets/icon_widgets/x_icon.dart b/lib/widgets/icon_widgets/x_icon.dart index 4a497a464..39d00571e 100644 --- a/lib/widgets/icon_widgets/x_icon.dart +++ b/lib/widgets/icon_widgets/x_icon.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; class XIcon extends StatelessWidget { const XIcon({ diff --git a/lib/widgets/loading_indicator.dart b/lib/widgets/loading_indicator.dart index de4ed464e..1d95ca616 100644 --- a/lib/widgets/loading_indicator.dart +++ b/lib/widgets/loading_indicator.dart @@ -1,9 +1,12 @@ -import 'package:flutter/cupertino.dart'; -import 'package:lottie/lottie.dart'; +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:lottie/lottie.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; import 'package:stackwallet/utilities/assets.dart'; -class LoadingIndicator extends StatelessWidget { +class LoadingIndicator extends ConsumerWidget { const LoadingIndicator({ Key? key, this.width, @@ -14,14 +17,29 @@ class LoadingIndicator extends StatelessWidget { final double? height; @override - Widget build(BuildContext context) { - return SizedBox( - width: width, - height: height, - child: Lottie.asset( - Assets.lottie.test2, - animate: true, - repeat: true, + Widget build(BuildContext context, WidgetRef ref) { + final assetPath = ref.watch( + themeProvider.select((value) => value.assets.loadingGif), + ); + + return Container( + color: Colors.transparent, + child: Center( + child: SizedBox( + width: width, + height: height, + child: assetPath != null + ? Image.file( + File( + assetPath, + ), + ) + : Lottie.asset( + Assets.lottie.test2, + animate: true, + repeat: true, + ), + ), ), ); } diff --git a/lib/widgets/managed_favorite.dart b/lib/widgets/managed_favorite.dart index 5ced849fd..6383c8501 100644 --- a/lib/widgets/managed_favorite.dart +++ b/lib/widgets/managed_favorite.dart @@ -1,13 +1,14 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/custom_buttons/favorite_toggle.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; @@ -82,8 +83,10 @@ class _ManagedFavoriteCardState extends ConsumerState { ), child: Padding( padding: EdgeInsets.all(isDesktop ? 6 : 4), - child: SvgPicture.asset( - Assets.svg.iconFor(coin: manager.coin), + child: SvgPicture.file( + File( + ref.watch(coinIconProvider(manager.coin)), + ), width: 20, height: 20, ), @@ -104,12 +107,13 @@ class _ManagedFavoriteCardState extends ConsumerState { ), Expanded( child: Text( - "${Format.localizedStringAsFixed( - value: manager.cachedTotalBalance, + "${manager.balance.total.localizedStringAsFixed( locale: ref.watch( - localeServiceChangeNotifierProvider - .select((value) => value.locale)), - decimalPlaces: 8, + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), + ), + decimalPlaces: manager.coin.decimals, )} ${manager.coin.ticker}", style: STextStyles.itemSubtitle(context), ), @@ -146,11 +150,13 @@ class _ManagedFavoriteCardState extends ConsumerState { height: 2, ), Text( - "${Format.localizedStringAsFixed( - value: manager.cachedTotalBalance, - locale: ref.watch(localeServiceChangeNotifierProvider - .select((value) => value.locale)), - decimalPlaces: 8, + "${manager.balance.total.localizedStringAsFixed( + locale: ref.watch( + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), + ), + decimalPlaces: manager.coin.decimals, )} ${manager.coin.ticker}", style: STextStyles.itemSubtitle(context), ), diff --git a/lib/widgets/master_wallet_card.dart b/lib/widgets/master_wallet_card.dart new file mode 100644 index 000000000..b5a3802cb --- /dev/null +++ b/lib/widgets/master_wallet_card.dart @@ -0,0 +1,143 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/widgets/animated_widgets/rotate_icon.dart'; +import 'package:stackwallet/widgets/expandable.dart'; +import 'package:stackwallet/widgets/rounded_white_container.dart'; +import 'package:stackwallet/widgets/wallet_card.dart'; +import 'package:stackwallet/widgets/wallet_info_row/wallet_info_row.dart'; + +class MasterWalletCard extends ConsumerStatefulWidget { + const MasterWalletCard({ + Key? key, + required this.walletId, + this.popPrevious = false, + }) : super(key: key); + + final String walletId; + final bool popPrevious; + + @override + ConsumerState createState() => _MasterWalletCardState(); +} + +class _MasterWalletCardState extends ConsumerState { + final expandableController = ExpandableController(); + final rotateIconController = RotateIconController(); + late final List tokenContractAddresses; + + @override + void initState() { + final ethWallet = ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId) + .wallet as EthereumWallet; + + tokenContractAddresses = ethWallet.getWalletTokenContractAddresses(); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return RoundedWhiteContainer( + padding: EdgeInsets.zero, + child: Expandable( + controller: expandableController, + onExpandWillChange: (toState) { + if (toState == ExpandableState.expanded) { + rotateIconController.forward?.call(); + } else { + rotateIconController.reverse?.call(); + } + }, + header: Padding( + padding: const EdgeInsets.all(12), + child: Row( + children: [ + Expanded( + child: WalletInfoRow( + walletId: widget.walletId, + ), + ), + MaterialButton( + padding: const EdgeInsets.all(5), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + minWidth: 32, + height: 32, + color: Theme.of(context) + .extension()! + .textFieldDefaultBG, + elevation: 0, + hoverElevation: 0, + disabledElevation: 0, + highlightElevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), + ), + onPressed: () { + if (expandableController.state == ExpandableState.collapsed) { + rotateIconController.forward?.call(); + } else { + rotateIconController.reverse?.call(); + } + expandableController.toggle?.call(); + }, + child: RotateIcon( + controller: rotateIconController, + icon: SvgPicture.asset( + Assets.svg.chevronDown, + width: 14, + ), + curve: Curves.easeInOut, + ), + ), + ], + ), + ), + body: ListView( + shrinkWrap: true, + primary: false, + children: [ + Container( + width: double.infinity, + height: 1.5, + color: + Theme.of(context).extension()!.backgroundAppBar, + ), + Padding( + padding: const EdgeInsets.all( + 7, + ), + child: SimpleWalletCard( + walletId: widget.walletId, + popPrevious: true, + ), + ), + ...tokenContractAddresses.map( + (e) => Padding( + padding: const EdgeInsets.only( + left: 7, + right: 7, + bottom: 7, + ), + child: SimpleWalletCard( + walletId: widget.walletId, + contractAddress: e, + popPrevious: true, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/node_card.dart b/lib/widgets/node_card.dart index e366cd713..da0c20ccf 100644 --- a/lib/widgets/node_card.dart +++ b/lib/widgets/node_card.dart @@ -9,17 +9,16 @@ import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/default_nodes.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/enums/sync_type_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/test_epic_box_connection.dart'; import 'package:stackwallet/utilities/test_monero_node_connection.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/conditional_parent.dart'; import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; @@ -149,6 +148,7 @@ class _NodeCardState extends ConsumerState { case Coin.litecoin: case Coin.dogecoin: case Coin.firo: + case Coin.particl: case Coin.bitcoinTestNet: case Coin.firoTestNet: case Coin.dogecoinTestNet: @@ -261,7 +261,7 @@ class _NodeCardState extends ConsumerState { const SizedBox( width: 66, ), - BlueTextButton( + CustomTextButton( text: "Connect", enabled: _status == "Disconnected", onTap: () async { @@ -285,7 +285,7 @@ class _NodeCardState extends ConsumerState { const SizedBox( width: 48, ), - BlueTextButton( + CustomTextButton( text: "Details", onTap: () { Navigator.of(context).pushNamed( diff --git a/lib/widgets/node_options_sheet.dart b/lib/widgets/node_options_sheet.dart index 80ce2f5be..3c02c2ba7 100644 --- a/lib/widgets/node_options_sheet.dart +++ b/lib/widgets/node_options_sheet.dart @@ -9,17 +9,16 @@ import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart'; import 'package:stackwallet/pages/settings_views/global_settings_view/manage_nodes_views/node_details_view.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/default_nodes.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/enums/flush_bar_type.dart'; import 'package:stackwallet/utilities/enums/sync_type_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; import 'package:stackwallet/utilities/test_epic_box_connection.dart'; import 'package:stackwallet/utilities/test_monero_node_connection.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:tuple/tuple.dart'; @@ -132,6 +131,7 @@ class NodeOptionsSheet extends ConsumerWidget { case Coin.litecoin: case Coin.dogecoin: case Coin.firo: + case Coin.particl: case Coin.bitcoinTestNet: case Coin.firoTestNet: case Coin.dogecoinTestNet: @@ -307,7 +307,7 @@ class NodeOptionsSheet extends ConsumerWidget { child: TextButton( style: Theme.of(context) .extension()! - .getSecondaryEnabledButtonColor(context), + .getSecondaryEnabledButtonStyle(context), onPressed: () { Navigator.pop(context); Navigator.of(context).pushNamed( @@ -337,10 +337,10 @@ class NodeOptionsSheet extends ConsumerWidget { style: status == "Connected" ? Theme.of(context) .extension()! - .getPrimaryDisabledButtonColor(context) + .getPrimaryDisabledButtonStyle(context) : Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), onPressed: status == "Connected" ? null : () async { diff --git a/lib/widgets/rounded_container.dart b/lib/widgets/rounded_container.dart index 9f9bfbd8d..ef679472b 100644 --- a/lib/widgets/rounded_container.dart +++ b/lib/widgets/rounded_container.dart @@ -1,5 +1,6 @@ -import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; class RoundedContainer extends StatelessWidget { const RoundedContainer({ @@ -11,6 +12,9 @@ class RoundedContainer extends StatelessWidget { this.width, this.height, this.borderColor, + this.hoverColor, + this.boxShadow, + this.onPressed, }) : super(key: key); final Widget? child; @@ -20,23 +24,54 @@ class RoundedContainer extends StatelessWidget { final double? width; final double? height; final Color? borderColor; + final Color? hoverColor; + final List? boxShadow; + final VoidCallback? onPressed; @override Widget build(BuildContext context) { - return Container( - width: width, - height: height, - decoration: BoxDecoration( - color: color, - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius * radiusMultiplier, + return ConditionalParent( + condition: onPressed != null, + builder: (child) => RawMaterialButton( + fillColor: color, + hoverColor: hoverColor, + elevation: 0, + highlightElevation: 0, + disabledElevation: 0, + hoverElevation: 0, + focusElevation: 0, + padding: const EdgeInsets.all(0), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius * radiusMultiplier, + ), + side: borderColor == null + ? BorderSide.none + : BorderSide( + color: borderColor!, + width: 1.2, + ), ), - border: borderColor == null ? null : Border.all(color: borderColor!), - ), - child: Padding( - padding: padding, + onPressed: onPressed, child: child, ), + child: Container( + width: width, + height: height, + decoration: BoxDecoration( + color: onPressed != null ? Colors.transparent : color, + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius * radiusMultiplier, + ), + border: borderColor == null ? null : Border.all(color: borderColor!), + boxShadow: boxShadow, + ), + child: Padding( + padding: padding, + child: child, + ), + ), ); } } diff --git a/lib/widgets/rounded_white_container.dart b/lib/widgets/rounded_white_container.dart index 1173e95b1..d3acfcbb6 100644 --- a/lib/widgets/rounded_white_container.dart +++ b/lib/widgets/rounded_white_container.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/widgets/rounded_container.dart'; class RoundedWhiteContainer extends StatelessWidget { @@ -11,6 +11,9 @@ class RoundedWhiteContainer extends StatelessWidget { this.width, this.height, this.borderColor, + this.hoverColor, + this.boxShadow, + this.onPressed, }) : super(key: key); final Widget? child; @@ -19,6 +22,9 @@ class RoundedWhiteContainer extends StatelessWidget { final double? width; final double? height; final Color? borderColor; + final Color? hoverColor; + final List? boxShadow; + final VoidCallback? onPressed; @override Widget build(BuildContext context) { @@ -29,6 +35,9 @@ class RoundedWhiteContainer extends StatelessWidget { width: width, height: height, borderColor: borderColor, + boxShadow: boxShadow, + hoverColor: hoverColor, + onPressed: onPressed, child: child, ); } diff --git a/lib/widgets/stack_dialog.dart b/lib/widgets/stack_dialog.dart index ea2638264..50f248e6b 100644 --- a/lib/widgets/stack_dialog.dart +++ b/lib/widgets/stack_dialog.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; class StackDialogBase extends StatelessWidget { @@ -21,20 +21,24 @@ class StackDialogBase extends StatelessWidget { mainAxisAlignment: !Util.isDesktop ? MainAxisAlignment.end : MainAxisAlignment.center, children: [ - Material( - borderRadius: BorderRadius.circular( - 20, - ), - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).extension()!.popupBG, + Flexible( + child: SingleChildScrollView( + child: Material( borderRadius: BorderRadius.circular( 20, ), - ), - child: Padding( - padding: padding, - child: child, + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).extension()!.popupBG, + borderRadius: BorderRadius.circular( + 20, + ), + ), + child: Padding( + padding: padding, + child: child, + ), + ), ), ), ), @@ -193,7 +197,7 @@ class StackOkDialog extends StatelessWidget { }, style: Theme.of(context) .extension()! - .getPrimaryEnabledButtonColor(context), + .getPrimaryEnabledButtonStyle(context), child: Text( "Ok", style: STextStyles.button(context), diff --git a/lib/widgets/stack_text_field.dart b/lib/widgets/stack_text_field.dart index 1f1e9f8de..edc336bdb 100644 --- a/lib/widgets/stack_text_field.dart +++ b/lib/widgets/stack_text_field.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; InputDecoration standardInputDecoration( diff --git a/lib/widgets/table_view/table_view_row.dart b/lib/widgets/table_view/table_view_row.dart index 2bfc9d0c2..0b4fc8d16 100644 --- a/lib/widgets/table_view/table_view_row.dart +++ b/lib/widgets/table_view/table_view_row.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/widgets/expandable.dart'; import 'package:stackwallet/widgets/table_view/table_view_cell.dart'; @@ -10,6 +10,7 @@ class TableViewRow extends StatefulWidget { required this.expandingChild, this.decoration, this.onExpandChanged, + this.expandOverride, this.padding = const EdgeInsets.all(0), this.spacing = 0.0, this.crossAxisAlignment = CrossAxisAlignment.center, @@ -19,6 +20,7 @@ class TableViewRow extends StatefulWidget { final Widget? expandingChild; final BoxDecoration? decoration; final void Function(ExpandableState)? onExpandChanged; + final VoidCallback? expandOverride; final EdgeInsetsGeometry padding; final double spacing; final CrossAxisAlignment crossAxisAlignment; @@ -74,6 +76,7 @@ class _TableViewRowState extends State { ), ) : Expandable( + expandOverride: widget.expandOverride, onExpandChanged: widget.onExpandChanged, header: MouseRegion( onEnter: (_) { diff --git a/lib/widgets/textfield_icon_button.dart b/lib/widgets/textfield_icon_button.dart index 9375c6f31..df231a781 100644 --- a/lib/widgets/textfield_icon_button.dart +++ b/lib/widgets/textfield_icon_button.dart @@ -8,6 +8,7 @@ class TextFieldIconButton extends StatefulWidget { this.onTap, required this.child, this.color = Colors.transparent, + this.semanticsLabel = "Button", }) : super(key: key); final double width; @@ -15,6 +16,7 @@ class TextFieldIconButton extends StatefulWidget { final VoidCallback? onTap; final Widget child; final Color color; + final String semanticsLabel; @override State createState() => _TextFieldIconButtonState(); @@ -36,21 +38,25 @@ class _TextFieldIconButtonState extends State { width: widget.width, child: ClipRRect( borderRadius: BorderRadius.circular(100), - child: RawMaterialButton( - constraints: BoxConstraints( - minWidth: widget.width, - minHeight: widget.height, - ), - onPressed: onTap, - child: Container( - width: widget.width, - height: widget.height, - color: widget.color, - child: Center( - child: widget.child, + child: Semantics( + label: widget.semanticsLabel, + excludeSemantics: true, + child: RawMaterialButton( + constraints: BoxConstraints( + minWidth: widget.width, + minHeight: widget.height, + ), + onPressed: onTap, + child: Container( + width: widget.width, + height: widget.height, + color: widget.color, + child: Center( + child: widget.child, + ), ), ), - ), + ) ), ); } diff --git a/lib/widgets/textfields/exchange_textfield.dart b/lib/widgets/textfields/exchange_textfield.dart index 399d077c4..ab23eabbf 100644 --- a/lib/widgets/textfields/exchange_textfield.dart +++ b/lib/widgets/textfields/exchange_textfield.dart @@ -1,9 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/exchange/aggregate_currency.dart'; +import 'package:stackwallet/pages/buy_view/sub_widgets/crypto_selection_view.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/assets.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/loading_indicator.dart'; class ExchangeTextField extends StatefulWidget { @@ -21,8 +24,7 @@ class ExchangeTextField extends StatefulWidget { this.onSubmitted, this.onTap, required this.isWalletCoin, - this.image, - this.ticker, + this.currency, this.readOnly = false, }) : super(key: key); @@ -40,8 +42,7 @@ class ExchangeTextField extends StatefulWidget { final bool isWalletCoin; final bool readOnly; - final String? image; - final String? ticker; + final AggregateCurrency? currency; @override State createState() => _ExchangeTextFieldState(); @@ -62,6 +63,8 @@ class _ExchangeTextFieldState extends State { late final void Function(String)? onChanged; late final void Function(String)? onSubmitted; + final isDesktop = Util.isDesktop; + @override void initState() { borderRadius = widget.borderRadius; @@ -100,16 +103,18 @@ class _ExchangeTextFieldState extends State { enableSuggestions: false, autocorrect: false, readOnly: widget.readOnly, - keyboardType: const TextInputType.numberWithOptions( - signed: false, - decimal: true, - ), + keyboardType: isDesktop + ? null + : const TextInputType.numberWithOptions( + signed: false, + decimal: true, + ), decoration: InputDecoration( contentPadding: const EdgeInsets.only( top: 12, left: 12, ), - hintText: "0", + hintText: widget.currency == null ? "select currency" : "0", hintStyle: STextStyles.fieldLabel(context).copyWith( fontSize: 14, ), @@ -151,12 +156,22 @@ class _ExchangeTextFieldState extends State { ), child: Builder( builder: (context) { - final image = widget.image; - - if (image != null && image.isNotEmpty) { + if (isStackCoin(widget.currency?.ticker)) { + return Center( + child: CoinIconForTicker( + size: 18, + ticker: widget.currency!.ticker, + ), + // child: getIconForTicker( + // widget.currency!.ticker, + // size: 18, + // ), + ); + } else if (widget.currency != null && + widget.currency!.image.isNotEmpty) { return Center( child: SvgPicture.network( - image, + widget.currency!.image, height: 18, placeholderBuilder: (_) => Container( width: 18, @@ -203,7 +218,7 @@ class _ExchangeTextFieldState extends State { width: 6, ), Text( - widget.ticker?.toUpperCase() ?? "-", + widget.currency?.ticker.toUpperCase() ?? "n/a", style: STextStyles.smallMed14(context).copyWith( color: Theme.of(context) .extension()! @@ -235,150 +250,3 @@ class _ExchangeTextFieldState extends State { ); } } - -// experimental UNUSED -// class ExchangeTextField extends StatefulWidget { -// const ExchangeTextField({ -// Key? key, -// this.borderRadius = 0, -// this.background, -// required this.controller, -// this.buttonColor, -// required this.focusNode, -// this.buttonContent, -// required this.textStyle, -// this.onButtonTap, -// this.onChanged, -// this.onSubmitted, -// }) : super(key: key); -// -// final double borderRadius; -// final Color? background; -// final Color? buttonColor; -// final Widget? buttonContent; -// final TextEditingController controller; -// final FocusNode focusNode; -// final TextStyle textStyle; -// final VoidCallback? onButtonTap; -// final void Function(String)? onChanged; -// final void Function(String)? onSubmitted; -// -// @override -// State createState() => _ExchangeTextFieldState(); -// } -// -// class _ExchangeTextFieldState extends State { -// late final TextEditingController controller; -// late final FocusNode focusNode; -// late final TextStyle textStyle; -// -// late final double borderRadius; -// -// late final Color? background; -// late final Color? buttonColor; -// late final Widget? buttonContent; -// late final VoidCallback? onButtonTap; -// late final void Function(String)? onChanged; -// late final void Function(String)? onSubmitted; -// -// @override -// void initState() { -// borderRadius = widget.borderRadius; -// background = widget.background; -// buttonColor = widget.buttonColor; -// controller = widget.controller; -// focusNode = widget.focusNode; -// buttonContent = widget.buttonContent; -// textStyle = widget.textStyle; -// onButtonTap = widget.onButtonTap; -// onChanged = widget.onChanged; -// onSubmitted = widget.onSubmitted; -// -// super.initState(); -// } -// -// @override -// Widget build(BuildContext context) { -// return Container( -// decoration: BoxDecoration( -// color: background, -// borderRadius: BorderRadius.circular(borderRadius), -// ), -// child: IntrinsicHeight( -// child: Row( -// crossAxisAlignment: CrossAxisAlignment.stretch, -// children: [ -// Expanded( -// child: MouseRegion( -// cursor: SystemMouseCursors.text, -// child: GestureDetector( -// onTap: () { -// // -// }, -// child: Padding( -// padding: const EdgeInsets.only( -// left: 16, -// top: 18, -// bottom: 17, -// ), -// child: IgnorePointer( -// ignoring: true, -// child: EditableText( -// controller: controller, -// focusNode: focusNode, -// style: textStyle, -// onChanged: onChanged, -// onSubmitted: onSubmitted, -// onEditingComplete: () => print("lol"), -// autocorrect: false, -// enableSuggestions: false, -// keyboardType: const TextInputType.numberWithOptions( -// signed: false, -// decimal: true, -// ), -// inputFormatters: [ -// // regex to validate a crypto amount with 8 decimal places -// TextInputFormatter.withFunction((oldValue, -// newValue) => -// RegExp(r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$') -// .hasMatch(newValue.text) -// ? newValue -// : oldValue), -// ], -// cursorColor: textStyle.color ?? -// Theme.of(context).backgroundColor, -// backgroundCursorColor: background ?? Colors.transparent, -// ), -// ), -// ), -// ), -// ), -// ), -// MouseRegion( -// cursor: SystemMouseCursors.click, -// child: GestureDetector( -// onTap: () => onButtonTap?.call(), -// child: Container( -// decoration: BoxDecoration( -// color: buttonColor, -// borderRadius: BorderRadius.horizontal( -// right: Radius.circular( -// borderRadius, -// ), -// ), -// ), -// child: Padding( -// padding: const EdgeInsets.symmetric( -// horizontal: 16, -// ), -// child: buttonContent, -// ), -// ), -// ), -// ), -// ], -// ), -// ), -// ); -// } -// } diff --git a/lib/widgets/toggle.dart b/lib/widgets/toggle.dart index 651f2b5e1..b1434045d 100644 --- a/lib/widgets/toggle.dart +++ b/lib/widgets/toggle.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; class Toggle extends StatefulWidget { @@ -186,29 +186,31 @@ class ToggleState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - SvgPicture.asset( - widget.onIcon ?? "", - width: 12, - height: 14, - color: isDesktop - ? !_isOn - ? Theme.of(context) - .extension()! - .accentColorBlue - : Theme.of(context) - .extension()! - .buttonTextSecondary - : !_isOn - ? Theme.of(context) - .extension()! - .textDark - : Theme.of(context) - .extension()! - .textSubtitle1, - ), - const SizedBox( - width: 5, - ), + if (widget.onIcon != null) + SvgPicture.asset( + widget.onIcon ?? "", + width: 12, + height: 14, + color: isDesktop + ? !_isOn + ? Theme.of(context) + .extension()! + .accentColorBlue + : Theme.of(context) + .extension()! + .buttonTextSecondary + : !_isOn + ? Theme.of(context) + .extension()! + .textDark + : Theme.of(context) + .extension()! + .textSubtitle1, + ), + if (widget.onIcon != null) + const SizedBox( + width: 5, + ), Text( widget.onText ?? "", style: isDesktop @@ -243,29 +245,31 @@ class ToggleState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - SvgPicture.asset( - widget.offIcon ?? "", - width: 12, - height: 14, - color: isDesktop - ? _isOn - ? Theme.of(context) - .extension()! - .accentColorBlue - : Theme.of(context) - .extension()! - .buttonTextSecondary - : _isOn - ? Theme.of(context) - .extension()! - .textDark - : Theme.of(context) - .extension()! - .textSubtitle1, - ), - const SizedBox( - width: 5, - ), + if (widget.offIcon != null) + SvgPicture.asset( + widget.offIcon ?? "", + width: 12, + height: 14, + color: isDesktop + ? _isOn + ? Theme.of(context) + .extension()! + .accentColorBlue + : Theme.of(context) + .extension()! + .buttonTextSecondary + : _isOn + ? Theme.of(context) + .extension()! + .textDark + : Theme.of(context) + .extension()! + .textSubtitle1, + ), + if (widget.offIcon != null) + const SizedBox( + width: 5, + ), Text( widget.offText ?? "", style: isDesktop diff --git a/lib/widgets/trade_card.dart b/lib/widgets/trade_card.dart index 5a14a0777..3e442e07a 100644 --- a/lib/widgets/trade_card.dart +++ b/lib/widgets/trade_card.dart @@ -1,10 +1,13 @@ +import 'dart:io'; + import 'package:decimal/decimal.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; -import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; import 'package:stackwallet/utilities/util.dart'; @@ -21,15 +24,25 @@ class TradeCard extends ConsumerWidget { final Trade trade; final VoidCallback onTap; - String _fetchIconAssetForStatus(String statusString, BuildContext context) { + String _fetchIconAssetForStatus(String statusString, IThemeAssets assets) { ChangeNowTransactionStatus? status; try { if (statusString.toLowerCase().startsWith("waiting")) { - statusString = "waiting"; + statusString = "Waiting"; } status = changeNowTransactionStatusFromStringIgnoreCase(statusString); } on ArgumentError catch (_) { - status = ChangeNowTransactionStatus.Failed; + switch (statusString.toLowerCase()) { + case "funds confirming": + case "processing payment": + return assets.txExchangePending; + + case "completed": + return assets.txExchange; + + default: + status = ChangeNowTransactionStatus.Failed; + } } switch (status) { @@ -40,11 +53,11 @@ class TradeCard extends ConsumerWidget { case ChangeNowTransactionStatus.Sending: case ChangeNowTransactionStatus.Refunded: case ChangeNowTransactionStatus.Verifying: - return Assets.svg.txExchangePending(context); + return assets.txExchangePending; case ChangeNowTransactionStatus.Finished: - return Assets.svg.txExchange(context); + return assets.txExchange; case ChangeNowTransactionStatus.Failed: - return Assets.svg.txExchangeFailed(context); + return assets.txExchangeFailed; } } @@ -72,10 +85,12 @@ class TradeCard extends ConsumerWidget { borderRadius: BorderRadius.circular(32), ), child: Center( - child: SvgPicture.asset( - _fetchIconAssetForStatus( - trade.status, - context, + child: SvgPicture.file( + File( + _fetchIconAssetForStatus( + trade.status, + ref.watch(themeAssetsProvider), + ), ), width: 32, height: 32, diff --git a/lib/widgets/transaction_card.dart b/lib/widgets/transaction_card.dart index 4389573c3..8cb613152 100644 --- a/lib/widgets/transaction_card.dart +++ b/lib/widgets/transaction_card.dart @@ -2,16 +2,18 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/models/paymint/transactions_model.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; import 'package:stackwallet/notifications/show_flush_bar.dart'; import 'package:stackwallet/pages/wallet_view/sub_widgets/tx_icon.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart'; +import 'package:stackwallet/providers/db/main_db_provider.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/format.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:tuple/tuple.dart'; @@ -33,15 +35,29 @@ class TransactionCard extends ConsumerStatefulWidget { class _TransactionCardState extends ConsumerState { late final Transaction _transaction; late final String walletId; + late final bool isTokenTx; + late final String prefix; + late final String unit; + late final Coin coin; - String whatIsIt(String type, Coin coin) { + String whatIsIt( + TransactionType type, + Coin coin, + int currentHeight, + ) { if (coin == Coin.epicCash && _transaction.slateId == null) { return "Restored Funds"; } - if (_transaction.subType == "mint") { + final confirmedStatus = _transaction.isConfirmed( + currentHeight, + coin.requiredConfirmations, + ); + + if (type != TransactionType.incoming && + _transaction.subType == TransactionSubType.mint) { // if (type == "Received") { - if (_transaction.confirmedStatus) { + if (confirmedStatus) { return "Anonymized"; } else { return "Anonymizing"; @@ -57,23 +73,25 @@ class _TransactionCardState extends ConsumerState { // } } - if (type == "Received") { + if (type == TransactionType.incoming) { // if (_transaction.isMinting) { // return "Minting"; // } else - if (_transaction.confirmedStatus) { + if (confirmedStatus) { return "Received"; } else { return "Receiving"; } - } else if (type == "Sent") { - if (_transaction.confirmedStatus) { + } else if (type == TransactionType.outgoing) { + if (confirmedStatus) { return "Sent"; } else { return "Sending"; } + } else if (type == TransactionType.sentToSelf) { + return "Sent to self"; } else { - return type; + return type.name; } } @@ -81,6 +99,29 @@ class _TransactionCardState extends ConsumerState { void initState() { walletId = widget.walletId; _transaction = widget.transaction; + isTokenTx = _transaction.subType == TransactionSubType.ethToken; + if (Util.isDesktop) { + if (_transaction.type == TransactionType.outgoing) { + prefix = "-"; + } else if (_transaction.type == TransactionType.incoming) { + prefix = "+"; + } else { + prefix = ""; + } + } else { + prefix = ""; + } + coin = ref + .read(walletsChangeNotifierProvider) + .getManager(widget.walletId) + .coin; + + unit = isTokenTx + ? ref + .read(mainDBProvider) + .getEthContractSync(_transaction.otherData!)! + .symbol + : coin.ticker; super.initState(); } @@ -88,27 +129,18 @@ class _TransactionCardState extends ConsumerState { Widget build(BuildContext context) { final locale = ref.watch( localeServiceChangeNotifierProvider.select((value) => value.locale)); - final manager = ref.watch(walletsChangeNotifierProvider - .select((value) => value.getManager(walletId))); final baseCurrency = ref .watch(prefsChangeNotifierProvider.select((value) => value.currency)); - final coin = manager.coin; - final price = ref - .watch(priceAnd24hChangeNotifierProvider - .select((value) => value.getPrice(coin))) + .watch(priceAnd24hChangeNotifierProvider.select((value) => isTokenTx + ? value.getTokenPrice(_transaction.otherData!) + : value.getPrice(coin))) .item1; - String prefix = ""; - if (Util.isDesktop) { - if (_transaction.txType == "Sent") { - prefix = "-"; - } else if (_transaction.txType == "Received") { - prefix = "+"; - } - } + final currentHeight = ref.watch(walletsChangeNotifierProvider + .select((value) => value.getManager(walletId).currentHeight)); return Material( color: Theme.of(context).extension()!.popupBG, @@ -166,7 +198,11 @@ class _TransactionCardState extends ConsumerState { padding: const EdgeInsets.all(8), child: Row( children: [ - TxIcon(transaction: _transaction), + TxIcon( + transaction: _transaction, + coin: coin, + currentHeight: currentHeight, + ), const SizedBox( width: 14, ), @@ -184,7 +220,11 @@ class _TransactionCardState extends ConsumerState { child: Text( _transaction.isCancelled ? "Cancelled" - : whatIsIt(_transaction.txType, coin), + : whatIsIt( + _transaction.type, + coin, + currentHeight, + ), style: STextStyles.itemSubtitle12(context), ), ), @@ -197,11 +237,13 @@ class _TransactionCardState extends ConsumerState { fit: BoxFit.scaleDown, child: Builder( builder: (_) { - final amount = _transaction.amount; + final amount = _transaction.realAmount; + return Text( - "$prefix${Format.satoshiAmountToPrettyString(amount, locale, coin)} ${coin.ticker}", - style: - STextStyles.itemSubtitle12_600(context), + "$prefix${amount.localizedStringAsFixed( + locale: locale, + )} $unit", + style: STextStyles.itemSubtitle12(context), ); }, ), @@ -237,13 +279,13 @@ class _TransactionCardState extends ConsumerState { fit: BoxFit.scaleDown, child: Builder( builder: (_) { - int value = _transaction.amount; + final amount = _transaction.realAmount; return Text( - "$prefix${Format.localizedStringAsFixed( - value: Format.satoshisToAmount(value, - coin: coin) * - price, + "$prefix${Amount.fromDecimal( + amount.decimal * price, + fractionDigits: 2, + ).localizedStringAsFixed( locale: locale, decimalPlaces: 2, )} $baseCurrency", diff --git a/lib/widgets/trocador_kyc_rating_info.dart b/lib/widgets/trocador_kyc_rating_info.dart new file mode 100644 index 000000000..f7d531da7 --- /dev/null +++ b/lib/widgets/trocador_kyc_rating_info.dart @@ -0,0 +1,174 @@ +import 'package:flutter/material.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; +import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/desktop/secondary_button.dart'; +import 'package:stackwallet/widgets/exchange/trocador/trocador_kyc_icon.dart'; +import 'package:stackwallet/widgets/exchange/trocador/trocador_rating_type_enum.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; + +class TrocadorKYCRatingInfo extends StatelessWidget { + const TrocadorKYCRatingInfo({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final small = MediaQuery.of(context).size.width <= 500; + + return ConditionalParent( + condition: !small, + builder: (child) => DesktopDialog( + maxHeight: double.infinity, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.only(left: 32), + child: Text( + "Trocador KYC Rating", + style: STextStyles.desktopH3(context), + ), + ), + const DesktopDialogCloseButton(), + ], + ), + const SizedBox( + height: 16, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 32, + ), + child: child, + ), + Padding( + padding: const EdgeInsets.all(32), + child: Row( + children: [ + const Spacer(), + const SizedBox( + width: 16, + ), + Expanded( + child: PrimaryButton( + label: "Ok", + buttonHeight: ButtonHeight.l, + onPressed: Navigator.of(context).pop, + ), + ), + ], + ), + ) + ], + ), + ), + child: ConditionalParent( + condition: small, + builder: (child) { + return StackDialogBase( + child: child, + ); + }, + child: Column( + children: [ + if (small) + Text( + "Trocador KYC Rating", + style: STextStyles.pageTitleH2(context), + ), + if (small) + const SizedBox( + height: 16, + ), + const _Rating( + kycType: TrocadorKYCType.a, + text: "Never asks for user verification.", + ), + const SizedBox( + height: 16, + ), + const _Rating( + kycType: TrocadorKYCType.b, + text: "Rarely asks for verification. Refunds if refused.", + ), + const SizedBox( + height: 16, + ), + const _Rating( + kycType: TrocadorKYCType.c, + text: + "Rarely asks for verification. Refunds if refused, unless a " + "legal order prevents it.", + ), + const SizedBox( + height: 16, + ), + const _Rating( + kycType: TrocadorKYCType.d, + text: + "Rarely asks for verification. In case of refusal may block " + "funds indefinitely without a legal order.", + ), + if (small) + Padding( + padding: const EdgeInsets.only( + top: 16, + ), + child: Row( + children: [ + const Spacer(), + const SizedBox( + width: 16, + ), + Expanded( + child: SecondaryButton( + label: "Close", + onPressed: Navigator.of(context).pop, + ), + ) + ], + ), + ), + ], + ), + ), + ); + } +} + +class _Rating extends StatelessWidget { + const _Rating({ + Key? key, + required this.kycType, + required this.text, + }) : super(key: key); + + final TrocadorKYCType kycType; + final String text; + + @override + Widget build(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TrocadorKYCIcon( + kycType: kycType, + width: 20, + height: 20, + ), + const SizedBox( + width: 8, + ), + Flexible( + child: Text( + text, + style: STextStyles.subtitle(context), + ), + ), + ], + ); + } +} diff --git a/lib/widgets/wallet_card.dart b/lib/widgets/wallet_card.dart index 0e8a515bd..a5130dd1e 100644 --- a/lib/widgets/wallet_card.dart +++ b/lib/widgets/wallet_card.dart @@ -1,50 +1,182 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; +import 'package:stackwallet/pages/token_view/token_view.dart'; import 'package:stackwallet/pages/wallet_view/wallet_view.dart'; +import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_token_view.dart'; +import 'package:stackwallet/pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart'; +import 'package:stackwallet/providers/db/main_db_provider.dart'; +import 'package:stackwallet/providers/global/secure_store_provider.dart'; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart'; +import 'package:stackwallet/services/coins/manager.dart'; +import 'package:stackwallet/services/ethereum/ethereum_token_service.dart'; +import 'package:stackwallet/services/transaction_notification_tracker.dart'; import 'package:stackwallet/utilities/constants.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/show_loading.dart'; +import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/desktop/primary_button.dart'; +import 'package:stackwallet/widgets/dialogs/basic_dialog.dart'; import 'package:stackwallet/widgets/rounded_white_container.dart'; import 'package:stackwallet/widgets/wallet_info_row/wallet_info_row.dart'; import 'package:tuple/tuple.dart'; -class WalletSheetCard extends ConsumerWidget { - const WalletSheetCard({ +class SimpleWalletCard extends ConsumerWidget { + const SimpleWalletCard({ Key? key, required this.walletId, + this.contractAddress, this.popPrevious = false, + this.desktopNavigatorState, }) : super(key: key); final String walletId; + final String? contractAddress; final bool popPrevious; + final NavigatorState? desktopNavigatorState; + + Future _loadTokenWallet( + BuildContext context, + WidgetRef ref, + Manager manager, + EthContract contract, + ) async { + ref.read(tokenServiceStateProvider.state).state = EthTokenWallet( + token: contract, + secureStore: ref.read(secureStoreProvider), + ethWallet: manager.wallet as EthereumWallet, + tracker: TransactionNotificationTracker( + walletId: walletId, + ), + ); + + try { + await ref.read(tokenServiceProvider)!.initialize(); + return true; + } catch (_) { + await showDialog( + barrierDismissible: false, + context: context, + builder: (context) => BasicDialog( + title: "Failed to load token data", + desktopHeight: double.infinity, + desktopWidth: 450, + rightButton: PrimaryButton( + label: "OK", + onPressed: () { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + if (desktopNavigatorState == null) { + Navigator.of(context).pop(); + } + }, + ), + ), + ); + return false; + } + } + + void _openWallet(BuildContext context, WidgetRef ref) async { + final nav = Navigator.of(context); + + final manager = + ref.read(walletsChangeNotifierProvider).getManager(walletId); + if (manager.coin == Coin.monero || manager.coin == Coin.wownero) { + await manager.initializeExisting(); + } + if (context.mounted) { + if (popPrevious) nav.pop(); + + if (desktopNavigatorState != null) { + unawaited( + desktopNavigatorState!.pushNamed( + DesktopWalletView.routeName, + arguments: walletId, + ), + ); + } else { + unawaited( + nav.pushNamed( + WalletView.routeName, + arguments: Tuple2( + walletId, + ref + .read(walletsChangeNotifierProvider) + .getManagerProvider(walletId), + ), + ), + ); + } + + if (contractAddress != null) { + final contract = + ref.read(mainDBProvider).getEthContractSync(contractAddress!)!; + + final success = await showLoading( + whileFuture: _loadTokenWallet( + desktopNavigatorState?.context ?? context, + ref, + manager, + contract), + context: desktopNavigatorState?.context ?? context, + opaqueBG: true, + message: "Loading ${contract.name}", + ); + + if (!success) { + return; + } + + if (desktopNavigatorState == null) { + // pop loading + nav.pop(); + } + + if (desktopNavigatorState != null) { + await desktopNavigatorState!.pushNamed( + DesktopTokenView.routeName, + arguments: walletId, + ); + } else { + await nav.pushNamed( + TokenView.routeName, + arguments: walletId, + ); + } + } + } + } @override Widget build(BuildContext context, WidgetRef ref) { - return RoundedWhiteContainer( - padding: const EdgeInsets.all(0), - child: MaterialButton( - // splashColor: Theme.of(context).extension()!.highlight, - key: Key("walletsSheetItemButtonKey_$walletId"), - padding: const EdgeInsets.all(5), - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - Constants.size.circularBorderRadius, + return ConditionalParent( + condition: !Util.isDesktop, + builder: (child) => RoundedWhiteContainer( + padding: const EdgeInsets.all(0), + child: MaterialButton( + // splashColor: Theme.of(context).extension()!.highlight, + key: Key("walletsSheetItemButtonKey_$walletId"), + padding: const EdgeInsets.all(10), + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + Constants.size.circularBorderRadius, + ), ), + onPressed: () => _openWallet(context, ref), + child: child, ), - onPressed: () { - if (popPrevious) Navigator.of(context).pop(); - Navigator.of(context).pushNamed( - WalletView.routeName, - arguments: Tuple2( - walletId, - ref - .read(walletsChangeNotifierProvider) - .getManagerProvider(walletId)), - ); - }, - child: WalletInfoRow( - walletId: walletId, - ), + ), + child: WalletInfoRow( + walletId: walletId, + contractAddress: contractAddress, + onPressedDesktop: + Util.isDesktop ? () => _openWallet(context, ref) : null, ), ); } diff --git a/lib/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance.dart b/lib/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance.dart new file mode 100644 index 000000000..051e96f70 --- /dev/null +++ b/lib/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; +import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/services/coins/ethereum/ethereum_wallet.dart'; +import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/utilities/util.dart'; + +class WalletInfoRowBalance extends ConsumerWidget { + const WalletInfoRowBalance({ + Key? key, + required this.walletId, + this.contractAddress, + }) : super(key: key); + + final String walletId; + final String? contractAddress; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final manager = ref.watch(ref + .watch(walletsChangeNotifierProvider.notifier) + .getManagerProvider(walletId)); + + final locale = ref.watch( + localeServiceChangeNotifierProvider.select( + (value) => value.locale, + ), + ); + + Amount totalBalance; + int decimals; + String unit; + if (contractAddress == null) { + totalBalance = manager.balance.total; + if (manager.coin == Coin.firo || manager.coin == Coin.firoTestNet) { + totalBalance = + totalBalance + (manager.wallet as FiroWallet).balancePrivate.total; + } + unit = manager.coin.ticker; + decimals = manager.coin.decimals; + } else { + final ethWallet = manager.wallet as EthereumWallet; + final contract = MainDB.instance.getEthContractSync(contractAddress!)!; + totalBalance = ethWallet.getCachedTokenBalance(contract).total; + unit = contract.symbol; + decimals = contract.decimals; + } + + return Text( + "${totalBalance.localizedStringAsFixed( + locale: locale, + decimalPlaces: decimals, + )} $unit", + style: Util.isDesktop + ? STextStyles.desktopTextExtraSmall(context).copyWith( + color: Theme.of(context).extension()!.textSubtitle1, + ) + : STextStyles.itemSubtitle(context), + ); + } +} diff --git a/lib/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance_future.dart b/lib/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance_future.dart deleted file mode 100644 index a59c157ec..000000000 --- a/lib/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance_future.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:decimal/decimal.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/format.dart'; -import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; -import 'package:stackwallet/utilities/util.dart'; -import 'package:stackwallet/widgets/animated_text.dart'; - -class WalletInfoRowBalanceFuture extends ConsumerWidget { - const WalletInfoRowBalanceFuture({Key? key, required this.walletId}) - : super(key: key); - - final String walletId; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final manager = ref.watch(ref - .watch(walletsChangeNotifierProvider.notifier) - .getManagerProvider(walletId)); - - final locale = ref.watch( - localeServiceChangeNotifierProvider.select( - (value) => value.locale, - ), - ); - - return FutureBuilder( - future: manager.totalBalance, - builder: (builderContext, AsyncSnapshot snapshot) { - if (snapshot.connectionState == ConnectionState.done && - snapshot.hasData) { - return Text( - "${Format.localizedStringAsFixed( - value: snapshot.data!, - locale: locale, - decimalPlaces: 8, - )} ${manager.coin.ticker}", - style: Util.isDesktop - ? STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ) - : STextStyles.itemSubtitle(context), - ); - } else { - return AnimatedText( - stringsToLoopThrough: const [ - "Loading balance", - "Loading balance.", - "Loading balance..", - "Loading balance..." - ], - style: Util.isDesktop - ? STextStyles.desktopTextExtraSmall(context).copyWith( - color: Theme.of(context) - .extension()! - .textSubtitle1, - ) - : STextStyles.itemSubtitle(context), - ); - } - }, - ); - } -} diff --git a/lib/widgets/wallet_info_row/sub_widgets/wallet_info_row_coin_icon.dart b/lib/widgets/wallet_info_row/sub_widgets/wallet_info_row_coin_icon.dart index ec5924de6..5d87cd90b 100644 --- a/lib/widgets/wallet_info_row/sub_widgets/wallet_info_row_coin_icon.dart +++ b/lib/widgets/wallet_info_row/sub_widgets/wallet_info_row_coin_icon.dart @@ -1,34 +1,73 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:stackwallet/utilities/assets.dart'; +import 'package:isar/isar.dart'; +import 'package:stackwallet/models/isar/exchange_cache/currency.dart'; +import 'package:stackwallet/services/exchange/change_now/change_now_exchange.dart'; +import 'package:stackwallet/services/exchange/exchange_data_loading_service.dart'; +import 'package:stackwallet/themes/coin_icon_provider.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/constants.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; -class WalletInfoCoinIcon extends StatelessWidget { - const WalletInfoCoinIcon({Key? key, required this.coin}) : super(key: key); +class WalletInfoCoinIcon extends ConsumerWidget { + const WalletInfoCoinIcon({ + Key? key, + required this.coin, + this.size = 32, + this.contractAddress, + }) : super(key: key); final Coin coin; + final String? contractAddress; + final double size; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + Currency? currency; + if (contractAddress != null) { + currency = ExchangeDataLoadingService.instance.isar.currencies + .where() + .exchangeNameEqualTo(ChangeNowExchange.exchangeName) + .filter() + .tokenContractEqualTo( + contractAddress!, + caseSensitive: false, + ) + .and() + .imageIsNotEmpty() + .findFirstSync(); + } + return Container( + width: size, + height: size, decoration: BoxDecoration( color: Theme.of(context) .extension()! .colorForCoin(coin) - .withOpacity(0.5), + .withOpacity(0.4), borderRadius: BorderRadius.circular( Constants.size.circularBorderRadius, ), ), child: Padding( - padding: const EdgeInsets.all(4), - child: SvgPicture.asset( - Assets.svg.iconFor(coin: coin), - width: 20, - height: 20, - ), + padding: EdgeInsets.all(size / 5), + child: currency != null && currency.image.isNotEmpty + ? SvgPicture.network( + currency.image, + width: 20, + height: 20, + ) + : SvgPicture.file( + File( + ref.watch(coinIconProvider(coin)), + ), + width: 20, + height: 20, + ), ), ); } diff --git a/lib/widgets/wallet_info_row/wallet_info_row.dart b/lib/widgets/wallet_info_row/wallet_info_row.dart index 5bb51e2e6..4a3a35b57 100644 --- a/lib/widgets/wallet_info_row/wallet_info_row.dart +++ b/lib/widgets/wallet_info_row/wallet_info_row.dart @@ -1,24 +1,28 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/models/isar/models/ethereum/eth_contract.dart'; +import 'package:stackwallet/pages/token_view/sub_widgets/token_summary.dart'; +import 'package:stackwallet/providers/db/main_db_provider.dart'; import 'package:stackwallet/providers/providers.dart'; -import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/text_styles.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; -import 'package:stackwallet/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance_future.dart'; +import 'package:stackwallet/widgets/custom_buttons/blue_text_button.dart'; +import 'package:stackwallet/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance.dart'; import 'package:stackwallet/widgets/wallet_info_row/sub_widgets/wallet_info_row_coin_icon.dart'; class WalletInfoRow extends ConsumerWidget { const WalletInfoRow({ Key? key, required this.walletId, - this.onPressed, + this.onPressedDesktop, + this.contractAddress, this.padding = const EdgeInsets.all(0), }) : super(key: key); final String walletId; - final VoidCallback? onPressed; + final String? contractAddress; + final VoidCallback? onPressedDesktop; final EdgeInsets padding; @override @@ -27,69 +31,93 @@ class WalletInfoRow extends ConsumerWidget { .watch(walletsChangeNotifierProvider.notifier) .getManagerProvider(walletId)); + EthContract? contract; + if (contractAddress != null) { + contract = ref.watch(mainDBProvider + .select((value) => value.getEthContractSync(contractAddress!))); + } + if (Util.isDesktop) { - return MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: onPressed, - child: Padding( - padding: padding, - child: Container( - color: Colors.transparent, - child: Row( - children: [ - Expanded( - flex: 4, - child: Row( - children: [ - WalletInfoCoinIcon(coin: manager.coin), - const SizedBox( - width: 12, - ), - Text( - manager.walletName, - style: STextStyles.desktopTextExtraSmall(context) - .copyWith( - color: Theme.of(context) - .extension()! - .textDark, + return Padding( + padding: padding, + child: Container( + color: Colors.transparent, + child: Row( + children: [ + Expanded( + flex: 3, + child: Row( + children: [ + WalletInfoCoinIcon( + coin: manager.coin, + contractAddress: contractAddress, + ), + const SizedBox( + width: 12, + ), + contract != null + ? Row( + children: [ + Text( + contract.name, + style: + STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), + ), + const SizedBox( + width: 4, + ), + CoinTickerTag( + walletId: walletId, + ), + ], + ) + : Text( + manager.walletName, + style: STextStyles.desktopTextExtraSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .textDark, + ), ), - ), - ], - ), - ), - Expanded( - flex: 4, - child: WalletInfoRowBalanceFuture( - walletId: walletId, - ), - ), - Expanded( - flex: 6, - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - SvgPicture.asset( - Assets.svg.chevronRight, - width: 20, - height: 20, - color: Theme.of(context) - .extension()! - .textSubtitle1, - ) - ], - ), - ) - ], + ], + ), ), - ), + Expanded( + flex: 4, + child: WalletInfoRowBalance( + walletId: walletId, + contractAddress: contractAddress, + ), + ), + Expanded( + flex: 2, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + CustomTextButton( + text: "Open wallet", + onTap: onPressedDesktop, + ), + ], + ), + ) + ], ), ), ); } else { return Row( children: [ - WalletInfoCoinIcon(coin: manager.coin), + WalletInfoCoinIcon( + coin: manager.coin, + contractAddress: contractAddress, + ), const SizedBox( width: 12, ), @@ -98,14 +126,32 @@ class WalletInfoRow extends ConsumerWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - manager.walletName, - style: STextStyles.titleBold12(context), - ), + contract != null + ? Row( + children: [ + Text( + contract.name, + style: STextStyles.titleBold12(context), + ), + const SizedBox( + width: 4, + ), + CoinTickerTag( + walletId: walletId, + ), + ], + ) + : Text( + manager.walletName, + style: STextStyles.titleBold12(context), + ), const SizedBox( height: 2, ), - WalletInfoRowBalanceFuture(walletId: walletId), + WalletInfoRowBalance( + walletId: walletId, + contractAddress: contractAddress, + ), ], ), ), diff --git a/lib/widgets/wallet_navigation_bar/components/icons/buy_nav_icon.dart b/lib/widgets/wallet_navigation_bar/components/icons/buy_nav_icon.dart new file mode 100644 index 000000000..5f0e30df8 --- /dev/null +++ b/lib/widgets/wallet_navigation_bar/components/icons/buy_nav_icon.dart @@ -0,0 +1,25 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; + +class BuyNavIcon extends ConsumerWidget { + const BuyNavIcon({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return SvgPicture.file( + File( + ref.watch( + themeProvider.select( + (value) => value.assets.buy, + ), + ), + ), + width: 24, + height: 24, + ); + } +} diff --git a/lib/widgets/wallet_navigation_bar/components/icons/coin_control_nav_icon.dart b/lib/widgets/wallet_navigation_bar/components/icons/coin_control_nav_icon.dart new file mode 100644 index 000000000..3d8e2cea4 --- /dev/null +++ b/lib/widgets/wallet_navigation_bar/components/icons/coin_control_nav_icon.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; + +class CoinControlNavIcon extends StatelessWidget { + const CoinControlNavIcon({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return SvgPicture.asset( + Assets.svg.coinControl.gamePad, + height: 20, + width: 20, + color: Theme.of(context).extension()!.bottomNavIconIcon, + ); + } +} diff --git a/lib/widgets/wallet_navigation_bar/components/icons/exchange_nav_icon.dart b/lib/widgets/wallet_navigation_bar/components/icons/exchange_nav_icon.dart new file mode 100644 index 000000000..223703334 --- /dev/null +++ b/lib/widgets/wallet_navigation_bar/components/icons/exchange_nav_icon.dart @@ -0,0 +1,25 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/themes/theme_providers.dart'; + +class ExchangeNavIcon extends ConsumerWidget { + const ExchangeNavIcon({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return SvgPicture.file( + File( + ref.watch( + themeProvider.select( + (value) => value.assets.exchange, + ), + ), + ), + width: 24, + height: 24, + ); + } +} diff --git a/lib/widgets/wallet_navigation_bar/components/icons/paynym_nav_icon.dart b/lib/widgets/wallet_navigation_bar/components/icons/paynym_nav_icon.dart new file mode 100644 index 000000000..63ca7617e --- /dev/null +++ b/lib/widgets/wallet_navigation_bar/components/icons/paynym_nav_icon.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; + +class PaynymNavIcon extends StatelessWidget { + const PaynymNavIcon({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return SvgPicture.asset( + Assets.svg.robotHead, + height: 20, + width: 20, + color: Theme.of(context).extension()!.bottomNavIconIcon, + ); + } +} diff --git a/lib/widgets/wallet_navigation_bar/components/icons/receive_nav_icon.dart b/lib/widgets/wallet_navigation_bar/components/icons/receive_nav_icon.dart new file mode 100644 index 000000000..4690382c2 --- /dev/null +++ b/lib/widgets/wallet_navigation_bar/components/icons/receive_nav_icon.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; + +class ReceiveNavIcon extends StatelessWidget { + const ReceiveNavIcon({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .bottomNavIconIcon + .withOpacity(0.4), + borderRadius: BorderRadius.circular( + 24, + ), + ), + child: Padding( + padding: const EdgeInsets.all(6.0), + child: SvgPicture.asset( + Assets.svg.arrowDownLeft, + width: 12, + height: 12, + color: Theme.of(context).extension()!.bottomNavIconIcon, + ), + ), + ); + } +} diff --git a/lib/widgets/wallet_navigation_bar/components/icons/send_nav_icon.dart b/lib/widgets/wallet_navigation_bar/components/icons/send_nav_icon.dart new file mode 100644 index 000000000..3df04f4ee --- /dev/null +++ b/lib/widgets/wallet_navigation_bar/components/icons/send_nav_icon.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; + +class SendNavIcon extends StatelessWidget { + const SendNavIcon({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .bottomNavIconIcon + .withOpacity(0.4), + borderRadius: BorderRadius.circular( + 24, + ), + ), + child: Padding( + padding: const EdgeInsets.all(6.0), + child: SvgPicture.asset( + Assets.svg.arrowUpRight, + width: 12, + height: 12, + color: Theme.of(context).extension()!.bottomNavIconIcon, + ), + ), + ); + } +} diff --git a/lib/widgets/wallet_navigation_bar/components/icons/whirlpool_nav_icon.dart b/lib/widgets/wallet_navigation_bar/components/icons/whirlpool_nav_icon.dart new file mode 100644 index 000000000..9a3eff747 --- /dev/null +++ b/lib/widgets/wallet_navigation_bar/components/icons/whirlpool_nav_icon.dart @@ -0,0 +1,18 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; + +class WhirlpoolNavIcon extends StatelessWidget { + const WhirlpoolNavIcon({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return SvgPicture.asset( + Assets.svg.whirlPool, + height: 20, + width: 20, + color: Theme.of(context).extension()!.bottomNavIconIcon, + ); + } +} diff --git a/lib/widgets/wallet_navigation_bar/components/wallet_navigation_bar_item.dart b/lib/widgets/wallet_navigation_bar/components/wallet_navigation_bar_item.dart new file mode 100644 index 000000000..8fed3c4e4 --- /dev/null +++ b/lib/widgets/wallet_navigation_bar/components/wallet_navigation_bar_item.dart @@ -0,0 +1,133 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/rounded_container.dart'; +import 'package:stackwallet/widgets/wallet_navigation_bar/wallet_navigation_bar.dart'; + +class WalletNavigationBarItemData { + WalletNavigationBarItemData({ + required this.icon, + required this.label, + required this.onTap, + this.isMore = false, + this.overrideText, + }); + + final Widget icon; + final String? label; + final VoidCallback? onTap; + final bool isMore; + final Widget? overrideText; +} + +class WalletNavigationBarItem extends ConsumerWidget { + const WalletNavigationBarItem({ + Key? key, + required this.data, + required this.disableDuration, + }) : super(key: key); + + final WalletNavigationBarItemData data; + final Duration disableDuration; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return GestureDetector( + onTap: data.isMore || !ref.watch(walletNavBarMore.state).state + ? data.onTap + : null, + child: RoundedContainer( + color: Colors.transparent, + padding: const EdgeInsets.all(0), + radiusMultiplier: 2, + child: AnimatedOpacity( + opacity: + data.isMore || !ref.watch(walletNavBarMore.state).state ? 1 : 0.2, + duration: disableDuration, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: ConstrainedBox( + constraints: const BoxConstraints( + maxHeight: 45, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + width: 24, + height: 24, + child: Center( + child: data.icon, + ), + ), + const Spacer(), + data.overrideText ?? + Text( + data.label ?? "", + style: STextStyles.buttonSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .bottomNavText, + ), + ), + ], + ), + ), + ), + ), + ), + ); + } +} + +class WalletNavigationBarMoreItem extends ConsumerWidget { + const WalletNavigationBarMoreItem({ + Key? key, + required this.data, + }) : super(key: key); + + final WalletNavigationBarItemData data; + + @override + Widget build(BuildContext context, WidgetRef ref) { + return GestureDetector( + onTap: () { + data.onTap?.call(); + ref.read(walletNavBarMore.state).state = false; + }, + child: Material( + color: Colors.transparent, + child: RoundedContainer( + color: Theme.of(context).extension()!.bottomNavBack, + radiusMultiplier: 100, + padding: const EdgeInsets.symmetric( + vertical: 16, + horizontal: 30, + ), + child: Row( + children: [ + Expanded( + child: Text( + data.label ?? "", + textAlign: TextAlign.center, + style: STextStyles.buttonSmall(context).copyWith( + color: Theme.of(context) + .extension()! + .bottomNavText, + ), + ), + ), + const SizedBox( + width: 10, + ), + data.icon, + ], + ), + ), + ), + ); + } +} diff --git a/lib/widgets/wallet_navigation_bar/wallet_navigation_bar.dart b/lib/widgets/wallet_navigation_bar/wallet_navigation_bar.dart new file mode 100644 index 000000000..f389b2c85 --- /dev/null +++ b/lib/widgets/wallet_navigation_bar/wallet_navigation_bar.dart @@ -0,0 +1,237 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/assets.dart'; +import 'package:stackwallet/utilities/text_styles.dart'; +import 'package:stackwallet/widgets/conditional_parent.dart'; +import 'package:stackwallet/widgets/wallet_navigation_bar/components/wallet_navigation_bar_item.dart'; + +final walletNavBarMore = StateProvider.autoDispose((ref) => false); + +class WalletNavigationBar extends ConsumerStatefulWidget { + const WalletNavigationBar({ + Key? key, + required this.items, + required this.moreItems, + }) : super(key: key); + + final List items; + final List moreItems; + + @override + ConsumerState createState() => + _WalletNavigationBarState(); +} + +class _WalletNavigationBarState extends ConsumerState { + static const double horizontalPadding = 16; + + final _moreDuration = const Duration(milliseconds: 200); + + void _onMorePressed() { + ref.read(walletNavBarMore.state).state = + !ref.read(walletNavBarMore.state).state; + } + + @override + Widget build(BuildContext context) { + final width = MediaQuery.of(context).size.width - 40; + + final hasMore = widget.moreItems.isNotEmpty; + final buttonCount = widget.items.length + (hasMore ? 1 : 0); + + return Stack( + alignment: Alignment.bottomCenter, + children: [ + IgnorePointer( + ignoring: !ref.read(walletNavBarMore.state).state, + child: GestureDetector( + onTap: () { + if (ref.read(walletNavBarMore.state).state) { + ref.read(walletNavBarMore.state).state = false; + } + }, + child: AnimatedOpacity( + opacity: ref.watch(walletNavBarMore.state).state ? 1 : 0, + duration: _moreDuration, + child: Container( + color: Colors.black.withOpacity(0.7), + ), + ), + ), + ), + Padding( + padding: const EdgeInsets.only( + left: horizontalPadding, + right: horizontalPadding, + bottom: horizontalPadding, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + AnimatedScale( + scale: ref.watch(walletNavBarMore.state).state ? 1 : 0, + duration: _moreDuration, + alignment: const Alignment( + 0.5, + 1.0, + ), + child: AnimatedOpacity( + opacity: ref.watch(walletNavBarMore.state).state ? 1 : 0, + duration: _moreDuration, + child: IntrinsicWidth( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + ...widget.moreItems.map( + (e) { + return Column( + children: [ + WalletNavigationBarMoreItem(data: e), + const SizedBox( + height: 8, + ), + ], + ); + }, + ), + ], + ), + ), + ), + ), + Material( + color: Colors.transparent, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + 1000, + ), + ), + child: Container( + decoration: BoxDecoration( + color: Theme.of(context) + .extension()! + .bottomNavBack, + boxShadow: [ + Theme.of(context) + .extension()! + .standardBoxShadow + ], + borderRadius: BorderRadius.circular( + 1000, + ), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 6, + horizontal: 20, + ), + // child: IntrinsicWidth( + child: ConditionalParent( + condition: buttonCount > 4, + builder: (child) => SizedBox( + width: width * 0.9, + child: child, + ), + child: ConditionalParent( + condition: buttonCount <= 4, + builder: (child) => SizedBox( + width: width * 0.2 * buttonCount, + child: child, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ...widget.items.map( + (e) => Expanded( + child: WalletNavigationBarItem( + data: e, + disableDuration: _moreDuration, + ), + ), + ), + if (hasMore) + Expanded( + child: WalletNavigationBarItem( + data: WalletNavigationBarItemData( + icon: AnimatedCrossFade( + firstChild: SvgPicture.asset( + Assets.svg.bars, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .bottomNavIconIcon, + ), + secondChild: SvgPicture.asset( + Assets.svg.bars, + width: 20, + height: 20, + color: Theme.of(context) + .extension()! + .bottomNavIconIconHighlighted, + ), + crossFadeState: ref + .watch(walletNavBarMore.state) + .state + ? CrossFadeState.showSecond + : CrossFadeState.showFirst, + duration: _moreDuration, + ), + overrideText: AnimatedCrossFade( + firstChild: Text( + "More", + style: + STextStyles.buttonSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .bottomNavText, + ), + ), + secondChild: Text( + "More", + style: + STextStyles.buttonSmall(context) + .copyWith( + color: Theme.of(context) + .extension()! + .bottomNavIconIconHighlighted, + ), + ), + crossFadeState: ref + .watch(walletNavBarMore.state) + .state + ? CrossFadeState.showSecond + : CrossFadeState.showFirst, + duration: _moreDuration, + ), + label: null, + isMore: true, + onTap: _onMorePressed, + ), + disableDuration: _moreDuration, + ), + ), + ], + ), + ), + ), + ), + ), + ), + ], + ), + ], + ), + ), + ], + ); + } +} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index fe1e41d19..b174939fa 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,7 @@ #include "generated_plugin_registrant.h" +#include #include #include #include @@ -16,6 +17,9 @@ #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) desktop_drop_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin"); + desktop_drop_plugin_register_with_registrar(desktop_drop_registrar); g_autoptr(FlPluginRegistrar) devicelocale_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "DevicelocalePlugin"); devicelocale_plugin_register_with_registrar(devicelocale_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index ba2cb50f2..38eed0699 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + desktop_drop devicelocale flutter_libepiccash flutter_libmonero diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 59063791f..570461de7 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,6 +6,7 @@ import FlutterMacOS import Foundation import connectivity_plus_macos +import desktop_drop import device_info_plus import devicelocale import flutter_libepiccash @@ -13,9 +14,9 @@ import flutter_local_notifications import flutter_secure_storage_macos import isar_flutter_libs import package_info_plus_macos -import path_provider_macos -import share_plus_macos -import shared_preferences_macos +import path_provider_foundation +import share_plus +import shared_preferences_foundation import stack_wallet_backup import url_launcher_macos import wakelock_macos @@ -23,6 +24,7 @@ import window_size func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin")) + DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DevicelocalePlugin.register(with: registry.registrar(forPlugin: "DevicelocalePlugin")) FlutterLibepiccashPlugin.register(with: registry.registrar(forPlugin: "FlutterLibepiccashPlugin")) diff --git a/pubspec.lock b/pubspec.lock index e8f875d35..600a92d67 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,72 +5,82 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - url: "https://pub.dartlang.org" + sha256: "4897882604d919befd350648c7f91926a9d5de99e67b455bf0917cc2362f4bb8" + url: "https://pub.dev" source: hosted version: "47.0.0" analyzer: dependency: "direct dev" description: name: analyzer - url: "https://pub.dartlang.org" + sha256: "690e335554a8385bc9d787117d9eb52c0c03ee207a607e593de3c9d71b1cfe80" + url: "https://pub.dev" source: hosted version: "4.7.0" animations: dependency: "direct main" description: name: animations - url: "https://pub.dartlang.org" + sha256: fe8a6bdca435f718bb1dc8a11661b2c22504c6da40ef934cee8327ed77934164 + url: "https://pub.dev" source: hosted version: "2.0.7" another_flushbar: dependency: "direct main" description: name: another_flushbar - url: "https://pub.dartlang.org" + sha256: fa09f8a4ca582c417669b7b1d0e85ce65bd074d80bb0dcbb1302ad1b22bdc3ef + url: "https://pub.dev" source: hosted version: "1.12.29" app_settings: dependency: "direct main" description: name: app_settings - url: "https://pub.dartlang.org" + sha256: "66715a323ac36d6c8201035ba678777c0d2ea869e4d7064300d95af10c3bb8cb" + url: "https://pub.dev" source: hosted - version: "4.1.8" + version: "4.2.0" archive: - dependency: transitive + dependency: "direct main" description: name: archive - url: "https://pub.dartlang.org" + sha256: "80e5141fafcb3361653ce308776cfd7d45e6e9fbb429e14eec571382c0c5fecb" + url: "https://pub.dev" source: hosted - version: "3.1.11" + version: "3.3.2" args: dependency: transitive description: name: args - url: "https://pub.dartlang.org" + sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440" + url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.0" asn1lib: dependency: transitive description: name: asn1lib - url: "https://pub.dartlang.org" + sha256: ab96a1cb3beeccf8145c52e449233fe68364c9641623acd3adad66f8184f1039 + url: "https://pub.dev" source: hosted version: "1.4.0" async: - dependency: transitive + dependency: "direct main" description: name: async - url: "https://pub.dartlang.org" + sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + url: "https://pub.dev" source: hosted - version: "2.8.2" + version: "2.10.0" barcode_scan2: dependency: "direct main" description: name: barcode_scan2 - url: "https://pub.dartlang.org" + sha256: f9af9252b8f3f5fa446f5456fd45f8871d09f883d8389a1d608b39231bfbc3fa + url: "https://pub.dev" source: hosted - version: "4.2.1" + version: "4.2.3" bech32: dependency: "direct main" description: @@ -84,7 +94,8 @@ packages: dependency: "direct main" description: name: bip32 - url: "https://pub.dartlang.org" + sha256: "54787cd7a111e9d37394aabbf53d1fc5e2e0e0af2cd01c459147a97c0e3f8a97" + url: "https://pub.dev" source: hosted version: "2.0.0" bip39: @@ -96,13 +107,22 @@ packages: url: "https://github.com/cypherstack/stack-bip39.git" source: git version: "1.0.6" + bip47: + dependency: "direct main" + description: + path: "." + ref: "38847255d035c0f6ec5bc93d19130ec804cf90e9" + resolved-ref: "38847255d035c0f6ec5bc93d19130ec804cf90e9" + url: "https://github.com/cypherstack/bip47.git" + source: git + version: "2.0.0" bitbox: dependency: "direct main" description: path: "." - ref: ea65073efbaf395a5557e8cd7bd72f195cd7eb11 - resolved-ref: ea65073efbaf395a5557e8cd7bd72f195cd7eb11 - url: "https://github.com/Quppy/bitbox-flutter.git" + ref: "50bf29957514a5712466ba37590a851212a244bf" + resolved-ref: "50bf29957514a5712466ba37590a851212a244bf" + url: "https://github.com/PiRK/bitbox-flutter.git" source: git version: "1.0.1" bitcoindart: @@ -118,203 +138,224 @@ packages: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" bs58check: dependency: "direct main" description: name: bs58check - url: "https://pub.dartlang.org" + sha256: c4a164d42b25c2f6bc88a8beccb9fc7d01440f3c60ba23663a20a70faf484ea9 + url: "https://pub.dev" source: hosted version: "1.0.2" build: dependency: transitive description: name: build - url: "https://pub.dartlang.org" + sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + url: "https://pub.dev" source: hosted version: "2.3.1" build_config: dependency: transitive description: name: build_config - url: "https://pub.dartlang.org" + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" source: hosted version: "1.1.1" build_daemon: dependency: transitive description: name: build_daemon - url: "https://pub.dartlang.org" + sha256: "6bc5544ea6ce4428266e7ea680e945c68806c4aae2da0eb5e9ccf38df8d6acbf" + url: "https://pub.dev" source: hosted version: "3.1.0" build_resolvers: dependency: transitive description: name: build_resolvers - url: "https://pub.dartlang.org" + sha256: "687cf90a3951affac1bd5f9ecb5e3e90b60487f3d9cdc359bb310f8876bb02a6" + url: "https://pub.dev" source: hosted version: "2.0.10" build_runner: dependency: "direct dev" description: name: build_runner - url: "https://pub.dartlang.org" + sha256: "93f05c041932674be039b0a2323d6cf57e5f2bbf884a3c0382f9e53fc45ebace" + url: "https://pub.dev" source: hosted version: "2.3.0" build_runner_core: dependency: transitive description: name: build_runner_core - url: "https://pub.dartlang.org" + sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292" + url: "https://pub.dev" source: hosted version: "7.2.7" built_collection: dependency: transitive description: name: built_collection - url: "https://pub.dartlang.org" + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" source: hosted version: "5.1.1" built_value: dependency: transitive description: name: built_value - url: "https://pub.dartlang.org" + sha256: "169565c8ad06adb760c3645bf71f00bff161b00002cace266cad42c5d22a7725" + url: "https://pub.dev" source: hosted - version: "8.4.2" + version: "8.4.3" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + url: "https://pub.dev" source: hosted - version: "1.2.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" + version: "1.2.1" checked_yaml: dependency: transitive description: name: checked_yaml - url: "https://pub.dartlang.org" + sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311" + url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" cli_util: dependency: transitive description: name: cli_util - url: "https://pub.dartlang.org" + sha256: "66f86e916d285c1a93d3b79587d94bd71984a66aac4ff74e524cfa7877f1395c" + url: "https://pub.dev" source: hosted version: "0.3.5" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" code_builder: dependency: transitive description: name: code_builder - url: "https://pub.dartlang.org" + sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe" + url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.4.0" collection: dependency: transitive description: name: collection - url: "https://pub.dartlang.org" + sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" connectivity_plus: dependency: "direct main" description: name: connectivity_plus - url: "https://pub.dartlang.org" + sha256: e18bbd0243342ceca6fb33718fd2737c468c1a881ad7fed4b0a47258b5838d9d + url: "https://pub.dev" source: hosted version: "2.3.6+1" connectivity_plus_linux: dependency: transitive description: name: connectivity_plus_linux - url: "https://pub.dartlang.org" + sha256: "3caf859d001f10407b8e48134c761483e4495ae38094ffcca97193f6c271f5e2" + url: "https://pub.dev" source: hosted version: "1.3.1" connectivity_plus_macos: dependency: transitive description: name: connectivity_plus_macos - url: "https://pub.dartlang.org" + sha256: "488d2de1e47e1224ad486e501b20b088686ba1f4ee9c4420ecbc3b9824f0b920" + url: "https://pub.dev" source: hosted version: "1.2.6" connectivity_plus_platform_interface: dependency: transitive description: name: connectivity_plus_platform_interface - url: "https://pub.dartlang.org" + sha256: cf1d1c28f4416f8c654d7dc3cd638ec586076255d407cef3ddbdaf178272a71a + url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.2.4" connectivity_plus_web: dependency: transitive description: name: connectivity_plus_web - url: "https://pub.dartlang.org" + sha256: "81332be1b4baf8898fed17bb4fdef27abb7c6fd990bf98c54fd978478adf2f1a" + url: "https://pub.dev" source: hosted version: "1.2.5" connectivity_plus_windows: dependency: transitive description: name: connectivity_plus_windows - url: "https://pub.dartlang.org" + sha256: "535b0404b4d5605c4dd8453d67e5d6d2ea0dd36e3b477f50f31af51b0aeab9dd" + url: "https://pub.dev" source: hosted version: "1.2.2" convert: dependency: transitive description: name: convert - url: "https://pub.dartlang.org" + sha256: "196284f26f69444b7f5c50692b55ec25da86d9e500451dc09333bf2e3ad69259" + url: "https://pub.dev" source: hosted version: "3.0.2" coverage: dependency: transitive description: name: coverage - url: "https://pub.dartlang.org" + sha256: "525ac94733f9ce82507a050bfd62ad89eb1dcbc56308e1e2e17ab11abeee4a75" + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.5.0" cross_file: dependency: transitive description: name: cross_file - url: "https://pub.dartlang.org" + sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9" + url: "https://pub.dev" source: hosted - version: "0.3.3+2" + version: "0.3.3+4" crypto: dependency: "direct main" description: name: crypto - url: "https://pub.dartlang.org" + sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + url: "https://pub.dev" source: hosted version: "3.0.2" cryptography: dependency: transitive description: name: cryptography - url: "https://pub.dartlang.org" + sha256: ffd770340e5a48f57e473c42d9036a773c43b396e80b41a2dd164ffaf53f57a4 + url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "2.1.1" csslib: dependency: transitive description: name: csslib - url: "https://pub.dartlang.org" + sha256: b36c7f7e24c0bdf1bf9a3da461c837d1de64b9f8beb190c9011d8c72a3dfd745 + url: "https://pub.dev" source: hosted version: "0.17.2" cw_core: @@ -345,137 +386,204 @@ packages: relative: true source: path version: "0.0.1" + dart_base_x: + dependency: transitive + description: + name: dart_base_x + sha256: c8af4f6a6518daab4aa85bb27ee148221644e80446bb44117052b6f4674cdb23 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + dart_bs58: + dependency: "direct main" + description: + name: dart_bs58 + sha256: e2fff08fca810d5215f6fca3ea713d8a4a9728aaf1b1658472863b2de7377234 + url: "https://pub.dev" + source: hosted + version: "1.0.1" + dart_bs58check: + dependency: "direct main" + description: + name: dart_bs58check + sha256: "4284e606795a18c1df5a955928bdc4e1b6f908da7ab0e87f49db51b3774e9e6c" + url: "https://pub.dev" + source: hosted + version: "3.0.2" dart_numerics: dependency: "direct main" description: name: dart_numerics - url: "https://pub.dartlang.org" + sha256: "47408d4890551636204851325e5649bf1a1616ebc325184c36722a1716cbaba4" + url: "https://pub.dev" source: hosted version: "0.0.6" dart_style: dependency: transitive description: name: dart_style - url: "https://pub.dartlang.org" + sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4" + url: "https://pub.dev" source: hosted version: "2.2.4" dartx: dependency: transitive description: name: dartx - url: "https://pub.dartlang.org" + sha256: "45d7176701f16c5a5e00a4798791c1964bc231491b879369c818dd9a9c764871" + url: "https://pub.dev" source: hosted version: "1.1.0" dbus: dependency: transitive description: name: dbus - url: "https://pub.dartlang.org" + sha256: "6f07cba3f7b3448d42d015bfd3d53fe12e5b36da2423f23838efc1d5fb31a263" + url: "https://pub.dev" source: hosted version: "0.7.8" decimal: dependency: "direct main" description: name: decimal - url: "https://pub.dartlang.org" + sha256: eece91944f523657c75a3a008a90ec7f7eb3986191153a78570c7d0ac8ef3d01 + url: "https://pub.dev" source: hosted version: "2.3.2" dependency_validator: dependency: "direct dev" description: name: dependency_validator - url: "https://pub.dartlang.org" + sha256: "08349175533ed0bd06eb9b6043cde66c45b2bfc7ebc222a7542cdb1324f1bf03" + url: "https://pub.dev" source: hosted version: "3.2.2" + desktop_drop: + dependency: "direct main" + description: + name: desktop_drop + sha256: "4ca4d960f4b11c032e9adfd2a0a8ac615bc3fddb4cbe73dcf840dd8077582186" + url: "https://pub.dev" + source: hosted + version: "0.4.1" device_info_plus: dependency: "direct main" description: name: device_info_plus - url: "https://pub.dartlang.org" + sha256: "8d99246809e63d93e4e68fade79495d81f445ad735bde2b129b19c0adddcaf1a" + url: "https://pub.dev" source: hosted version: "7.0.1" device_info_plus_platform_interface: dependency: transitive description: name: device_info_plus_platform_interface - url: "https://pub.dartlang.org" + sha256: "8b8b65e598b84fdb82c26cf9b3f05a6c4978636e99b0c070bae5905a24728199" + url: "https://pub.dev" source: hosted version: "6.0.1" devicelocale: dependency: "direct main" description: name: devicelocale - url: "https://pub.dartlang.org" + sha256: "5fd46b8c2a987b1c14b1551433be1974dbca5ec6ab00e7d53288b5197a21863e" + url: "https://pub.dev" source: hosted version: "0.5.5" dropdown_button2: dependency: "direct main" description: name: dropdown_button2 - url: "https://pub.dartlang.org" + sha256: "1a00301a1ec87666b1f1938f373865e3f5fb3056e4e36e1b3c2c0dc0c11f4737" + url: "https://pub.dev" source: hosted version: "1.7.2" emojis: dependency: "direct main" description: name: emojis - url: "https://pub.dartlang.org" + sha256: "2e4d847c3f1e2670f30dc355909ce6fa7808b4e626c34a4dd503a360995a38bf" + url: "https://pub.dev" source: hosted version: "0.9.9" encrypt: dependency: transitive description: name: encrypt - url: "https://pub.dartlang.org" + sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb" + url: "https://pub.dev" source: hosted version: "5.0.1" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" + ethereum_addresses: + dependency: "direct main" + description: + name: ethereum_addresses + sha256: e6ba01d44ecb9c5634367b017d6e94598fc937be8b28fc406d0e51ed6e9513dd + url: "https://pub.dev" + source: hosted + version: "1.0.2" event_bus: dependency: "direct main" description: name: event_bus - url: "https://pub.dartlang.org" + sha256: "44baa799834f4c803921873e7446a2add0f3efa45e101a054b1f0ab9b95f8edc" + url: "https://pub.dev" source: hosted version: "2.0.0" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.dartlang.org" + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.3.1" ffi: dependency: "direct main" description: name: ffi - url: "https://pub.dartlang.org" + sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978 + url: "https://pub.dev" source: hosted version: "2.0.1" file: dependency: transitive description: name: file - url: "https://pub.dartlang.org" + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" source: hosted - version: "6.1.2" + version: "6.1.4" file_picker: dependency: "direct main" description: name: file_picker - url: "https://pub.dartlang.org" + sha256: d090ae03df98b0247b82e5928f44d1b959867049d18d73635e2e0bc3f49542b9 + url: "https://pub.dev" source: hosted - version: "5.2.3" + version: "5.2.5" fixnum: dependency: transitive description: name: fixnum - url: "https://pub.dartlang.org" + sha256: "04be3e934c52e082558cc9ee21f42f5c1cd7a1262f4c63cd0357c08d5bba81ec" + url: "https://pub.dev" source: hosted version: "1.0.1" flare_flutter: dependency: "direct main" description: name: flare_flutter - url: "https://pub.dartlang.org" + sha256: "99d63c60f00fac81249ce6410ee015d7b125c63d8278a30da81edf3317a1f6a0" + url: "https://pub.dev" source: hosted version: "3.0.2" flutter: @@ -492,14 +600,16 @@ packages: dependency: "direct main" description: name: flutter_feather_icons - url: "https://pub.dartlang.org" + sha256: b33b9c276fc8108254632da6644cf01f71af6c17fbfb26e136a86945f5ff9b67 + url: "https://pub.dev" source: hosted version: "2.0.0+1" flutter_launcher_icons: dependency: "direct dev" description: name: flutter_launcher_icons - url: "https://pub.dartlang.org" + sha256: ce0e501cfc258907842238e4ca605e74b7fd1cdf04b3b43e86c43f3e40a1592c + url: "https://pub.dev" source: hosted version: "0.11.0" flutter_libepiccash: @@ -520,119 +630,136 @@ packages: dependency: "direct dev" description: name: flutter_lints - url: "https://pub.dartlang.org" + sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + url: "https://pub.dev" source: hosted version: "2.0.1" flutter_local_notifications: dependency: "direct main" description: name: flutter_local_notifications - url: "https://pub.dartlang.org" + sha256: "57d0012730780fe137260dd180e072c18a73fbeeb924cdc029c18aaa0f338d64" + url: "https://pub.dev" source: hosted version: "9.9.1" flutter_local_notifications_linux: dependency: transitive description: name: flutter_local_notifications_linux - url: "https://pub.dartlang.org" + sha256: b472bfc173791b59ede323661eae20f7fff0b6908fea33dd720a6ef5d576bae8 + url: "https://pub.dev" source: hosted version: "0.5.1" flutter_local_notifications_platform_interface: dependency: transitive description: name: flutter_local_notifications_platform_interface - url: "https://pub.dartlang.org" + sha256: "21bceee103a66a53b30ea9daf677f990e5b9e89b62f222e60dd241cd08d63d3a" + url: "https://pub.dev" source: hosted version: "5.0.0" flutter_mobx: dependency: transitive description: name: flutter_mobx - url: "https://pub.dartlang.org" + sha256: "0da4add0016387a7bf309a0d0c41d36c6b3ae25ed7a176409267f166509e723e" + url: "https://pub.dev" source: hosted version: "2.0.6+5" flutter_native_splash: dependency: "direct main" description: name: flutter_native_splash - url: "https://pub.dartlang.org" + sha256: "6777a3abb974021a39b5fdd2d46a03ca390e03903b6351f21d10e7ecc969f12d" + url: "https://pub.dev" source: hosted - version: "2.2.9" + version: "2.2.16" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle - url: "https://pub.dartlang.org" + sha256: "60fc7b78455b94e6de2333d2f95196d32cf5c22f4b0b0520a628804cb463503b" + url: "https://pub.dev" source: hosted version: "2.0.7" flutter_riverpod: dependency: "direct main" description: name: flutter_riverpod - url: "https://pub.dartlang.org" + sha256: d84e180f039a6b963e610d2e4435641fdfe8f12437e8770e963632e05af16d80 + url: "https://pub.dev" source: hosted version: "1.0.4" flutter_rounded_date_picker: dependency: "direct main" description: name: flutter_rounded_date_picker - url: "https://pub.dartlang.org" + sha256: "369f7c63c1518d24c63f5da889ea9a6fb9a0a6f105ba9d22ccbba7665475784f" + url: "https://pub.dev" source: hosted version: "3.0.2" flutter_secure_storage: dependency: "direct main" description: name: flutter_secure_storage - url: "https://pub.dartlang.org" + sha256: "5abe3d5c25ab435e48c47fb61bac25606062a305fac637c2f020e25abd30014a" + url: "https://pub.dev" source: hosted version: "5.1.2" flutter_secure_storage_linux: dependency: transitive description: name: flutter_secure_storage_linux - url: "https://pub.dartlang.org" + sha256: "0912ae29a572230ad52d8a4697e5518d7f0f429052fd51df7e5a7952c7efe2a3" + url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.3" flutter_secure_storage_macos: dependency: transitive description: name: flutter_secure_storage_macos - url: "https://pub.dartlang.org" + sha256: "388f76fd0f093e7415a39ec4c169ae7cceeee6d9f9ba529d788a13f2be4de7bd" + url: "https://pub.dev" source: hosted version: "1.1.2" flutter_secure_storage_platform_interface: dependency: transitive description: name: flutter_secure_storage_platform_interface - url: "https://pub.dartlang.org" + sha256: b3773190e385a3c8a382007893d678ae95462b3c2279e987b55d140d3b0cb81b + url: "https://pub.dev" source: hosted version: "1.0.1" flutter_secure_storage_web: dependency: transitive description: name: flutter_secure_storage_web - url: "https://pub.dartlang.org" + sha256: "42938e70d4b872e856e678c423cc0e9065d7d294f45bc41fc1981a4eb4beaffe" + url: "https://pub.dev" source: hosted version: "1.1.1" flutter_secure_storage_windows: dependency: transitive description: name: flutter_secure_storage_windows - url: "https://pub.dartlang.org" + sha256: ca89c8059cf439985aa83c59619b3674c7ef6cc2e86943d169a7369d6a69cab5 + url: "https://pub.dev" source: hosted version: "1.1.3" flutter_spinkit: dependency: "direct main" description: name: flutter_spinkit - url: "https://pub.dartlang.org" + sha256: "77a2117c0517ff909221f3160b8eb20052ab5216107581168af574ac1f05dff8" + url: "https://pub.dev" source: hosted version: "5.1.0" flutter_svg: dependency: "direct main" description: name: flutter_svg - url: "https://pub.dartlang.org" + sha256: "6ff9fa12892ae074092de2fa6a9938fb21dbabfdaa2ff57dc697ff912fc8d4b2" + url: "https://pub.dev" source: hosted version: "1.1.6" flutter_test: @@ -649,7 +776,8 @@ packages: dependency: transitive description: name: frontend_server_client - url: "https://pub.dartlang.org" + sha256: "4f4a162323c86ffc1245765cfe138872b8f069deb42f7dbb36115fa27f31469b" + url: "https://pub.dev" source: hosted version: "2.1.3" fuchsia_remote_debug_protocol: @@ -661,98 +789,112 @@ packages: dependency: transitive description: name: glob - url: "https://pub.dartlang.org" + sha256: c51b4fdfee4d281f49b8c957f1add91b815473597f76bcf07377987f66a55729 + url: "https://pub.dev" source: hosted version: "2.1.0" google_fonts: dependency: "direct main" description: name: google_fonts - url: "https://pub.dartlang.org" + sha256: e70521755a6b08c6bde14ddae27dff5bf21010033888fc61da6c595f8a9f58c1 + url: "https://pub.dev" source: hosted version: "2.3.3" graphs: dependency: transitive description: name: graphs - url: "https://pub.dartlang.org" + sha256: f9e130f3259f52d26f0cfc0e964513796dafed572fa52e45d2f8d6ca14db39b2 + url: "https://pub.dev" source: hosted version: "2.2.0" hex: - dependency: transitive + dependency: "direct main" description: name: hex - url: "https://pub.dartlang.org" + sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" + url: "https://pub.dev" source: hosted version: "0.2.0" hive: dependency: "direct main" description: name: hive - url: "https://pub.dartlang.org" + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" source: hosted version: "2.2.3" hive_flutter: dependency: "direct main" description: name: hive_flutter - url: "https://pub.dartlang.org" + sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc + url: "https://pub.dev" source: hosted version: "1.1.0" hive_generator: dependency: "direct dev" description: name: hive_generator - url: "https://pub.dartlang.org" + sha256: "81fd20125cb2ce8fd23623d7744ffbaf653aae93706c9bd3bf7019ea0ace3938" + url: "https://pub.dev" source: hosted version: "1.1.3" hive_test: dependency: "direct dev" description: name: hive_test - url: "https://pub.dartlang.org" + sha256: dd7a5cf0be7af288566a96180b5d07574023777aa947ef252b69046ec36d8eb2 + url: "https://pub.dev" source: hosted version: "1.0.1" html: dependency: transitive description: name: html - url: "https://pub.dartlang.org" + sha256: d9793e10dbe0e6c364f4c59bf3e01fb33a9b2a674bc7a1081693dba0614b6269 + url: "https://pub.dev" source: hosted version: "0.15.1" http: dependency: "direct main" description: name: http - url: "https://pub.dartlang.org" + sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482" + url: "https://pub.dev" source: hosted version: "0.13.5" http_multi_server: dependency: transitive description: name: http_multi_server - url: "https://pub.dartlang.org" + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" source: hosted version: "3.2.1" http_parser: dependency: transitive description: name: http_parser - url: "https://pub.dartlang.org" + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" source: hosted version: "4.0.2" image: dependency: transitive description: name: image - url: "https://pub.dartlang.org" + sha256: "8e9d133755c3e84c73288363e6343157c383a0c6c56fc51afcc5d4d7180306d6" + url: "https://pub.dev" source: hosted - version: "3.2.2" + version: "3.3.0" import_sorter: dependency: "direct dev" description: name: import_sorter - url: "https://pub.dartlang.org" + sha256: eb15738ccead84e62c31e0208ea4e3104415efcd4972b86906ca64a1187d0836 + url: "https://pub.dev" source: hosted version: "4.6.0" integration_test: @@ -764,63 +906,80 @@ packages: dependency: "direct main" description: name: intl - url: "https://pub.dartlang.org" + sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91" + url: "https://pub.dev" source: hosted version: "0.17.0" io: dependency: transitive description: name: io - url: "https://pub.dartlang.org" + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" isar: dependency: "direct main" description: name: isar - url: "https://pub.dartlang.org" + sha256: "5be35dbc489880fccc535da3d1c4b3f5fdeee6ebfcacd4b149e39e803c4029cd" + url: "https://pub.dev" source: hosted - version: "3.0.0-dev.10" + version: "3.0.5" isar_flutter_libs: dependency: "direct main" description: name: isar_flutter_libs - url: "https://pub.dartlang.org" + sha256: "9794524734856a8a3629652f9f359b66e3fea3cebeec4dbdeb3e3a8fb253073e" + url: "https://pub.dev" source: hosted - version: "3.0.0-dev.10" + version: "3.0.5" isar_generator: dependency: "direct dev" description: name: isar_generator - url: "https://pub.dartlang.org" + sha256: ee4ab5d5b251bc7e86e1257793b57af100065831f00f3a12404b177ae53c2d69 + url: "https://pub.dev" source: hosted - version: "3.0.0-dev.10" + version: "3.0.5" js: dependency: transitive description: name: js - url: "https://pub.dartlang.org" + sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "0.6.5" json_annotation: dependency: transitive description: name: json_annotation - url: "https://pub.dartlang.org" + sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 + url: "https://pub.dev" source: hosted - version: "4.7.0" + version: "4.8.0" + json_rpc_2: + dependency: transitive + description: + name: json_rpc_2 + sha256: "5e469bffa23899edacb7b22787780068d650b106a21c76db3c49218ab7ca447e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" jsonrpc2: dependency: "direct main" description: name: jsonrpc2 - url: "https://pub.dartlang.org" + sha256: "98a71b834240ca6d003499ab8f28d1c35aa8ca90235b51e972e0f70596b86bd3" + url: "https://pub.dev" source: hosted version: "3.0.1" keyboard_dismisser: dependency: "direct main" description: name: keyboard_dismisser - url: "https://pub.dartlang.org" + sha256: f67e032581fc3dd1f77e1cb54c421b089e015d122aeba2490ba001cfcc42a181 + url: "https://pub.dev" source: hosted version: "3.0.0" lelantus: @@ -830,508 +989,524 @@ packages: relative: true source: path version: "0.0.1" - lint: - dependency: transitive - description: - name: lint - url: "https://pub.dartlang.org" - source: hosted - version: "1.10.0" lints: dependency: transitive description: name: lints - url: "https://pub.dartlang.org" + sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + url: "https://pub.dev" source: hosted version: "2.0.1" local_auth: dependency: "direct main" description: name: local_auth - url: "https://pub.dartlang.org" + sha256: d3fece0749101725b03206f84a7dab7aaafb702dbbd09131ff8d8173259a9b19 + url: "https://pub.dev" source: hosted version: "1.1.11" logging: dependency: transitive description: name: logging - url: "https://pub.dartlang.org" + sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d" + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" lottie: dependency: "direct main" description: name: lottie - url: "https://pub.dartlang.org" + sha256: "893da7a0022ec2fcaa616f34529a081f617e86cc501105b856e5a3184c58c7c2" + url: "https://pub.dev" source: hosted - version: "1.4.2" + version: "1.4.3" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + url: "https://pub.dev" source: hosted - version: "0.12.11" + version: "0.12.13" material_color_utilities: dependency: transitive description: name: material_color_utilities - url: "https://pub.dartlang.org" + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + url: "https://pub.dev" source: hosted - version: "0.1.4" + version: "0.2.0" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + url: "https://pub.dev" source: hosted - version: "1.7.0" + version: "1.8.0" mime: dependency: transitive description: name: mime - url: "https://pub.dartlang.org" + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" mobx: dependency: transitive description: name: mobx - url: "https://pub.dartlang.org" + sha256: "6738620307a424d2c9ad8b873f4dce391c44e9135eb4e75668ac8202fec7a9b8" + url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" mockingjay: dependency: "direct dev" description: name: mockingjay - url: "https://pub.dartlang.org" + sha256: b05c786d68da95286274470ad53d9ca98198d168300005500bdd348fbf6a503a + url: "https://pub.dev" source: hosted version: "0.2.0" mockito: dependency: "direct dev" description: name: mockito - url: "https://pub.dartlang.org" + sha256: "2a8a17b82b1bde04d514e75d90d634a0ac23f6cb4991f6098009dd56836aeafe" + url: "https://pub.dev" source: hosted version: "5.3.2" mocktail: dependency: transitive description: name: mocktail - url: "https://pub.dartlang.org" + sha256: dd85ca5229cf677079fd9ac740aebfc34d9287cdf294e6b2ba9fae25c39e4dc2 + url: "https://pub.dev" source: hosted version: "0.2.0" mutex: dependency: "direct main" description: name: mutex - url: "https://pub.dartlang.org" + sha256: "03116a4e46282a671b46c12de649d72c0ed18188ffe12a8d0fc63e83f4ad88f4" + url: "https://pub.dev" source: hosted version: "3.0.1" nm: dependency: transitive description: name: nm - url: "https://pub.dartlang.org" + sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254" + url: "https://pub.dev" source: hosted version: "0.5.0" node_preamble: dependency: transitive description: name: node_preamble - url: "https://pub.dartlang.org" + sha256: "8ebdbaa3b96d5285d068f80772390d27c21e1fa10fb2df6627b1b9415043608d" + url: "https://pub.dev" source: hosted version: "2.0.1" package_config: dependency: transitive description: name: package_config - url: "https://pub.dartlang.org" + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" source: hosted version: "2.1.0" package_info_plus: dependency: "direct main" description: name: package_info_plus - url: "https://pub.dartlang.org" + sha256: f62d7253edc197fe3c88d7c2ddab82d68f555e778d55390ccc3537eca8e8d637 + url: "https://pub.dev" source: hosted version: "1.4.3+1" package_info_plus_linux: dependency: transitive description: name: package_info_plus_linux - url: "https://pub.dartlang.org" + sha256: "04b575f44233d30edbb80a94e57cad9107aada334fc02aabb42b6becd13c43fc" + url: "https://pub.dev" source: hosted version: "1.0.5" package_info_plus_macos: dependency: transitive description: name: package_info_plus_macos - url: "https://pub.dartlang.org" + sha256: a2ad8b4acf4cd479d4a0afa5a74ea3f5b1c7563b77e52cc32b3ee6956d5482a6 + url: "https://pub.dev" source: hosted version: "1.3.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - url: "https://pub.dartlang.org" + sha256: f7a0c8f1e7e981bc65f8b64137a53fd3c195b18d429fba960babc59a5a1c7ae8 + url: "https://pub.dev" source: hosted version: "1.0.2" package_info_plus_web: dependency: transitive description: name: package_info_plus_web - url: "https://pub.dartlang.org" + sha256: f0829327eb534789e0a16ccac8936a80beed4e2401c4d3a74f3f39094a822d3b + url: "https://pub.dev" source: hosted version: "1.0.6" package_info_plus_windows: dependency: transitive description: name: package_info_plus_windows - url: "https://pub.dartlang.org" + sha256: "79524f11c42dd9078b96d797b3cf79c0a2883a50c4920dc43da8562c115089bc" + url: "https://pub.dev" source: hosted version: "2.1.0" path: dependency: transitive description: name: path - url: "https://pub.dartlang.org" + sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + url: "https://pub.dev" source: hosted - version: "1.8.1" + version: "1.8.2" path_drawing: dependency: transitive description: name: path_drawing - url: "https://pub.dartlang.org" + sha256: bbb1934c0cbb03091af082a6389ca2080345291ef07a5fa6d6e078ba8682f977 + url: "https://pub.dev" source: hosted version: "1.0.1" path_parsing: dependency: transitive description: name: path_parsing - url: "https://pub.dartlang.org" + sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + url: "https://pub.dev" source: hosted version: "1.0.1" path_provider: dependency: "direct main" description: name: path_provider - url: "https://pub.dartlang.org" + sha256: dcea5feb97d8abf90cab9e9030b497fb7c3cbf26b7a1fe9e3ef7dcb0a1ddec95 + url: "https://pub.dev" source: hosted - version: "2.0.11" + version: "2.0.12" path_provider_android: dependency: transitive description: name: path_provider_android - url: "https://pub.dartlang.org" + sha256: a776c088d671b27f6e3aa8881d64b87b3e80201c64e8869b811325de7a76c15e + url: "https://pub.dev" source: hosted version: "2.0.22" - path_provider_ios: + path_provider_foundation: dependency: transitive description: - name: path_provider_ios - url: "https://pub.dartlang.org" + name: path_provider_foundation + sha256: "62a68e7e1c6c459f9289859e2fae58290c981ce21d1697faf54910fe1faa4c74" + url: "https://pub.dev" source: hosted - version: "2.0.11" + version: "2.1.1" path_provider_linux: dependency: transitive description: name: path_provider_linux - url: "https://pub.dartlang.org" + sha256: "2e32f1640f07caef0d3cb993680f181c79e54a3827b997d5ee221490d131fbd9" + url: "https://pub.dev" source: hosted - version: "2.1.7" - path_provider_macos: - dependency: transitive - description: - name: path_provider_macos - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.6" + version: "2.1.8" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - url: "https://pub.dartlang.org" + sha256: f0abc8ebd7253741f05488b4813d936b4d07c6bae3e86148a09e342ee4b08e76 + url: "https://pub.dev" source: hosted version: "2.0.5" path_provider_windows: dependency: transitive description: name: path_provider_windows - url: "https://pub.dartlang.org" + sha256: bcabbe399d4042b8ee687e17548d5d3f527255253b4a639f5f8d2094a9c2b45c + url: "https://pub.dev" source: hosted version: "2.1.3" permission_handler: dependency: "direct main" description: name: permission_handler - url: "https://pub.dartlang.org" + sha256: "33c6a1253d1f95fd06fa74b65b7ba907ae9811f9d5c1d3150e51417d04b8d6a8" + url: "https://pub.dev" source: hosted version: "10.2.0" permission_handler_android: dependency: transitive description: name: permission_handler_android - url: "https://pub.dartlang.org" + sha256: "8028362b40c4a45298f1cbfccd227c8dd6caf0e27088a69f2ba2ab15464159e2" + url: "https://pub.dev" source: hosted version: "10.2.0" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - url: "https://pub.dartlang.org" + sha256: "9c370ef6a18b1c4b2f7f35944d644a56aa23576f23abee654cf73968de93f163" + url: "https://pub.dev" source: hosted version: "9.0.7" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - url: "https://pub.dartlang.org" + sha256: "68abbc472002b5e6dfce47fe9898c6b7d8328d58b5d2524f75e277c07a97eb84" + url: "https://pub.dev" source: hosted version: "3.9.0" permission_handler_windows: dependency: transitive description: name: permission_handler_windows - url: "https://pub.dartlang.org" + sha256: f67cab14b4328574938ecea2db3475dad7af7ead6afab6338772c5f88963e38b + url: "https://pub.dev" source: hosted version: "0.1.2" petitparser: dependency: transitive description: name: petitparser - url: "https://pub.dartlang.org" + sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4" + url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "5.1.0" platform: dependency: transitive description: name: platform - url: "https://pub.dartlang.org" + sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + url: "https://pub.dev" source: hosted version: "3.1.0" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - url: "https://pub.dartlang.org" + sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a + url: "https://pub.dev" source: hosted version: "2.1.3" pointycastle: dependency: "direct main" description: name: pointycastle - url: "https://pub.dartlang.org" + sha256: db7306cf0249f838d1a24af52b5a5887c5bf7f31d8bb4e827d071dc0939ad346 + url: "https://pub.dev" source: hosted version: "3.6.2" pool: dependency: transitive description: name: pool - url: "https://pub.dartlang.org" + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" source: hosted version: "1.5.1" process: dependency: transitive description: name: process - url: "https://pub.dartlang.org" + sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + url: "https://pub.dev" source: hosted version: "4.2.4" protobuf: dependency: transitive description: name: protobuf - url: "https://pub.dartlang.org" + sha256: "01dd9bd0fa02548bf2ceee13545d4a0ec6046459d847b6b061d8a27237108a08" + url: "https://pub.dev" source: hosted version: "2.1.0" pub_semver: dependency: transitive description: name: pub_semver - url: "https://pub.dartlang.org" + sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17" + url: "https://pub.dev" source: hosted version: "2.1.3" pubspec_parse: dependency: transitive description: name: pubspec_parse - url: "https://pub.dartlang.org" + sha256: "75f6614d6dde2dc68948dffbaa4fe5dae32cd700eb9fb763fe11dfb45a3c4d0a" + url: "https://pub.dev" source: hosted version: "1.2.1" qr: dependency: transitive description: name: qr - url: "https://pub.dartlang.org" + sha256: "5c4208b4dc0d55c3184d10d83ee0ded6212dc2b5e2ba17c5a0c0aab279128d21" + url: "https://pub.dev" source: hosted version: "2.1.0" qr_flutter: dependency: "direct main" description: name: qr_flutter - url: "https://pub.dartlang.org" + sha256: c5c121c54cb6dd837b9b9d57eb7bc7ec6df4aee741032060c8833a678c80b87e + url: "https://pub.dev" source: hosted version: "4.0.0" rational: - dependency: transitive + dependency: "direct main" description: name: rational - url: "https://pub.dartlang.org" + sha256: ba58e9e18df9abde280e8b10051e4bce85091e41e8e7e411b6cde2e738d357cf + url: "https://pub.dev" source: hosted version: "2.2.2" riverpod: dependency: transitive description: name: riverpod - url: "https://pub.dartlang.org" + sha256: e7f097159b9512f5953ff544164c19057f45ce28fd0cb971fc4cad1f7b28217d + url: "https://pub.dev" source: hosted version: "1.0.3" rpc_dispatcher: dependency: transitive description: name: rpc_dispatcher - url: "https://pub.dartlang.org" + sha256: b6ddcae58b3fbc1172a7c2dc8ab30d2f1090db8c7a728e4405bd10142dc48a47 + url: "https://pub.dev" source: hosted version: "1.0.1" rpc_exceptions: dependency: transitive description: name: rpc_exceptions - url: "https://pub.dartlang.org" + sha256: "09b2e5f3f805b65a262b40e3410d79fb916ff5be2a65e2f6b8b0eeef7aa965b7" + url: "https://pub.dev" source: hosted version: "1.0.1" rxdart: dependency: "direct main" description: name: rxdart - url: "https://pub.dartlang.org" + sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + url: "https://pub.dev" source: hosted version: "0.27.7" share_plus: dependency: "direct main" description: name: share_plus - url: "https://pub.dartlang.org" + sha256: "8c6892037b1824e2d7e8f59d54b3105932899008642e6372e5079c6939b4b625" + url: "https://pub.dev" source: hosted - version: "4.5.3" - share_plus_linux: - dependency: transitive - description: - name: share_plus_linux - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" - share_plus_macos: - dependency: transitive - description: - name: share_plus_macos - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" + version: "6.3.1" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - url: "https://pub.dartlang.org" + sha256: "82ddd4ab9260c295e6e39612d4ff00390b9a7a21f1bb1da771e2f232d80ab8a1" + url: "https://pub.dev" source: hosted version: "3.2.0" - share_plus_web: - dependency: transitive - description: - name: share_plus_web - url: "https://pub.dartlang.org" - source: hosted - version: "3.1.0" - share_plus_windows: - dependency: transitive - description: - name: share_plus_windows - url: "https://pub.dartlang.org" - source: hosted - version: "3.0.1" shared_preferences: dependency: transitive description: name: shared_preferences - url: "https://pub.dartlang.org" + sha256: "5949029e70abe87f75cfe59d17bf5c397619c4b74a099b10116baeb34786fad9" + url: "https://pub.dev" source: hosted - version: "2.0.15" + version: "2.0.17" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - url: "https://pub.dartlang.org" + sha256: "955e9736a12ba776bdd261cf030232b30eadfcd9c79b32a3250dd4a494e8c8f7" + url: "https://pub.dev" source: hosted - version: "2.0.14" - shared_preferences_ios: + version: "2.0.15" + shared_preferences_foundation: dependency: transitive description: - name: shared_preferences_ios - url: "https://pub.dartlang.org" + name: shared_preferences_foundation + sha256: "2b55c18636a4edc529fa5cd44c03d3f3100c00513f518c5127c951978efcccd0" + url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.3" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - url: "https://pub.dartlang.org" + sha256: f8ea038aa6da37090093974ebdcf4397010605fd2ff65c37a66f9d28394cb874 + url: "https://pub.dev" source: hosted - version: "2.1.1" - shared_preferences_macos: - dependency: transitive - description: - name: shared_preferences_macos - url: "https://pub.dartlang.org" - source: hosted - version: "2.0.4" + version: "2.1.3" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - url: "https://pub.dartlang.org" + sha256: da9431745ede5ece47bc26d5d73a9d3c6936ef6945c101a5aca46f62e52c1cf3 + url: "https://pub.dev" source: hosted version: "2.1.0" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - url: "https://pub.dartlang.org" + sha256: a4b5bc37fe1b368bbc81f953197d55e12f49d0296e7e412dfe2d2d77d6929958 + url: "https://pub.dev" source: hosted version: "2.0.4" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - url: "https://pub.dartlang.org" + sha256: "5eaf05ae77658d3521d0e993ede1af962d4b326cd2153d312df716dc250f00c9" + url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.3" shelf: dependency: transitive description: name: shelf - url: "https://pub.dartlang.org" + sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c + url: "https://pub.dev" source: hosted version: "1.4.0" shelf_packages_handler: dependency: transitive description: name: shelf_packages_handler - url: "https://pub.dartlang.org" + sha256: aef74dc9195746a384843102142ab65b6a4735bb3beea791e63527b88cc83306 + url: "https://pub.dev" source: hosted version: "3.0.1" shelf_static: dependency: transitive description: name: shelf_static - url: "https://pub.dartlang.org" + sha256: e792b76b96a36d4a41b819da593aff4bdd413576b3ba6150df5d8d9996d2e74c + url: "https://pub.dev" source: hosted version: "1.1.1" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - url: "https://pub.dartlang.org" + sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8 + url: "https://pub.dev" source: hosted version: "1.0.3" sky_engine: @@ -1343,50 +1518,56 @@ packages: dependency: transitive description: name: source_gen - url: "https://pub.dartlang.org" + sha256: "2d79738b6bbf38a43920e2b8d189e9a3ce6cc201f4b8fc76be5e4fe377b1c38d" + url: "https://pub.dev" source: hosted version: "1.2.6" source_helper: dependency: transitive description: name: source_helper - url: "https://pub.dartlang.org" + sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f" + url: "https://pub.dev" source: hosted version: "1.3.3" source_map_stack_trace: dependency: transitive description: name: source_map_stack_trace - url: "https://pub.dartlang.org" + sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" + url: "https://pub.dev" source: hosted version: "2.1.1" source_maps: dependency: transitive description: name: source_maps - url: "https://pub.dartlang.org" + sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + url: "https://pub.dev" source: hosted - version: "0.10.10" + version: "0.10.12" source_span: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.9.1" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" stack_wallet_backup: dependency: "direct main" description: path: "." - ref: "011dc9ce3d29f5fdeeaf711d58b5122f055c146d" - resolved-ref: "011dc9ce3d29f5fdeeaf711d58b5122f055c146d" + ref: e4b08d2b8965a5ae49bd57f598fa9011dd0c25e9 + resolved-ref: e4b08d2b8965a5ae49bd57f598fa9011dd0c25e9 url: "https://github.com/cypherstack/stack_wallet_backup.git" source: git version: "0.0.1" @@ -1394,275 +1575,338 @@ packages: dependency: transitive description: name: state_notifier - url: "https://pub.dartlang.org" + sha256: "8fe42610f179b843b12371e40db58c9444f8757f8b69d181c97e50787caed289" + url: "https://pub.dev" source: hosted version: "0.7.2+1" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.1" stream_transform: dependency: transitive description: name: stream_transform - url: "https://pub.dartlang.org" + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" source: hosted version: "2.1.0" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.0" + string_to_hex: + dependency: "direct main" + description: + name: string_to_hex + sha256: "63e5dc1f4821a2449d505033fbd4569f7020ebf30ddffb54d00ebaba8e144a49" + url: "https://pub.dev" + source: hosted + version: "0.2.2" string_validator: dependency: "direct main" description: name: string_validator - url: "https://pub.dartlang.org" + sha256: "50dd8ecf91db6a732f4a851eeae81ee12406eedc62d0da72f2d91a04a2d10dd8" + url: "https://pub.dev" source: hosted version: "0.3.0" sync_http: dependency: transitive description: name: sync_http - url: "https://pub.dartlang.org" + sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" + url: "https://pub.dev" source: hosted - version: "0.3.0" + version: "0.3.1" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" test: dependency: transitive description: name: test - url: "https://pub.dartlang.org" + sha256: a5fcd2d25eeadbb6589e80198a47d6a464ba3e2049da473943b8af9797900c2d + url: "https://pub.dev" source: hosted - version: "1.21.1" + version: "1.22.0" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + url: "https://pub.dev" source: hosted - version: "0.4.9" + version: "0.4.16" test_core: dependency: transitive description: name: test_core - url: "https://pub.dartlang.org" + sha256: "0ef9755ec6d746951ba0aabe62f874b707690b5ede0fecc818b138fcc9b14888" + url: "https://pub.dev" source: hosted - version: "0.4.13" + version: "0.4.20" time: dependency: transitive description: name: time - url: "https://pub.dartlang.org" + sha256: "83427e11d9072e038364a5e4da559e85869b227cf699a541be0da74f14140124" + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" timezone: dependency: transitive description: name: timezone - url: "https://pub.dartlang.org" + sha256: "57b35f6e8ef731f18529695bffc62f92c6189fac2e52c12d478dec1931afb66e" + url: "https://pub.dev" source: hosted version: "0.8.0" timing: dependency: transitive description: name: timing - url: "https://pub.dartlang.org" + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.1" tint: dependency: transitive description: name: tint - url: "https://pub.dartlang.org" + sha256: "9652d9a589f4536d5e392cf790263d120474f15da3cf1bee7f1fdb31b4de5f46" + url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.1" toast: dependency: "direct main" description: name: toast - url: "https://pub.dartlang.org" + sha256: bedb96d37030acf9c4c06a7ac2ffd1f1f365e780cda9458c9e24e6a1e1ab6fd9 + url: "https://pub.dev" source: hosted version: "0.1.5" tuple: dependency: "direct main" description: name: tuple - url: "https://pub.dartlang.org" + sha256: "0ea99cd2f9352b2586583ab2ce6489d1f95a5f6de6fb9492faaf97ae2060f0aa" + url: "https://pub.dev" source: hosted version: "2.0.1" typed_data: dependency: transitive description: name: typed_data - url: "https://pub.dartlang.org" + sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.3.1" universal_io: dependency: transitive description: name: universal_io - url: "https://pub.dartlang.org" + sha256: "06866290206d196064fd61df4c7aea1ffe9a4e7c4ccaa8fcded42dd41948005d" + url: "https://pub.dev" source: hosted - version: "2.0.4" + version: "2.2.0" url_launcher: dependency: "direct main" description: name: url_launcher - url: "https://pub.dartlang.org" + sha256: e8f2efc804810c0f2f5b485f49e7942179f56eabcfe81dce3387fec4bb55876b + url: "https://pub.dev" source: hosted - version: "6.1.7" + version: "6.1.9" url_launcher_android: dependency: transitive description: name: url_launcher_android - url: "https://pub.dartlang.org" + sha256: "3e2f6dfd2c7d9cd123296cab8ef66cfc2c1a13f5845f42c7a0f365690a8a7dd1" + url: "https://pub.dev" source: hosted - version: "6.0.22" + version: "6.0.23" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - url: "https://pub.dartlang.org" + sha256: "0a5af0aefdd8cf820dd739886efb1637f1f24489900204f50984634c07a54815" + url: "https://pub.dev" source: hosted - version: "6.0.17" + version: "6.1.0" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - url: "https://pub.dartlang.org" + sha256: "318c42cba924e18180c029be69caf0a1a710191b9ec49bb42b5998fdcccee3cc" + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - url: "https://pub.dartlang.org" + sha256: "41988b55570df53b3dd2a7fc90c76756a963de6a8c5f8e113330cb35992e2094" + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.2" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - url: "https://pub.dartlang.org" + sha256: "4eae912628763eb48fc214522e58e942fd16ce195407dbf45638239523c759a6" + url: "https://pub.dev" source: hosted version: "2.1.1" url_launcher_web: dependency: transitive description: name: url_launcher_web - url: "https://pub.dartlang.org" + sha256: "44d79408ce9f07052095ef1f9a693c258d6373dc3944249374e30eff7219ccb0" + url: "https://pub.dev" source: hosted - version: "2.0.13" + version: "2.0.14" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - url: "https://pub.dartlang.org" + sha256: b6217370f8eb1fd85c8890c539f5a639a01ab209a36db82c921ebeacefc7a615 + url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.0.3" uuid: dependency: "direct main" description: name: uuid - url: "https://pub.dartlang.org" + sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + url: "https://pub.dev" source: hosted version: "3.0.7" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" vm_service: dependency: transitive description: name: vm_service - url: "https://pub.dartlang.org" + sha256: e7fb6c2282f7631712b69c19d1bff82f3767eea33a2321c14fa59ad67ea391c7 + url: "https://pub.dev" source: hosted - version: "8.2.2" + version: "9.4.0" wakelock: dependency: "direct main" description: name: wakelock - url: "https://pub.dartlang.org" + sha256: "769ecf42eb2d07128407b50cb93d7c10bd2ee48f0276ef0119db1d25cc2f87db" + url: "https://pub.dev" source: hosted version: "0.6.2" wakelock_macos: dependency: transitive description: name: wakelock_macos - url: "https://pub.dartlang.org" + sha256: "047c6be2f88cb6b76d02553bca5a3a3b95323b15d30867eca53a19a0a319d4cd" + url: "https://pub.dev" source: hosted version: "0.4.0" wakelock_platform_interface: dependency: transitive description: name: wakelock_platform_interface - url: "https://pub.dartlang.org" + sha256: "1f4aeb81fb592b863da83d2d0f7b8196067451e4df91046c26b54a403f9de621" + url: "https://pub.dev" source: hosted version: "0.3.0" wakelock_web: dependency: transitive description: name: wakelock_web - url: "https://pub.dartlang.org" + sha256: "1b256b811ee3f0834888efddfe03da8d18d0819317f20f6193e2922b41a501b5" + url: "https://pub.dev" source: hosted version: "0.4.0" wakelock_windows: dependency: transitive description: name: wakelock_windows - url: "https://pub.dartlang.org" + sha256: "857f77b3fe6ae82dd045455baa626bc4b93cb9bb6c86bf3f27c182167c3a5567" + url: "https://pub.dev" source: hosted version: "0.2.1" watcher: dependency: transitive description: name: watcher - url: "https://pub.dartlang.org" + sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" + url: "https://pub.dev" source: hosted version: "1.0.2" + web3dart: + dependency: "direct main" + description: + name: web3dart + sha256: "48b89a5fac0029770a18d1a8bd05ce8431722bacf76184e4301dae05781565e5" + url: "https://pub.dev" + source: hosted + version: "2.3.5" web_socket_channel: dependency: transitive description: name: web_socket_channel - url: "https://pub.dartlang.org" + sha256: ca49c0bc209c687b887f30527fb6a9d80040b072cc2990f34b9bec3e7663101b + url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.0" webdriver: dependency: transitive description: name: webdriver - url: "https://pub.dartlang.org" + sha256: ef67178f0cc7e32c1494645b11639dd1335f1d18814aa8435113a92e9ef9d841 + url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.0.1" webkit_inspection_protocol: dependency: transitive description: name: webkit_inspection_protocol - url: "https://pub.dartlang.org" + sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d" + url: "https://pub.dev" source: hosted version: "1.2.0" + websocket_universal: + dependency: "direct main" + description: + name: websocket_universal + sha256: c12656930ceb52dc22505bcb0705f93418c97ab70b8bbc31e6cc7e79a3717fd6 + url: "https://pub.dev" + source: hosted + version: "0.5.1" win32: dependency: transitive description: name: win32 - url: "https://pub.dartlang.org" + sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46 + url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" window_size: dependency: "direct main" description: @@ -1676,37 +1920,42 @@ packages: dependency: transitive description: name: xdg_directories - url: "https://pub.dartlang.org" + sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86 + url: "https://pub.dev" source: hosted - version: "0.2.0+2" + version: "0.2.0+3" xml: dependency: transitive description: name: xml - url: "https://pub.dartlang.org" + sha256: ac0e3f4bf00ba2708c33fbabbbe766300e509f8c82dbd4ab6525039813f7e2fb + url: "https://pub.dev" source: hosted version: "6.1.0" xxh3: dependency: transitive description: name: xxh3 - url: "https://pub.dartlang.org" + sha256: a92b30944a9aeb4e3d4f3c3d4ddb3c7816ca73475cd603682c4f8149690f56d7 + url: "https://pub.dev" source: hosted version: "1.0.1" yaml: dependency: transitive description: name: yaml - url: "https://pub.dartlang.org" + sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370" + url: "https://pub.dev" source: hosted version: "3.1.1" zxcvbn: dependency: "direct main" description: name: zxcvbn - url: "https://pub.dartlang.org" + sha256: "5d860ab87c0e7f295902697afd364aa722d89d4e5839e8800ad1b0faf3d63b08" + url: "https://pub.dev" source: hosted version: "1.0.0" sdks: - dart: ">=2.17.5 <3.0.0" - flutter: ">=3.0.1" + dart: ">=2.18.5 <3.0.0" + flutter: ">=3.3.0" diff --git a/pubspec.yaml b/pubspec.yaml index 0fe5ef6d8..9cd249b2f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,7 @@ description: Stack Wallet # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.5.25+98 +version: 1.7.8+172 environment: sdk: ">=2.17.0 <3.0.0" @@ -21,6 +21,7 @@ dependencies: sdk: flutter ffi: ^2.0.1 mutex: ^3.0.0 + websocket_universal: ^0.5.1 lelantus: path: ./crypto_plugins/flutter_liblelantus @@ -54,7 +55,12 @@ dependencies: stack_wallet_backup: git: url: https://github.com/cypherstack/stack_wallet_backup.git - ref: 011dc9ce3d29f5fdeeaf711d58b5122f055c146d + ref: e4b08d2b8965a5ae49bd57f598fa9011dd0c25e9 + + bip47: + git: + url: https://github.com/cypherstack/bip47.git + ref: 38847255d035c0f6ec5bc93d19130ec804cf90e9 # Utility plugins # provider: ^6.0.1 @@ -79,8 +85,8 @@ dependencies: ref: 3bef5acc21340f3cc78df0ad1dce5868a3ed68a5 bitbox: git: - url: https://github.com/Quppy/bitbox-flutter.git - ref: ea65073efbaf395a5557e8cd7bd72f195cd7eb11 + url: https://github.com/PiRK/bitbox-flutter.git + ref: 50bf29957514a5712466ba37590a851212a244bf bip32: ^2.0.0 bech32: git: @@ -88,6 +94,11 @@ dependencies: ref: 22279d4bb24ed541b431acd269a1bc50af0f36a0 bs58check: ^1.0.2 + # Eth Plugins + web3dart: 2.3.5 + string_to_hex: 0.2.2 + ethereum_addresses: 1.0.2 + # Storage plugins flutter_secure_storage: ^5.0.2 hive: ^2.0.5 @@ -121,7 +132,7 @@ dependencies: tuple: ^2.0.0 flutter_riverpod: ^1.0.3 qr_flutter: ^4.0.0 - share_plus: ^4.0.10 + share_plus: ^6.3.0 emojis: ^0.9.9 pointycastle: ^3.6.0 package_info_plus: ^1.4.2 @@ -130,10 +141,18 @@ dependencies: file_picker: ^5.0.1 connectivity_plus: 2.3.6+1 # document_file_save_plus: ^1.0.5 - isar: 3.0.0-dev.10 - isar_flutter_libs: 3.0.0-dev.10 # contains the binaries + isar: 3.0.5 + isar_flutter_libs: 3.0.5 # contains the binaries dropdown_button2: 1.7.2 string_validator: ^0.3.0 + equatable: ^2.0.5 + async: ^2.10.0 + dart_bs58: ^1.0.1 + dart_bs58check: ^3.0.2 + hex: ^0.2.0 + rational: ^2.2.2 + archive: ^3.3.2 + desktop_drop: ^0.4.1 dev_dependencies: flutter_test: @@ -151,7 +170,7 @@ dev_dependencies: analyzer: ^4.6.0 import_sorter: ^4.6.0 flutter_lints: ^2.0.1 - isar_generator: 3.0.0-dev.10 + isar_generator: 3.0.5 flutter_icons: android: true @@ -199,19 +218,9 @@ flutter: - google_fonts/ - assets/svg/circle-check.svg - assets/svg/clipboard.svg - - assets/images/stack.png - - assets/images/monero.png - - assets/images/wownero.png - - assets/images/firo.png - - assets/images/litecoin.png - - assets/images/doge.png - - assets/images/bitcoin.png - - assets/images/epic-cash.png - - assets/images/bitcoincash.png - - assets/images/namecoin.png - - assets/images/particl.png - assets/images/glasses.png - assets/images/glasses-hidden.png + - assets/svg/unclaimed.svg - assets/svg/plus.svg - assets/svg/gear.svg - assets/svg/bell.svg @@ -239,7 +248,6 @@ flutter: - assets/svg/chevron-up.svg - assets/svg/lock-keyhole.svg - assets/svg/lock-open.svg - - assets/svg/rotate-exclamation.svg - assets/svg/folder-down.svg - assets/svg/network-wired.svg - assets/svg/network-wired-2.svg @@ -255,6 +263,8 @@ flutter: - assets/svg/x.svg - assets/svg/x-fat.svg - assets/svg/user.svg + - assets/svg/user-plus.svg + - assets/svg/user-minus.svg - assets/svg/trash.svg - assets/svg/eye.svg - assets/svg/eye-slash.svg @@ -282,8 +292,6 @@ flutter: - assets/svg/tx-icon-anonymize-pending.svg - assets/svg/tx-icon-anonymize-failed.svg - assets/svg/Polygon.svg - - assets/svg/persona-easy-1.svg - - assets/svg/persona-incognito-1.svg - assets/svg/Button.svg - assets/svg/enabled-button.svg - assets/svg/lock-circle.svg @@ -300,26 +308,14 @@ flutter: - assets/svg/keys.svg - assets/svg/arrow-down.svg - assets/svg/plus-circle.svg + - assets/svg/circle-plus-filled.svg - assets/svg/configuration.svg - # coin icons - - assets/svg/coin_icons/Bitcoin.svg - - assets/svg/coin_icons/Litecoin.svg - - assets/svg/coin_icons/Bitcoincash.svg - - assets/svg/coin_icons/Dogecoin.svg - - assets/svg/coin_icons/EpicCash.svg - - assets/svg/coin_icons/Firo.svg - - assets/svg/coin_icons/Monero.svg - - assets/svg/coin_icons/Wownero.svg - - assets/svg/coin_icons/Namecoin.svg - - assets/svg/coin_icons/Particl.svg - # lottie animations - - assets/lottie/test.json - - assets/lottie/test2.json - # socials - - assets/svg/socials/discord.svg - - assets/svg/socials/reddit-alien-brands.svg - - assets/svg/socials/twitter-brands.svg - - assets/svg/socials/telegram-brands.svg + - assets/svg/tokens.svg + - assets/svg/circle-plus.svg + - assets/svg/robot-head.svg + - assets/svg/whirlpool.svg + - assets/svg/fingerprint.svg + - assets/svg/faceid.svg - assets/svg/chevron-right.svg - assets/svg/minimize.svg - assets/svg/wallet-fa.svg @@ -329,60 +325,36 @@ flutter: - assets/svg/box-auto.svg - assets/svg/framed-address-book.svg - assets/svg/framed-gear.svg + - assets/svg/list-ul.svg + - assets/svg/cc.svg + - assets/svg/file.svg + - assets/svg/file-upload.svg + - assets/svg/trocador_rating_a.svg + - assets/svg/trocador_rating_b.svg + - assets/svg/trocador_rating_c.svg + - assets/svg/trocador_rating_d.svg + + # coin control icons + - assets/svg/coin_control/ + + # socials + - assets/svg/socials/ + # exchange icons - - assets/svg/exchange_icons/change_now_logo_1.svg - - assets/svg/exchange_icons/simpleswap-icon.svg + - assets/svg/exchange_icons/ - # theme selectors - - assets/svg/dark-theme.svg - - assets/svg/light-mode.svg - - assets/svg/ocean-breeze-theme.svg + # buy + - assets/svg/buy/ - # light theme specific - - assets/svg/light/tx-exchange-icon.svg - - assets/svg/light/tx-exchange-icon-pending.svg - - assets/svg/light/tx-exchange-icon-failed.svg - - assets/svg/light/tx-icon-send.svg - - assets/svg/light/tx-icon-send-pending.svg - - assets/svg/light/tx-icon-send-failed.svg - - assets/svg/light/tx-icon-receive.svg - - assets/svg/light/tx-icon-receive-pending.svg - - assets/svg/light/tx-icon-receive-failed.svg - - assets/svg/light/exchange-2.svg - - assets/svg/light/bell-new.svg - - assets/svg/light/stack-icon1.svg - - assets/svg/light/buy-coins-icon.svg + # lottie animations + # basic + - assets/lottie/test2.json + - assets/lottie/icon_send.json + - assets/lottie/loader_and_checkmark.json + - assets/lottie/arrow_rotate.json - # dark theme specific - - assets/svg/dark/tx-exchange-icon.svg - - assets/svg/dark/tx-exchange-icon-pending.svg - - assets/svg/dark/tx-exchange-icon-failed.svg - - assets/svg/dark/tx-icon-send.svg - - assets/svg/dark/tx-icon-send-pending.svg - - assets/svg/dark/tx-icon-send-failed.svg - - assets/svg/dark/tx-icon-receive.svg - - assets/svg/dark/tx-icon-receive-pending.svg - - assets/svg/dark/tx-icon-receive-failed.svg - - assets/svg/dark/exchange-2.svg - - assets/svg/dark/bell-new.svg - - assets/svg/dark/stack-icon1.svg - - assets/svg/dark/buy-coins-icon.svg - - # light theme specific - - assets/svg/oceanBreeze/tx-exchange-icon.svg - - assets/svg/oceanBreeze/tx-exchange-icon-pending.svg - - assets/svg/oceanBreeze/tx-exchange-icon-failed.svg - - assets/svg/oceanBreeze/tx-icon-send.svg - - assets/svg/oceanBreeze/tx-icon-send-pending.svg - - assets/svg/oceanBreeze/tx-icon-send-failed.svg - - assets/svg/oceanBreeze/tx-icon-receive.svg - - assets/svg/oceanBreeze/tx-icon-receive-pending.svg - - assets/svg/oceanBreeze/tx-icon-receive-failed.svg - - assets/svg/oceanBreeze/exchange-2.svg - - assets/svg/oceanBreeze/bell-new.svg - - assets/svg/oceanBreeze/stack-icon1.svg - - assets/svg/oceanBreeze/buy-coins-icon.svg - - assets/svg/oceanBreeze/bg.svg + # default themes_testing + - assets/default_themes/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. diff --git a/scripts/prebuild.sh b/scripts/prebuild.sh index b9ab666a9..77d65b253 100755 --- a/scripts/prebuild.sh +++ b/scripts/prebuild.sh @@ -1,3 +1,5 @@ +#!/bin/bash + # Create template lib/external_api_keys.dart file if it doesn't already exist KEYS=../lib/external_api_keys.dart if ! test -f "$KEYS"; then @@ -6,13 +8,14 @@ if ! test -f "$KEYS"; then fi # Create template wallet test parameter files if they don't already exist -declare -a coins=("bitcoin" "bitcoincash" "dogecoin" "namecoin" "firo" "particl") # TODO add monero and wownero when those tests are updated to use the .gitignored test wallet setup: when doing that, make sure to update the test vectors for a new, private development seed +declare -a coins +coins=("bitcoin" "bitcoincash" "dogecoin" "namecoin" "firo" "particl") # TODO add monero and wownero when those tests are updated to use the .gitignored test wallet setup: when doing that, make sure to update the test vectors for a new, private development seed for coin in "${coins[@]}" do WALLETTESTPARAMFILE="../test/services/coins/${coin}/${coin}_wallet_test_parameters.dart" if ! test -f "$WALLETTESTPARAMFILE"; then echo "prebuild.sh: creating template test/services/coins/${coin}/${coin}_wallet_test_parameters.dart file" - printf 'const TEST_MNEMONIC = "";\nconst ROOT_WIF = "";\nconst NODE_WIF_84 = "";\n' > $WALLETTESTPARAMFILE + printf 'const TEST_MNEMONIC = "";\nconst ROOT_WIF = "";\nconst NODE_WIF_84 = "";\n' > "$WALLETTESTPARAMFILE" fi done diff --git a/scripts/setup.sh b/scripts/setup.sh old mode 100644 new mode 100755 index 3b30a4694..7bf07ef5a --- a/scripts/setup.sh +++ b/scripts/setup.sh @@ -12,7 +12,7 @@ sudo apt install -y unzip pkg-config clang cmake ninja-build libgtk-3-dev cd $DEVELOPMENT git clone https://github.com/flutter/flutter.git cd flutter -git checkout 3.3.4 +git checkout 3.7.12 export FLUTTER_DIR=$(pwd)/bin echo 'export PATH="$PATH:'${FLUTTER_DIR}'"' >> ~/.bashrc source ~/.bashrc @@ -25,14 +25,14 @@ cd stack_wallet export STACK_WALLET=$(pwd) git submodule update --init --recursive -# Create template lib/external_api_keys.dart file if it doesn't already exist +# create template lib/external_api_keys.dart file if it doesn't already exist KEYS="$HOME/projects/stack_wallet/lib/external_api_keys.dart" if ! test -f "$KEYS"; then echo 'prebuild.sh: creating template lib/external_api_keys.dart file' printf 'const kChangeNowApiKey = "";\nconst kSimpleSwapApiKey = "";' > $KEYS fi -#install stack wallet dependencies +# install stack wallet dependencies sudo apt-get install -y unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake openjdk-8-jre-headless libgit2-dev clang libncurses5-dev libncursesw5-dev zlib1g-dev llvm sudo apt-get install -y debhelper libclang-dev cargo rustc opencl-headers libssl-dev ocl-icd-opencl-dev @@ -41,7 +41,14 @@ sudo apt-get install -y unzip automake build-essential file pkg-config git pytho sudo apt install -y libc6-dev-i386 -# Install Rust curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +source "$HOME/.cargo/env" cargo install cargo-ndk rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android + +# build stack wallet plugins +cd $STACK_WALLET +cd scripts/android +./build_all.sh +cd ../linux +./build_all.sh diff --git a/test/address_book_service_test.dart b/test/address_book_service_test.dart deleted file mode 100644 index c5effd223..000000000 --- a/test/address_book_service_test.dart +++ /dev/null @@ -1,174 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:hive/hive.dart'; -import 'package:hive_test/hive_test.dart'; -import 'package:stackwallet/hive/db.dart'; -import 'package:stackwallet/models/contact.dart'; -import 'package:stackwallet/models/contact_address_entry.dart'; -import 'package:stackwallet/services/address_book_service.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; - -void main() { - group("Empty DB tests", () { - setUp(() async { - await setUpTestHive(); - await Hive.openBox(DB.boxNameAddressBook); - }); - - test("get empty contacts", () { - final service = AddressBookService(); - expect(service.contacts, []); - }); - - test("get empty addressBookEntries", () async { - final service = AddressBookService(); - expect(await service.addressBookEntries, []); - }); - - test("getContactById from empty db", () { - final service = AddressBookService(); - expect(() => service.getContactById("some id"), throwsException); - }); - - tearDown(() async { - await tearDownTestHive(); - }); - }); - - group("Preloaded DB tests", () { - final contactA = Contact(name: "john", addresses: [], isFavorite: true); - final contactB = Contact( - name: "JANE", - addresses: [ - const ContactAddressEntry( - coin: Coin.bitcoin, - address: "some btc address", - label: "rent", - ), - ], - isFavorite: false); - final contactC = Contact( - name: "Bill", - addresses: [ - const ContactAddressEntry( - coin: Coin.monero, - address: "some xmr address", - label: "market", - ), - const ContactAddressEntry( - coin: Coin.epicCash, - address: "some epic address", - label: "gas", - ), - ], - isFavorite: true); - - setUp(() async { - await setUpTestHive(); - await Hive.openBox(DB.boxNameAddressBook); - - await DB.instance.put( - boxName: DB.boxNameAddressBook, - key: contactA.id, - value: contactA.toMap()); - await DB.instance.put( - boxName: DB.boxNameAddressBook, - key: contactB.id, - value: contactB.toMap()); - await DB.instance.put( - boxName: DB.boxNameAddressBook, - key: contactC.id, - value: contactC.toMap()); - }); - - test("getContactById with non existing ID", () { - final service = AddressBookService(); - expect(() => service.getContactById("some id"), throwsException); - }); - - test("getContactById with existing ID", () { - final service = AddressBookService(); - expect( - service.getContactById(contactA.id).toString(), contactA.toString()); - }); - - test("get contacts", () { - final service = AddressBookService(); - expect(service.contacts.toString(), - [contactC, contactB, contactA].toString()); - }); - - test("get addressBookEntries", () async { - final service = AddressBookService(); - expect((await service.addressBookEntries).toString(), - [contactC, contactB, contactA].toString()); - }); - - test("search contacts", () async { - final service = AddressBookService(); - final results = await service.search("j"); - expect(results.toString(), [contactB, contactA].toString()); - - final results2 = await service.search("ja"); - expect(results2.toString(), [contactB].toString()); - - final results3 = await service.search("john"); - expect(results3.toString(), [contactA].toString()); - - final results4 = await service.search("po"); - expect(results4.toString(), [].toString()); - - final results5 = await service.search(""); - expect(results5.toString(), [contactC, contactB, contactA].toString()); - - final results6 = await service.search("epic address"); - expect(results6.toString(), [contactC].toString()); - }); - - test("add new contact", () async { - final service = AddressBookService(); - final contactD = Contact(name: "tim", addresses: [], isFavorite: true); - final result = await service.addContact(contactD); - expect(result, true); - expect(service.contacts.length, 4); - expect( - service.getContactById(contactD.id).toString(), contactD.toString()); - }); - - test("add duplicate contact", () async { - final service = AddressBookService(); - final result = await service.addContact(contactA); - expect(result, false); - expect(service.contacts.length, 3); - expect(service.contacts.toString(), - [contactC, contactB, contactA].toString()); - }); - - test("edit contact", () async { - final service = AddressBookService(); - final editedContact = contactB.copyWith(name: "Mike"); - expect(await service.editContact(editedContact), true); - expect(service.contacts.length, 3); - expect(service.contacts.toString(), - [contactC, contactA, editedContact].toString()); - }); - - test("remove existing contact", () async { - final service = AddressBookService(); - await service.removeContact(contactB.id); - expect(service.contacts.length, 2); - expect(service.contacts.toString(), [contactC, contactA].toString()); - }); - - test("remove non existing contact", () async { - final service = AddressBookService(); - await service.removeContact("some id"); - expect(service.contacts.length, 3); - expect(service.contacts.toString(), - [contactC, contactB, contactA].toString()); - }); - - tearDown(() async { - await tearDownTestHive(); - }); - }); -} diff --git a/test/cached_electrumx_test.dart b/test/cached_electrumx_test.dart index e0f7fd6ca..329de9daf 100644 --- a/test/cached_electrumx_test.dart +++ b/test/cached_electrumx_test.dart @@ -3,9 +3,9 @@ import 'package:hive/hive.dart'; import 'package:hive_test/hive_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; +import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; -import 'package:stackwallet/hive/db.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/prefs.dart'; diff --git a/test/cached_electrumx_test.mocks.dart b/test/cached_electrumx_test.mocks.dart index 68d3b695f..b83e96ed9 100644 --- a/test/cached_electrumx_test.mocks.dart +++ b/test/cached_electrumx_test.mocks.dart @@ -4,14 +4,12 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i4; -import 'dart:ui' as _i9; +import 'dart:ui' as _i8; import 'package:decimal/decimal.dart' as _i2; import 'package:mockito/mockito.dart' as _i1; import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i3; -import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart' - as _i7; -import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i8; +import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i7; import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i6; import 'package:stackwallet/utilities/prefs.dart' as _i5; @@ -511,16 +509,15 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs { returnValueForMissingStub: null, ); @override - _i7.ExchangeRateType get exchangeRateType => (super.noSuchMethod( - Invocation.getter(#exchangeRateType), - returnValue: _i7.ExchangeRateType.estimated, - ) as _i7.ExchangeRateType); + bool get randomizePIN => (super.noSuchMethod( + Invocation.getter(#randomizePIN), + returnValue: false, + ) as bool); @override - set exchangeRateType(_i7.ExchangeRateType? exchangeRateType) => - super.noSuchMethod( + set randomizePIN(bool? randomizePIN) => super.noSuchMethod( Invocation.setter( - #exchangeRateType, - exchangeRateType, + #randomizePIN, + randomizePIN, ), returnValueForMissingStub: null, ); @@ -598,12 +595,12 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs { returnValueForMissingStub: null, ); @override - _i8.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod( + _i7.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod( Invocation.getter(#backupFrequencyType), - returnValue: _i8.BackupFrequencyType.everyTenMinutes, - ) as _i8.BackupFrequencyType); + returnValue: _i7.BackupFrequencyType.everyTenMinutes, + ) as _i7.BackupFrequencyType); @override - set backupFrequencyType(_i8.BackupFrequencyType? backupFrequencyType) => + set backupFrequencyType(_i7.BackupFrequencyType? backupFrequencyType) => super.noSuchMethod( Invocation.setter( #backupFrequencyType, @@ -668,6 +665,74 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs { returnValueForMissingStub: null, ); @override + bool get enableCoinControl => (super.noSuchMethod( + Invocation.getter(#enableCoinControl), + returnValue: false, + ) as bool); + @override + set enableCoinControl(bool? enableCoinControl) => super.noSuchMethod( + Invocation.setter( + #enableCoinControl, + enableCoinControl, + ), + returnValueForMissingStub: null, + ); + @override + bool get enableSystemBrightness => (super.noSuchMethod( + Invocation.getter(#enableSystemBrightness), + returnValue: false, + ) as bool); + @override + set enableSystemBrightness(bool? enableSystemBrightness) => + super.noSuchMethod( + Invocation.setter( + #enableSystemBrightness, + enableSystemBrightness, + ), + returnValueForMissingStub: null, + ); + @override + String get themeId => (super.noSuchMethod( + Invocation.getter(#themeId), + returnValue: '', + ) as String); + @override + set themeId(String? themeId) => super.noSuchMethod( + Invocation.setter( + #themeId, + themeId, + ), + returnValueForMissingStub: null, + ); + @override + String get systemBrightnessLightThemeId => (super.noSuchMethod( + Invocation.getter(#systemBrightnessLightThemeId), + returnValue: '', + ) as String); + @override + set systemBrightnessLightThemeId(String? systemBrightnessLightThemeId) => + super.noSuchMethod( + Invocation.setter( + #systemBrightnessLightThemeId, + systemBrightnessLightThemeId, + ), + returnValueForMissingStub: null, + ); + @override + String get systemBrightnessDarkThemeId => (super.noSuchMethod( + Invocation.getter(#systemBrightnessDarkThemeId), + returnValue: '', + ) as String); + @override + set systemBrightnessDarkThemeId(String? systemBrightnessDarkThemeId) => + super.noSuchMethod( + Invocation.setter( + #systemBrightnessDarkThemeId, + systemBrightnessDarkThemeId, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, @@ -699,7 +764,25 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs { returnValue: _i4.Future.value(false), ) as _i4.Future); @override - void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( + _i4.Future saveUserID(String? userId) => (super.noSuchMethod( + Invocation.method( + #saveUserID, + [userId], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + @override + _i4.Future saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod( + Invocation.method( + #saveSignupEpoch, + [signupEpoch], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + @override + void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -707,7 +790,7 @@ class MockPrefs extends _i1.Mock implements _i5.Prefs { returnValueForMissingStub: null, ); @override - void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/electrumx_test.mocks.dart b/test/electrumx_test.mocks.dart index c4a9ae9b2..1cfe9cf4a 100644 --- a/test/electrumx_test.mocks.dart +++ b/test/electrumx_test.mocks.dart @@ -4,13 +4,11 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'dart:async' as _i3; -import 'dart:ui' as _i8; +import 'dart:ui' as _i7; import 'package:mockito/mockito.dart' as _i1; import 'package:stackwallet/electrumx_rpc/rpc.dart' as _i2; -import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart' - as _i6; -import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i7; +import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i6; import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i5; import 'package:stackwallet/utilities/prefs.dart' as _i4; @@ -232,16 +230,15 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs { returnValueForMissingStub: null, ); @override - _i6.ExchangeRateType get exchangeRateType => (super.noSuchMethod( - Invocation.getter(#exchangeRateType), - returnValue: _i6.ExchangeRateType.estimated, - ) as _i6.ExchangeRateType); + bool get randomizePIN => (super.noSuchMethod( + Invocation.getter(#randomizePIN), + returnValue: false, + ) as bool); @override - set exchangeRateType(_i6.ExchangeRateType? exchangeRateType) => - super.noSuchMethod( + set randomizePIN(bool? randomizePIN) => super.noSuchMethod( Invocation.setter( - #exchangeRateType, - exchangeRateType, + #randomizePIN, + randomizePIN, ), returnValueForMissingStub: null, ); @@ -319,12 +316,12 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs { returnValueForMissingStub: null, ); @override - _i7.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod( + _i6.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod( Invocation.getter(#backupFrequencyType), - returnValue: _i7.BackupFrequencyType.everyTenMinutes, - ) as _i7.BackupFrequencyType); + returnValue: _i6.BackupFrequencyType.everyTenMinutes, + ) as _i6.BackupFrequencyType); @override - set backupFrequencyType(_i7.BackupFrequencyType? backupFrequencyType) => + set backupFrequencyType(_i6.BackupFrequencyType? backupFrequencyType) => super.noSuchMethod( Invocation.setter( #backupFrequencyType, @@ -389,6 +386,74 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs { returnValueForMissingStub: null, ); @override + bool get enableCoinControl => (super.noSuchMethod( + Invocation.getter(#enableCoinControl), + returnValue: false, + ) as bool); + @override + set enableCoinControl(bool? enableCoinControl) => super.noSuchMethod( + Invocation.setter( + #enableCoinControl, + enableCoinControl, + ), + returnValueForMissingStub: null, + ); + @override + bool get enableSystemBrightness => (super.noSuchMethod( + Invocation.getter(#enableSystemBrightness), + returnValue: false, + ) as bool); + @override + set enableSystemBrightness(bool? enableSystemBrightness) => + super.noSuchMethod( + Invocation.setter( + #enableSystemBrightness, + enableSystemBrightness, + ), + returnValueForMissingStub: null, + ); + @override + String get themeId => (super.noSuchMethod( + Invocation.getter(#themeId), + returnValue: '', + ) as String); + @override + set themeId(String? themeId) => super.noSuchMethod( + Invocation.setter( + #themeId, + themeId, + ), + returnValueForMissingStub: null, + ); + @override + String get systemBrightnessLightThemeId => (super.noSuchMethod( + Invocation.getter(#systemBrightnessLightThemeId), + returnValue: '', + ) as String); + @override + set systemBrightnessLightThemeId(String? systemBrightnessLightThemeId) => + super.noSuchMethod( + Invocation.setter( + #systemBrightnessLightThemeId, + systemBrightnessLightThemeId, + ), + returnValueForMissingStub: null, + ); + @override + String get systemBrightnessDarkThemeId => (super.noSuchMethod( + Invocation.getter(#systemBrightnessDarkThemeId), + returnValue: '', + ) as String); + @override + set systemBrightnessDarkThemeId(String? systemBrightnessDarkThemeId) => + super.noSuchMethod( + Invocation.setter( + #systemBrightnessDarkThemeId, + systemBrightnessDarkThemeId, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, @@ -420,7 +485,25 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs { returnValue: _i3.Future.value(false), ) as _i3.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + _i3.Future saveUserID(String? userId) => (super.noSuchMethod( + Invocation.method( + #saveUserID, + [userId], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + @override + _i3.Future saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod( + Invocation.method( + #saveSignupEpoch, + [signupEpoch], + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); + @override + void addListener(_i7.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -428,7 +511,7 @@ class MockPrefs extends _i1.Mock implements _i4.Prefs { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i7.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/formet_test.dart b/test/formet_test.dart index e27293114..cb8333dae 100644 --- a/test/formet_test.dart +++ b/test/formet_test.dart @@ -1,67 +1,7 @@ -import 'package:decimal/decimal.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/format.dart'; void main() { - group("satoshisToAmount", () { - test("12345", () { - expect(Format.satoshisToAmount(12345, coin: Coin.bitcoin), - Decimal.parse("0.00012345")); - }); - - test("100012345", () { - expect(Format.satoshisToAmount(100012345, coin: Coin.bitcoin), - Decimal.parse("1.00012345")); - }); - - test("0", () { - expect(Format.satoshisToAmount(0, coin: Coin.bitcoin), Decimal.zero); - }); - - test("1000000000", () { - expect(Format.satoshisToAmount(1000000000, coin: Coin.bitcoin), - Decimal.parse("10")); - }); - }); - - group("satoshiAmountToPrettyString", () { - const locale = "en_US"; - test("12345", () { - expect(Format.satoshiAmountToPrettyString(12345, locale, Coin.bitcoin), - "0.00012345"); - }); - - test("100012345", () { - expect( - Format.satoshiAmountToPrettyString(100012345, locale, Coin.bitcoin), - "1.00012345"); - }); - - test("123450000", () { - expect( - Format.satoshiAmountToPrettyString(123450000, locale, Coin.bitcoin), - "1.23450000"); - }); - - test("1230045000", () { - expect( - Format.satoshiAmountToPrettyString(1230045000, locale, Coin.bitcoin), - "12.30045000"); - }); - - test("1000000000", () { - expect( - Format.satoshiAmountToPrettyString(1000000000, locale, Coin.bitcoin), - "10.00000000"); - }); - - test("0", () { - expect(Format.satoshiAmountToPrettyString(0, locale, Coin.bitcoin), - "0.00000000"); - }); - }); - group("extractDateFrom", () { test("1614578400", () { expect(Format.extractDateFrom(1614578400, localized: false), diff --git a/test/hive/db_test.dart b/test/hive/db_test.dart index a87f568bd..2ea60bd52 100644 --- a/test/hive/db_test.dart +++ b/test/hive/db_test.dart @@ -1,6 +1,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:hive_test/hive_test.dart'; -import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; void main() { diff --git a/test/models/type_adapter_tests/lelantus_coin_adapter_test.dart b/test/models/type_adapter_tests/lelantus_coin_adapter_test.dart index 41f5d1232..44c2a1a70 100644 --- a/test/models/type_adapter_tests/lelantus_coin_adapter_test.dart +++ b/test/models/type_adapter_tests/lelantus_coin_adapter_test.dart @@ -84,12 +84,12 @@ void main() { ]); }); - test("get hashcode", () { - final adapter = LelantusCoinAdapter(); - - final result = adapter.hashCode; - expect(result, 9); - }); + // test("get hashcode", () { + // final adapter = LelantusCoinAdapter(); + // + // final result = adapter.hashCode; + // expect(result, 9); + // }); group("compare operator", () { test("is equal one", () { diff --git a/test/models/type_adapter_tests/transactions_model_adapter_test.dart b/test/models/type_adapter_tests/transactions_model_adapter_test.dart index d19446a66..a7c9d0441 100644 --- a/test/models/type_adapter_tests/transactions_model_adapter_test.dart +++ b/test/models/type_adapter_tests/transactions_model_adapter_test.dart @@ -52,12 +52,12 @@ void main() { ]); }); - test("TransactionDataAdapter.hashcode", () { - final adapter = TransactionDataAdapter(); - - final result = adapter.hashCode; - expect(result, 1); - }); + // test("TransactionDataAdapter.hashcode", () { + // final adapter = TransactionDataAdapter(); + // + // final result = adapter.hashCode; + // expect(result, 1); + // }); group("TransactionDataAdapter compare operator", () { test("TransactionDataAdapter is equal one", () { @@ -147,12 +147,12 @@ void main() { ]); }); - test("TransactionChunkAdapter.hashcode", () { - final adapter = TransactionChunkAdapter(); - - final result = adapter.hashCode; - expect(result, 2); - }); + // test("TransactionChunkAdapter.hashcode", () { + // final adapter = TransactionChunkAdapter(); + // + // final result = adapter.hashCode; + // expect(result, 2); + // }); group("TransactionChunkAdapter compare operator", () { test("TransactionChunkAdapter is equal one", () { @@ -377,12 +377,12 @@ void main() { ]); }); - test("TransactionAdapter.hashcode", () { - final adapter = TransactionAdapter(); - - final result = adapter.hashCode; - expect(result, 3); - }); + // test("TransactionAdapter.hashcode", () { + // final adapter = TransactionAdapter(); + // + // final result = adapter.hashCode; + // expect(result, 3); + // }); group("TransactionAdapter compare operator", () { test("TransactionAdapter is equal one", () { @@ -401,7 +401,7 @@ void main() { expect(result, true); }); - test("TransactionAdapteris not equal one", () { + test("TransactionAdapter is not equal one", () { final a = TransactionAdapter(); final b = TransactionDataAdapter(); @@ -517,12 +517,12 @@ void main() { ]); }); - test("InputAdapter.hashcode", () { - final adapter = InputAdapter(); - - final result = adapter.hashCode; - expect(result, 4); - }); + // test("InputAdapter.hashcode", () { + // final adapter = InputAdapter(); + // + // final result = adapter.hashCode; + // expect(result, 4); + // }); group("InputAdapter compare operator", () { test("InputAdapter is equal one", () { @@ -633,12 +633,12 @@ void main() { ]); }); - test("OutputAdapter.hashcode", () { - final adapter = OutputAdapter(); - - final result = adapter.hashCode; - expect(result, 5); - }); + // test("OutputAdapter.hashcode", () { + // final adapter = OutputAdapter(); + // + // final result = adapter.hashCode; + // expect(result, 5); + // }); group("OutputAdapter compare operator", () { test("OutputAdapter is equal one", () { diff --git a/test/models/type_adapter_tests/utxo_model_adapter_test.dart b/test/models/type_adapter_tests/utxo_model_adapter_test.dart index 498044976..9dfd5b0ba 100644 --- a/test/models/type_adapter_tests/utxo_model_adapter_test.dart +++ b/test/models/type_adapter_tests/utxo_model_adapter_test.dart @@ -87,12 +87,12 @@ void main() { ]); }); - test("UtxoDataAdapter.hashcode", () { - final adapter = UtxoDataAdapter(); - - final result = adapter.hashCode; - expect(result, 6); - }); + // test("UtxoDataAdapter.hashcode", () { + // final adapter = UtxoDataAdapter(); + // + // final result = adapter.hashCode; + // expect(result, 6); + // }); group("UtxoDataAdapter compare operator", () { test("UtxoDataAdapter is equal one", () { @@ -238,12 +238,12 @@ void main() { ]); }); - test("UtxoObjectAdapter.hashcode", () { - final adapter = UtxoObjectAdapter(); - - final result = adapter.hashCode; - expect(result, 7); - }); + // test("UtxoObjectAdapter.hashcode", () { + // final adapter = UtxoObjectAdapter(); + // + // final result = adapter.hashCode; + // expect(result, 7); + // }); group("UtxoObjectAdapter compare operator", () { test("UtxoObjectAdapter is equal one", () { @@ -359,12 +359,12 @@ void main() { ]); }); - test("StatusAdapter.hashcode", () { - final adapter = StatusAdapter(); - - final result = adapter.hashCode; - expect(result, 8); - }); + // test("StatusAdapter.hashcode", () { + // final adapter = StatusAdapter(); + // + // final result = adapter.hashCode; + // expect(result, 8); + // }); group("StatusAdapter compare operator", () { test("StatusAdapter is equal one", () { diff --git a/test/notifications/notification_card_test.dart b/test/notifications/notification_card_test.dart index 3ac11c21c..24c23b35a 100644 --- a/test/notifications/notification_card_test.dart +++ b/test/notifications/notification_card_test.dart @@ -1,24 +1,42 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; - import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; import 'package:stackwallet/models/notification_model.dart'; import 'package:stackwallet/notifications/notification_card.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_service.dart'; import 'package:stackwallet/utilities/assets.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import '../sample_data/theme_json.dart'; +import 'notification_card_test.mocks.dart'; + +@GenerateMocks([ + ThemeService, +]) void main() { testWidgets("test notification card", (widgetTester) async { final key = UniqueKey(); + final mockThemeService = MockThemeService(); + final theme = StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ); + + when(mockThemeService.getTheme(themeId: "light")).thenAnswer( + (_) => theme, + ); + final notificationCard = NotificationCard( key: key, notification: NotificationModel( id: 1, title: "notification title", description: "notification description", - iconAssetName: Assets.svg.iconFor(coin: Coin.bitcoin), + iconAssetName: Assets.svg.plus, date: DateTime.parse("1662544771"), walletId: "wallet id", read: true, @@ -27,14 +45,21 @@ void main() { ); await widgetTester.pumpWidget( - MaterialApp( - theme: ThemeData( - extensions: [ - StackColors.fromStackColorTheme(LightColors()), - ], - ), - home: Material( - child: notificationCard, + ProviderScope( + overrides: [ + pThemeService.overrideWithValue(mockThemeService), + ], + child: MaterialApp( + theme: ThemeData( + extensions: [ + StackColors.fromStackColorTheme( + theme, + ), + ], + ), + home: Material( + child: notificationCard, + ), ), ), ); diff --git a/test/notifications/notification_card_test.mocks.dart b/test/notifications/notification_card_test.mocks.dart new file mode 100644 index 000000000..7f60b97c9 --- /dev/null +++ b/test/notifications/notification_card_test.mocks.dart @@ -0,0 +1,122 @@ +// Mocks generated by Mockito 5.3.2 from annotations +// in stackwallet/test/notifications/notification_card_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i5; +import 'dart:typed_data' as _i6; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/db/isar/main_db.dart' as _i2; +import 'package:stackwallet/models/isar/stack_theme.dart' as _i4; +import 'package:stackwallet/themes/theme_service.dart' as _i3; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeMainDB_0 extends _i1.SmartFake implements _i2.MainDB { + _FakeMainDB_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [ThemeService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockThemeService extends _i1.Mock implements _i3.ThemeService { + MockThemeService() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.MainDB get db => (super.noSuchMethod( + Invocation.getter(#db), + returnValue: _FakeMainDB_0( + this, + Invocation.getter(#db), + ), + ) as _i2.MainDB); + @override + List<_i4.StackTheme> get installedThemes => (super.noSuchMethod( + Invocation.getter(#installedThemes), + returnValue: <_i4.StackTheme>[], + ) as List<_i4.StackTheme>); + @override + void init(_i2.MainDB? db) => super.noSuchMethod( + Invocation.method( + #init, + [db], + ), + returnValueForMissingStub: null, + ); + @override + _i5.Future install({required _i6.Uint8List? themeArchiveData}) => + (super.noSuchMethod( + Invocation.method( + #install, + [], + {#themeArchiveData: themeArchiveData}, + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future remove({required String? themeId}) => (super.noSuchMethod( + Invocation.method( + #remove, + [], + {#themeId: themeId}, + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future verifyInstalled({required String? themeId}) => + (super.noSuchMethod( + Invocation.method( + #verifyInstalled, + [], + {#themeId: themeId}, + ), + returnValue: _i5.Future.value(false), + ) as _i5.Future); + @override + _i5.Future> fetchThemes() => (super.noSuchMethod( + Invocation.method( + #fetchThemes, + [], + ), + returnValue: _i5.Future>.value( + <_i3.StackThemeMetaData>[]), + ) as _i5.Future>); + @override + _i5.Future<_i6.Uint8List> fetchTheme( + {required _i3.StackThemeMetaData? themeMetaData}) => + (super.noSuchMethod( + Invocation.method( + #fetchTheme, + [], + {#themeMetaData: themeMetaData}, + ), + returnValue: _i5.Future<_i6.Uint8List>.value(_i6.Uint8List(0)), + ) as _i5.Future<_i6.Uint8List>); + @override + _i4.StackTheme? getTheme({required String? themeId}) => + (super.noSuchMethod(Invocation.method( + #getTheme, + [], + {#themeId: themeId}, + )) as _i4.StackTheme?); +} diff --git a/test/pages/send_view/send_view_test.dart b/test/pages/send_view/send_view_test.dart index 73e5b8623..0759110e0 100644 --- a/test/pages/send_view/send_view_test.dart +++ b/test/pages/send_view/send_view_test.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; import 'package:stackwallet/models/send_view_auto_fill_data.dart'; import 'package:stackwallet/pages/send_view/send_view.dart'; import 'package:stackwallet/providers/providers.dart'; @@ -13,10 +14,12 @@ import 'package:stackwallet/services/locale_service.dart'; import 'package:stackwallet/services/node_service.dart'; import 'package:stackwallet/services/wallets.dart'; import 'package:stackwallet/services/wallets_service.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_service.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/prefs.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; + +import '../../sample_data/theme_json.dart'; import 'send_view_test.mocks.dart'; @GenerateMocks([ @@ -25,6 +28,7 @@ import 'send_view_test.mocks.dart'; NodeService, BitcoinWallet, LocaleService, + ThemeService, Prefs, ], customMocks: [ MockSpec(returnNullOnMissingStub: true), @@ -37,6 +41,7 @@ void main() { final mockNodeService = MockNodeService(); final CoinServiceAPI wallet = MockBitcoinWallet(); final mockLocaleService = MockLocaleService(); + final mockThemeService = MockThemeService(); final mockPrefs = MockPrefs(); when(wallet.coin).thenAnswer((_) => Coin.bitcoin); @@ -50,7 +55,14 @@ void main() { .thenAnswer((realInvocation) => manager); when(mockLocaleService.locale).thenAnswer((_) => "en_US"); + when(mockThemeService.getTheme(themeId: "light")).thenAnswer( + (_) => StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ); when(mockPrefs.currency).thenAnswer((_) => "USD"); + when(mockPrefs.enableCoinControl).thenAnswer((_) => false); when(wallet.validateAddress("send to address")) .thenAnswer((realInvocation) => true); @@ -64,13 +76,17 @@ void main() { localeServiceChangeNotifierProvider .overrideWithValue(mockLocaleService), prefsChangeNotifierProvider.overrideWithValue(mockPrefs), + pThemeService.overrideWithValue(mockThemeService), // previewTxButtonStateProvider ], child: MaterialApp( theme: ThemeData( extensions: [ StackColors.fromStackColorTheme( - LightColors(), + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), ), ], ), @@ -84,6 +100,8 @@ void main() { ), ); + await widgetTester.pumpAndSettle(); + expect(find.text("Send to"), findsOneWidget); expect(find.text("Amount"), findsOneWidget); expect(find.text("Note (optional)"), findsOneWidget); @@ -98,6 +116,7 @@ void main() { final CoinServiceAPI wallet = MockBitcoinWallet(); final mockLocaleService = MockLocaleService(); final mockPrefs = MockPrefs(); + final mockThemeService = MockThemeService(); when(wallet.coin).thenAnswer((_) => Coin.bitcoin); when(wallet.walletName).thenAnswer((_) => "some wallet"); @@ -111,8 +130,15 @@ void main() { when(mockLocaleService.locale).thenAnswer((_) => "en_US"); when(mockPrefs.currency).thenAnswer((_) => "USD"); + when(mockPrefs.enableCoinControl).thenAnswer((_) => false); when(wallet.validateAddress("send to address")) .thenAnswer((realInvocation) => false); + when(mockThemeService.getTheme(themeId: "light")).thenAnswer( + (_) => StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ); // when(manager.isOwnAddress("send to address")) // .thenAnswer((realInvocation) => Future(() => true)); @@ -127,13 +153,17 @@ void main() { localeServiceChangeNotifierProvider .overrideWithValue(mockLocaleService), prefsChangeNotifierProvider.overrideWithValue(mockPrefs), + pThemeService.overrideWithValue(mockThemeService) // previewTxButtonStateProvider ], child: MaterialApp( theme: ThemeData( extensions: [ StackColors.fromStackColorTheme( - LightColors(), + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), ), ], ), @@ -147,6 +177,8 @@ void main() { ), ); + await widgetTester.pumpAndSettle(); + expect(find.text("Send to"), findsOneWidget); expect(find.text("Amount"), findsOneWidget); expect(find.text("Note (optional)"), findsOneWidget); diff --git a/test/pages/send_view/send_view_test.mocks.dart b/test/pages/send_view/send_view_test.mocks.dart index b5a3e4e63..8a33fcb87 100644 --- a/test/pages/send_view/send_view_test.mocks.dart +++ b/test/pages/send_view/send_view_test.mocks.dart @@ -3,34 +3,44 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i16; -import 'dart:ui' as _i18; +import 'dart:async' as _i23; +import 'dart:typed_data' as _i30; +import 'dart:ui' as _i25; -import 'package:decimal/decimal.dart' as _i10; +import 'package:bip32/bip32.dart' as _i17; +import 'package:bip47/bip47.dart' as _i19; +import 'package:bitcoindart/bitcoindart.dart' as _i14; import 'package:flutter/foundation.dart' as _i4; import 'package:flutter_riverpod/flutter_riverpod.dart' as _i5; import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i12; -import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i11; -import 'package:stackwallet/models/models.dart' as _i9; -import 'package:stackwallet/models/node_model.dart' as _i19; -import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart' - as _i23; -import 'package:stackwallet/services/coins/bitcoin/bitcoin_wallet.dart' as _i20; -import 'package:stackwallet/services/coins/coin_service.dart' as _i13; +import 'package:stackwallet/db/isar/main_db.dart' as _i13; +import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i11; +import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i10; +import 'package:stackwallet/models/balance.dart' as _i12; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i18; +import 'package:stackwallet/models/isar/stack_theme.dart' as _i33; +import 'package:stackwallet/models/node_model.dart' as _i26; +import 'package:stackwallet/models/paymint/fee_object_model.dart' as _i9; +import 'package:stackwallet/models/signing_data.dart' as _i29; +import 'package:stackwallet/services/coins/bitcoin/bitcoin_wallet.dart' as _i27; +import 'package:stackwallet/services/coins/coin_service.dart' as _i20; import 'package:stackwallet/services/coins/manager.dart' as _i6; -import 'package:stackwallet/services/locale_service.dart' as _i21; +import 'package:stackwallet/services/locale_service.dart' as _i31; import 'package:stackwallet/services/node_service.dart' as _i3; import 'package:stackwallet/services/transaction_notification_tracker.dart' as _i8; -import 'package:stackwallet/services/wallets.dart' as _i14; +import 'package:stackwallet/services/wallets.dart' as _i21; import 'package:stackwallet/services/wallets_service.dart' as _i2; -import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i24; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i15; -import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i22; +import 'package:stackwallet/themes/theme_service.dart' as _i32; +import 'package:stackwallet/utilities/amount/amount.dart' as _i15; +import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i35; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i22; +import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart' as _i28; +import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i34; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' as _i7; -import 'package:stackwallet/utilities/prefs.dart' as _i17; +import 'package:stackwallet/utilities/prefs.dart' as _i24; +import 'package:tuple/tuple.dart' as _i16; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -107,8 +117,8 @@ class _FakeTransactionNotificationTracker_5 extends _i1.SmartFake ); } -class _FakeUtxoData_6 extends _i1.SmartFake implements _i9.UtxoData { - _FakeUtxoData_6( +class _FakeFeeObject_6 extends _i1.SmartFake implements _i9.FeeObject { + _FakeFeeObject_6( Object parent, Invocation parentInvocation, ) : super( @@ -117,8 +127,8 @@ class _FakeUtxoData_6 extends _i1.SmartFake implements _i9.UtxoData { ); } -class _FakeDecimal_7 extends _i1.SmartFake implements _i10.Decimal { - _FakeDecimal_7( +class _FakeElectrumX_7 extends _i1.SmartFake implements _i10.ElectrumX { + _FakeElectrumX_7( Object parent, Invocation parentInvocation, ) : super( @@ -127,8 +137,9 @@ class _FakeDecimal_7 extends _i1.SmartFake implements _i10.Decimal { ); } -class _FakeFeeObject_8 extends _i1.SmartFake implements _i9.FeeObject { - _FakeFeeObject_8( +class _FakeCachedElectrumX_8 extends _i1.SmartFake + implements _i11.CachedElectrumX { + _FakeCachedElectrumX_8( Object parent, Invocation parentInvocation, ) : super( @@ -137,9 +148,8 @@ class _FakeFeeObject_8 extends _i1.SmartFake implements _i9.FeeObject { ); } -class _FakeTransactionData_9 extends _i1.SmartFake - implements _i9.TransactionData { - _FakeTransactionData_9( +class _FakeBalance_9 extends _i1.SmartFake implements _i12.Balance { + _FakeBalance_9( Object parent, Invocation parentInvocation, ) : super( @@ -148,8 +158,8 @@ class _FakeTransactionData_9 extends _i1.SmartFake ); } -class _FakeElectrumX_10 extends _i1.SmartFake implements _i11.ElectrumX { - _FakeElectrumX_10( +class _FakeMainDB_10 extends _i1.SmartFake implements _i13.MainDB { + _FakeMainDB_10( Object parent, Invocation parentInvocation, ) : super( @@ -158,9 +168,8 @@ class _FakeElectrumX_10 extends _i1.SmartFake implements _i11.ElectrumX { ); } -class _FakeCachedElectrumX_11 extends _i1.SmartFake - implements _i12.CachedElectrumX { - _FakeCachedElectrumX_11( +class _FakeNetworkType_11 extends _i1.SmartFake implements _i14.NetworkType { + _FakeNetworkType_11( Object parent, Invocation parentInvocation, ) : super( @@ -170,7 +179,7 @@ class _FakeCachedElectrumX_11 extends _i1.SmartFake } class _FakeElectrumXNode_12 extends _i1.SmartFake - implements _i11.ElectrumXNode { + implements _i10.ElectrumXNode { _FakeElectrumXNode_12( Object parent, Invocation parentInvocation, @@ -180,9 +189,60 @@ class _FakeElectrumXNode_12 extends _i1.SmartFake ); } -class _FakeCoinServiceAPI_13 extends _i1.SmartFake - implements _i13.CoinServiceAPI { - _FakeCoinServiceAPI_13( +class _FakeAmount_13 extends _i1.SmartFake implements _i15.Amount { + _FakeAmount_13( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeTuple2_14 extends _i1.SmartFake + implements _i16.Tuple2 { + _FakeTuple2_14( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeBIP32_15 extends _i1.SmartFake implements _i17.BIP32 { + _FakeBIP32_15( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAddress_16 extends _i1.SmartFake implements _i18.Address { + _FakeAddress_16( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePaymentCode_17 extends _i1.SmartFake implements _i19.PaymentCode { + _FakePaymentCode_17( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeCoinServiceAPI_18 extends _i1.SmartFake + implements _i20.CoinServiceAPI { + _FakeCoinServiceAPI_18( Object parent, Invocation parentInvocation, ) : super( @@ -194,7 +254,7 @@ class _FakeCoinServiceAPI_13 extends _i1.SmartFake /// A class which mocks [Wallets]. /// /// See the documentation for Mockito's code generation for more information. -class MockWallets extends _i1.Mock implements _i14.Wallets { +class MockWallets extends _i1.Mock implements _i21.Wallets { MockWallets() { _i1.throwOnMissingStub(this); } @@ -261,7 +321,7 @@ class MockWallets extends _i1.Mock implements _i14.Wallets { returnValueForMissingStub: null, ); @override - List getWalletIdsFor({required _i15.Coin? coin}) => + List getWalletIdsFor({required _i22.Coin? coin}) => (super.noSuchMethod( Invocation.method( #getWalletIdsFor, @@ -271,15 +331,28 @@ class MockWallets extends _i1.Mock implements _i14.Wallets { returnValue: [], ) as List); @override - Map<_i15.Coin, List<_i5.ChangeNotifierProvider<_i6.Manager>>> + List<_i16.Tuple2<_i22.Coin, List<_i5.ChangeNotifierProvider<_i6.Manager>>>> getManagerProvidersByCoin() => (super.noSuchMethod( Invocation.method( #getManagerProvidersByCoin, [], ), - returnValue: <_i15.Coin, - List<_i5.ChangeNotifierProvider<_i6.Manager>>>{}, - ) as Map<_i15.Coin, List<_i5.ChangeNotifierProvider<_i6.Manager>>>); + returnValue: < + _i16.Tuple2<_i22.Coin, + List<_i5.ChangeNotifierProvider<_i6.Manager>>>>[], + ) as List< + _i16.Tuple2<_i22.Coin, + List<_i5.ChangeNotifierProvider<_i6.Manager>>>>); + @override + List<_i5.ChangeNotifierProvider<_i6.Manager>> getManagerProvidersForCoin( + _i22.Coin? coin) => + (super.noSuchMethod( + Invocation.method( + #getManagerProvidersForCoin, + [coin], + ), + returnValue: <_i5.ChangeNotifierProvider<_i6.Manager>>[], + ) as List<_i5.ChangeNotifierProvider<_i6.Manager>>); @override _i5.ChangeNotifierProvider<_i6.Manager> getManagerProvider( String? walletId) => @@ -336,17 +409,17 @@ class MockWallets extends _i1.Mock implements _i14.Wallets { returnValueForMissingStub: null, ); @override - _i16.Future load(_i17.Prefs? prefs) => (super.noSuchMethod( + _i23.Future load(_i24.Prefs? prefs) => (super.noSuchMethod( Invocation.method( #load, [prefs], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future loadAfterStackRestore( - _i17.Prefs? prefs, + _i23.Future loadAfterStackRestore( + _i24.Prefs? prefs, List<_i6.Manager>? managers, ) => (super.noSuchMethod( @@ -357,11 +430,11 @@ class MockWallets extends _i1.Mock implements _i14.Wallets { managers, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -369,7 +442,7 @@ class MockWallets extends _i1.Mock implements _i14.Wallets { returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -395,19 +468,19 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { } @override - _i16.Future> get walletNames => + _i23.Future> get walletNames => (super.noSuchMethod( Invocation.getter(#walletNames), - returnValue: _i16.Future>.value( + returnValue: _i23.Future>.value( {}), - ) as _i16.Future>); + ) as _i23.Future>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i16.Future renameWallet({ + _i23.Future renameWallet({ required String? from, required String? to, required bool? shouldNotifyListeners, @@ -422,13 +495,21 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future addExistingStackWallet({ + Map fetchWalletsData() => (super.noSuchMethod( + Invocation.method( + #fetchWalletsData, + [], + ), + returnValue: {}, + ) as Map); + @override + _i23.Future addExistingStackWallet({ required String? name, required String? walletId, - required _i15.Coin? coin, + required _i22.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -442,13 +523,13 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future addNewWallet({ + _i23.Future addNewWallet({ required String? name, - required _i15.Coin? coin, + required _i22.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -461,46 +542,46 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future> getFavoriteWalletIds() => (super.noSuchMethod( + _i23.Future> getFavoriteWalletIds() => (super.noSuchMethod( Invocation.method( #getFavoriteWalletIds, [], ), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); + returnValue: _i23.Future>.value([]), + ) as _i23.Future>); @override - _i16.Future saveFavoriteWalletIds(List? walletIds) => + _i23.Future saveFavoriteWalletIds(List? walletIds) => (super.noSuchMethod( Invocation.method( #saveFavoriteWalletIds, [walletIds], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future addFavorite(String? walletId) => (super.noSuchMethod( + _i23.Future addFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #addFavorite, [walletId], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future removeFavorite(String? walletId) => (super.noSuchMethod( + _i23.Future removeFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #removeFavorite, [walletId], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future moveFavorite({ + _i23.Future moveFavorite({ required int? fromIndex, required int? toIndex, }) => @@ -513,48 +594,48 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { #toIndex: toIndex, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future checkForDuplicate(String? name) => (super.noSuchMethod( + _i23.Future checkForDuplicate(String? name) => (super.noSuchMethod( Invocation.method( #checkForDuplicate, [name], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future getWalletId(String? walletName) => (super.noSuchMethod( + _i23.Future getWalletId(String? walletName) => (super.noSuchMethod( Invocation.method( #getWalletId, [walletName], ), - returnValue: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future isMnemonicVerified({required String? walletId}) => + _i23.Future isMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #isMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future setMnemonicVerified({required String? walletId}) => + _i23.Future setMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #setMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future deleteWallet( + _i23.Future deleteWallet( String? name, bool? shouldNotifyListeners, ) => @@ -566,20 +647,20 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { shouldNotifyListeners, ], ), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future.value(0), + ) as _i23.Future); @override - _i16.Future refreshWallets(bool? shouldNotifyListeners) => + _i23.Future refreshWallets(bool? shouldNotifyListeners) => (super.noSuchMethod( Invocation.method( #refreshWallets, [shouldNotifyListeners], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -587,7 +668,7 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -629,33 +710,33 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { ), ) as _i7.SecureStorageInterface); @override - List<_i19.NodeModel> get primaryNodes => (super.noSuchMethod( + List<_i26.NodeModel> get primaryNodes => (super.noSuchMethod( Invocation.getter(#primaryNodes), - returnValue: <_i19.NodeModel>[], - ) as List<_i19.NodeModel>); + returnValue: <_i26.NodeModel>[], + ) as List<_i26.NodeModel>); @override - List<_i19.NodeModel> get nodes => (super.noSuchMethod( + List<_i26.NodeModel> get nodes => (super.noSuchMethod( Invocation.getter(#nodes), - returnValue: <_i19.NodeModel>[], - ) as List<_i19.NodeModel>); + returnValue: <_i26.NodeModel>[], + ) as List<_i26.NodeModel>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i16.Future updateDefaults() => (super.noSuchMethod( + _i23.Future updateDefaults() => (super.noSuchMethod( Invocation.method( #updateDefaults, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future setPrimaryNodeFor({ - required _i15.Coin? coin, - required _i19.NodeModel? node, + _i23.Future setPrimaryNodeFor({ + required _i22.Coin? coin, + required _i26.NodeModel? node, bool? shouldNotifyListeners = false, }) => (super.noSuchMethod( @@ -668,44 +749,44 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i19.NodeModel? getPrimaryNodeFor({required _i15.Coin? coin}) => + _i26.NodeModel? getPrimaryNodeFor({required _i22.Coin? coin}) => (super.noSuchMethod(Invocation.method( #getPrimaryNodeFor, [], {#coin: coin}, - )) as _i19.NodeModel?); + )) as _i26.NodeModel?); @override - List<_i19.NodeModel> getNodesFor(_i15.Coin? coin) => (super.noSuchMethod( + List<_i26.NodeModel> getNodesFor(_i22.Coin? coin) => (super.noSuchMethod( Invocation.method( #getNodesFor, [coin], ), - returnValue: <_i19.NodeModel>[], - ) as List<_i19.NodeModel>); + returnValue: <_i26.NodeModel>[], + ) as List<_i26.NodeModel>); @override - _i19.NodeModel? getNodeById({required String? id}) => + _i26.NodeModel? getNodeById({required String? id}) => (super.noSuchMethod(Invocation.method( #getNodeById, [], {#id: id}, - )) as _i19.NodeModel?); + )) as _i26.NodeModel?); @override - List<_i19.NodeModel> failoverNodesFor({required _i15.Coin? coin}) => + List<_i26.NodeModel> failoverNodesFor({required _i22.Coin? coin}) => (super.noSuchMethod( Invocation.method( #failoverNodesFor, [], {#coin: coin}, ), - returnValue: <_i19.NodeModel>[], - ) as List<_i19.NodeModel>); + returnValue: <_i26.NodeModel>[], + ) as List<_i26.NodeModel>); @override - _i16.Future add( - _i19.NodeModel? node, + _i23.Future add( + _i26.NodeModel? node, String? password, bool? shouldNotifyListeners, ) => @@ -718,11 +799,11 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { shouldNotifyListeners, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future delete( + _i23.Future delete( String? id, bool? shouldNotifyListeners, ) => @@ -734,11 +815,11 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { shouldNotifyListeners, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future setEnabledState( + _i23.Future setEnabledState( String? id, bool? enabled, bool? shouldNotifyListeners, @@ -752,12 +833,12 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { shouldNotifyListeners, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future edit( - _i19.NodeModel? editedNode, + _i23.Future edit( + _i26.NodeModel? editedNode, String? password, bool? shouldNotifyListeners, ) => @@ -770,20 +851,20 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { shouldNotifyListeners, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future updateCommunityNodes() => (super.noSuchMethod( + _i23.Future updateCommunityNodes() => (super.noSuchMethod( Invocation.method( #updateCommunityNodes, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -791,7 +872,7 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -819,13 +900,13 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { /// A class which mocks [BitcoinWallet]. /// /// See the documentation for Mockito's code generation for more information. -class MockBitcoinWallet extends _i1.Mock implements _i20.BitcoinWallet { +class MockBitcoinWallet extends _i1.Mock implements _i27.BitcoinWallet { MockBitcoinWallet() { _i1.throwOnMissingStub(this); } @override - set timer(_i16.Timer? _timer) => super.noSuchMethod( + set timer(_i23.Timer? _timer) => super.noSuchMethod( Invocation.setter( #timer, _timer, @@ -850,19 +931,6 @@ class MockBitcoinWallet extends _i1.Mock implements _i20.BitcoinWallet { returnValueForMissingStub: null, ); @override - List<_i9.UtxoObject> get outputsList => (super.noSuchMethod( - Invocation.getter(#outputsList), - returnValue: <_i9.UtxoObject>[], - ) as List<_i9.UtxoObject>); - @override - set outputsList(List<_i9.UtxoObject>? _outputsList) => super.noSuchMethod( - Invocation.setter( - #outputsList, - _outputsList, - ), - returnValueForMissingStub: null, - ); - @override bool get longMutex => (super.noSuchMethod( Invocation.getter(#longMutex), returnValue: false, @@ -889,14 +957,6 @@ class MockBitcoinWallet extends _i1.Mock implements _i20.BitcoinWallet { returnValueForMissingStub: null, ); @override - set cachedTxData(_i9.TransactionData? _cachedTxData) => super.noSuchMethod( - Invocation.setter( - #cachedTxData, - _cachedTxData, - ), - returnValueForMissingStub: null, - ); - @override bool get isActive => (super.noSuchMethod( Invocation.getter(#isActive), returnValue: false, @@ -923,104 +983,74 @@ class MockBitcoinWallet extends _i1.Mock implements _i20.BitcoinWallet { returnValue: false, ) as bool); @override - _i15.Coin get coin => (super.noSuchMethod( + _i22.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i15.Coin.bitcoin, - ) as _i15.Coin); + returnValue: _i22.Coin.bitcoin, + ) as _i22.Coin); @override - _i16.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); + _i23.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i23.Future>.value(<_i18.UTXO>[]), + ) as _i23.Future>); @override - _i16.Future<_i9.UtxoData> get utxoData => (super.noSuchMethod( - Invocation.getter(#utxoData), - returnValue: _i16.Future<_i9.UtxoData>.value(_FakeUtxoData_6( - this, - Invocation.getter(#utxoData), - )), - ) as _i16.Future<_i9.UtxoData>); - @override - _i16.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), + _i23.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i16.Future>.value(<_i9.UtxoObject>[]), - ) as _i16.Future>); + _i23.Future>.value(<_i18.Transaction>[]), + ) as _i23.Future>); @override - _i16.Future<_i10.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i16.Future<_i10.Decimal>.value(_FakeDecimal_7( - this, - Invocation.getter(#availableBalance), - )), - ) as _i16.Future<_i10.Decimal>); - @override - _i16.Future<_i10.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i16.Future<_i10.Decimal>.value(_FakeDecimal_7( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i16.Future<_i10.Decimal>); - @override - _i16.Future<_i10.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i16.Future<_i10.Decimal>.value(_FakeDecimal_7( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i16.Future<_i10.Decimal>); - @override - _i16.Future<_i10.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i16.Future<_i10.Decimal>.value(_FakeDecimal_7( - this, - Invocation.getter(#totalBalance), - )), - ) as _i16.Future<_i10.Decimal>); - @override - _i16.Future get currentReceivingAddress => (super.noSuchMethod( + _i23.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future get currentLegacyReceivingAddress => (super.noSuchMethod( - Invocation.getter(#currentLegacyReceivingAddress), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + _i23.Future get currentChangeAddress => (super.noSuchMethod( + Invocation.getter(#currentChangeAddress), + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future get currentReceivingAddressP2SH => (super.noSuchMethod( - Invocation.getter(#currentReceivingAddressP2SH), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + _i23.Future get currentChangeAddressP2PKH => (super.noSuchMethod( + Invocation.getter(#currentChangeAddressP2PKH), + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override bool get hasCalledExit => (super.noSuchMethod( Invocation.getter(#hasCalledExit), returnValue: false, ) as bool); @override - _i16.Future<_i9.FeeObject> get fees => (super.noSuchMethod( + _i23.Future<_i9.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i16.Future<_i9.FeeObject>.value(_FakeFeeObject_8( + returnValue: _i23.Future<_i9.FeeObject>.value(_FakeFeeObject_6( this, Invocation.getter(#fees), )), - ) as _i16.Future<_i9.FeeObject>); + ) as _i23.Future<_i9.FeeObject>); @override - _i16.Future get maxFee => (super.noSuchMethod( + _i23.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future.value(0), + ) as _i23.Future); @override - _i16.Future> get mnemonic => (super.noSuchMethod( + _i23.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); + returnValue: _i23.Future>.value([]), + ) as _i23.Future>); @override - _i16.Future get chainHeight => (super.noSuchMethod( + _i23.Future get mnemonicString => (super.noSuchMethod( + Invocation.getter(#mnemonicString), + returnValue: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future get chainHeight => (super.noSuchMethod( Invocation.getter(#chainHeight), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future.value(0), + ) as _i23.Future); @override int get storedChainHeight => (super.noSuchMethod( Invocation.getter(#storedChainHeight), @@ -1050,15 +1080,6 @@ class MockBitcoinWallet extends _i1.Mock implements _i20.BitcoinWallet { returnValue: false, ) as bool); @override - _i16.Future<_i9.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), - returnValue: - _i16.Future<_i9.TransactionData>.value(_FakeTransactionData_9( - this, - Invocation.getter(#transactionData), - )), - ) as _i16.Future<_i9.TransactionData>); - @override String get walletId => (super.noSuchMethod( Invocation.getter(#walletId), returnValue: '', @@ -1077,21 +1098,34 @@ class MockBitcoinWallet extends _i1.Mock implements _i20.BitcoinWallet { returnValueForMissingStub: null, ); @override - _i11.ElectrumX get electrumXClient => (super.noSuchMethod( + _i10.ElectrumX get electrumXClient => (super.noSuchMethod( Invocation.getter(#electrumXClient), - returnValue: _FakeElectrumX_10( + returnValue: _FakeElectrumX_7( this, Invocation.getter(#electrumXClient), ), - ) as _i11.ElectrumX); + ) as _i10.ElectrumX); @override - _i12.CachedElectrumX get cachedElectrumXClient => (super.noSuchMethod( + _i11.CachedElectrumX get cachedElectrumXClient => (super.noSuchMethod( Invocation.getter(#cachedElectrumXClient), - returnValue: _FakeCachedElectrumX_11( + returnValue: _FakeCachedElectrumX_8( this, Invocation.getter(#cachedElectrumXClient), ), - ) as _i12.CachedElectrumX); + ) as _i11.CachedElectrumX); + @override + _i12.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_9( + this, + Invocation.getter(#balance), + ), + ) as _i12.Balance); + @override + _i23.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override set onIsActiveWalletChanged(void Function(bool)? _onIsActiveWalletChanged) => super.noSuchMethod( @@ -1102,38 +1136,44 @@ class MockBitcoinWallet extends _i1.Mock implements _i20.BitcoinWallet { returnValueForMissingStub: null, ); @override - _i16.Future exit() => (super.noSuchMethod( + _i13.MainDB get db => (super.noSuchMethod( + Invocation.getter(#db), + returnValue: _FakeMainDB_10( + this, + Invocation.getter(#db), + ), + ) as _i13.MainDB); + @override + _i14.NetworkType get networkType => (super.noSuchMethod( + Invocation.getter(#networkType), + returnValue: _FakeNetworkType_11( + this, + Invocation.getter(#networkType), + ), + ) as _i14.NetworkType); + @override + _i23.Future exit() => (super.noSuchMethod( Invocation.method( #exit, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future updateStoredChainHeight({required int? newHeight}) => - (super.noSuchMethod( - Invocation.method( - #updateStoredChainHeight, - [], - {#newHeight: newHeight}, - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i20.DerivePathType addressType({required String? address}) => + _i28.DerivePathType addressType({required String? address}) => (super.noSuchMethod( Invocation.method( #addressType, [], {#address: address}, ), - returnValue: _i20.DerivePathType.bip44, - ) as _i20.DerivePathType); + returnValue: _i28.DerivePathType.bip44, + ) as _i28.DerivePathType); @override - _i16.Future recoverFromMnemonic({ + _i23.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -1144,55 +1184,55 @@ class MockBitcoinWallet extends _i1.Mock implements _i20.BitcoinWallet { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future getTransactionCacheEarly(List? allAddresses) => + _i23.Future getTransactionCacheEarly(List? allAddresses) => (super.noSuchMethod( Invocation.method( #getTransactionCacheEarly, [allAddresses], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future refreshIfThereIsNewData() => (super.noSuchMethod( + _i23.Future refreshIfThereIsNewData() => (super.noSuchMethod( Invocation.method( #refreshIfThereIsNewData, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future getAllTxsToWatch(_i9.TransactionData? txData) => - (super.noSuchMethod( + _i23.Future getAllTxsToWatch() => (super.noSuchMethod( Invocation.method( #getAllTxsToWatch, - [txData], + [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future refresh() => (super.noSuchMethod( + _i23.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future> prepareSend({ + _i23.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i15.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -1201,49 +1241,31 @@ class MockBitcoinWallet extends _i1.Mock implements _i20.BitcoinWallet { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i23.Future>.value({}), + ) as _i23.Future>); @override - _i16.Future confirmSend({required Map? txData}) => + _i23.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); - @override - _i16.Future testNetworkConnection() => (super.noSuchMethod( + _i23.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override void startNetworkAlivePinging() => super.noSuchMethod( Invocation.method( @@ -1261,33 +1283,33 @@ class MockBitcoinWallet extends _i1.Mock implements _i20.BitcoinWallet { returnValueForMissingStub: null, ); @override - _i16.Future initializeNew() => (super.noSuchMethod( + _i23.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future initializeExisting() => (super.noSuchMethod( + _i23.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future updateSentCachedTxData(Map? txData) => + _i23.Future updateSentCachedTxData(Map? txData) => (super.noSuchMethod( Invocation.method( #updateSentCachedTxData, [txData], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -1297,112 +1319,70 @@ class MockBitcoinWallet extends _i1.Mock implements _i20.BitcoinWallet { returnValue: false, ) as bool); @override - _i16.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i23.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future<_i11.ElectrumXNode> getCurrentNode() => (super.noSuchMethod( + _i23.Future<_i10.ElectrumXNode> getCurrentNode() => (super.noSuchMethod( Invocation.method( #getCurrentNode, [], ), returnValue: - _i16.Future<_i11.ElectrumXNode>.value(_FakeElectrumXNode_12( + _i23.Future<_i10.ElectrumXNode>.value(_FakeElectrumXNode_12( this, Invocation.method( #getCurrentNode, [], ), )), - ) as _i16.Future<_i11.ElectrumXNode>); + ) as _i23.Future<_i10.ElectrumXNode>); @override - _i16.Future addDerivation({ - required int? chain, - required String? address, - required String? pubKey, - required String? wif, - required _i20.DerivePathType? derivePathType, - }) => - (super.noSuchMethod( - Invocation.method( - #addDerivation, - [], - { - #chain: chain, - #address: address, - #pubKey: pubKey, - #wif: wif, - #derivePathType: derivePathType, - }, - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i16.Future addDerivations({ - required int? chain, - required _i20.DerivePathType? derivePathType, - required Map? derivationsToAdd, - }) => - (super.noSuchMethod( - Invocation.method( - #addDerivations, - [], - { - #chain: chain, - #derivePathType: derivePathType, - #derivationsToAdd: derivationsToAdd, - }, - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i16.Future getTxCount({required String? address}) => - (super.noSuchMethod( - Invocation.method( - #getTxCount, - [], - {#address: address}, - ), - returnValue: _i16.Future.value(0), - ) as _i16.Future); - @override - _i16.Future checkCurrentReceivingAddressesForTransactions() => - (super.noSuchMethod( - Invocation.method( - #checkCurrentReceivingAddressesForTransactions, - [], - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i16.Future checkCurrentChangeAddressesForTransactions() => - (super.noSuchMethod( - Invocation.method( - #checkCurrentChangeAddressesForTransactions, - [], - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i16.Future>> fastFetch( + _i23.Future>> fastFetch( List? allTxHashes) => (super.noSuchMethod( Invocation.method( #fastFetch, [allTxHashes], ), - returnValue: _i16.Future>>.value( + returnValue: _i23.Future>>.value( >[]), - ) as _i16.Future>>); + ) as _i23.Future>>); + @override + _i23.Future getTxCount({required String? address}) => + (super.noSuchMethod( + Invocation.method( + #getTxCount, + [], + {#address: address}, + ), + returnValue: _i23.Future.value(0), + ) as _i23.Future); + @override + _i23.Future checkCurrentReceivingAddressesForTransactions() => + (super.noSuchMethod( + Invocation.method( + #checkCurrentReceivingAddressesForTransactions, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future checkCurrentChangeAddressesForTransactions() => + (super.noSuchMethod( + Invocation.method( + #checkCurrentChangeAddressesForTransactions, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override int estimateTxFee({ required int? vSize, @@ -1420,42 +1400,42 @@ class MockBitcoinWallet extends _i1.Mock implements _i20.BitcoinWallet { returnValue: 0, ) as int); @override - dynamic coinSelection( - int? satoshiAmountToSend, - int? selectedTxFeeRate, - String? _recipientAddress, - bool? isSendAll, { + dynamic coinSelection({ + required int? satoshiAmountToSend, + required int? selectedTxFeeRate, + required String? recipientAddress, + required bool? coinControl, + required bool? isSendAll, int? additionalOutputs = 0, - List<_i9.UtxoObject>? utxos, + List<_i18.UTXO>? utxos, }) => super.noSuchMethod(Invocation.method( #coinSelection, - [ - satoshiAmountToSend, - selectedTxFeeRate, - _recipientAddress, - isSendAll, - ], + [], { + #satoshiAmountToSend: satoshiAmountToSend, + #selectedTxFeeRate: selectedTxFeeRate, + #recipientAddress: recipientAddress, + #coinControl: coinControl, + #isSendAll: isSendAll, #additionalOutputs: additionalOutputs, #utxos: utxos, }, )); @override - _i16.Future> fetchBuildTxData( - List<_i9.UtxoObject>? utxosToUse) => + _i23.Future> fetchBuildTxData( + List<_i18.UTXO>? utxosToUse) => (super.noSuchMethod( Invocation.method( #fetchBuildTxData, [utxosToUse], ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i23.Future>.value(<_i29.SigningData>[]), + ) as _i23.Future>); @override - _i16.Future> buildTransaction({ - required List<_i9.UtxoObject>? utxosToUse, - required Map? utxoSigningData, + _i23.Future> buildTransaction({ + required List<_i29.SigningData>? utxoSigningData, required List? recipients, required List? satoshiAmounts, }) => @@ -1464,17 +1444,16 @@ class MockBitcoinWallet extends _i1.Mock implements _i20.BitcoinWallet { #buildTransaction, [], { - #utxosToUse: utxosToUse, #utxoSigningData: utxoSigningData, #recipients: recipients, #satoshiAmounts: satoshiAmounts, }, ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i23.Future>.value({}), + ) as _i23.Future>); @override - _i16.Future fullRescan( + _i23.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -1486,26 +1465,35 @@ class MockBitcoinWallet extends _i1.Mock implements _i20.BitcoinWallet { maxNumberOfIndexesToCheck, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future estimateFeeFor( - int? satoshiAmount, + _i23.Future<_i15.Amount> estimateFeeFor( + _i15.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future<_i15.Amount>.value(_FakeAmount_13( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i23.Future<_i15.Amount>); @override - int roughFeeEstimate( + _i15.Amount roughFeeEstimate( int? inputCount, int? outputCount, int? feeRatePerKB, @@ -1519,30 +1507,668 @@ class MockBitcoinWallet extends _i1.Mock implements _i20.BitcoinWallet { feeRatePerKB, ], ), - returnValue: 0, - ) as int); + returnValue: _FakeAmount_13( + this, + Invocation.method( + #roughFeeEstimate, + [ + inputCount, + outputCount, + feeRatePerKB, + ], + ), + ), + ) as _i15.Amount); @override - int sweepAllEstimate(int? feeRate) => (super.noSuchMethod( + _i23.Future<_i15.Amount> sweepAllEstimate(int? feeRate) => + (super.noSuchMethod( Invocation.method( #sweepAllEstimate, [feeRate], ), - returnValue: 0, - ) as int); + returnValue: _i23.Future<_i15.Amount>.value(_FakeAmount_13( + this, + Invocation.method( + #sweepAllEstimate, + [feeRate], + ), + )), + ) as _i23.Future<_i15.Amount>); @override - _i16.Future generateNewAddress() => (super.noSuchMethod( + _i23.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); + @override + void initCache( + String? walletId, + _i22.Coin? coin, + ) => + super.noSuchMethod( + Invocation.method( + #initCache, + [ + walletId, + coin, + ], + ), + returnValueForMissingStub: null, + ); + @override + _i23.Future updateCachedId(String? id) => (super.noSuchMethod( + Invocation.method( + #updateCachedId, + [id], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + int getCachedChainHeight() => (super.noSuchMethod( + Invocation.method( + #getCachedChainHeight, + [], + ), + returnValue: 0, + ) as int); + @override + _i23.Future updateCachedChainHeight(int? height) => (super.noSuchMethod( + Invocation.method( + #updateCachedChainHeight, + [height], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + bool getCachedIsFavorite() => (super.noSuchMethod( + Invocation.method( + #getCachedIsFavorite, + [], + ), + returnValue: false, + ) as bool); + @override + _i23.Future updateCachedIsFavorite(bool? isFavorite) => + (super.noSuchMethod( + Invocation.method( + #updateCachedIsFavorite, + [isFavorite], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i12.Balance getCachedBalance() => (super.noSuchMethod( + Invocation.method( + #getCachedBalance, + [], + ), + returnValue: _FakeBalance_9( + this, + Invocation.method( + #getCachedBalance, + [], + ), + ), + ) as _i12.Balance); + @override + _i23.Future updateCachedBalance(_i12.Balance? balance) => + (super.noSuchMethod( + Invocation.method( + #updateCachedBalance, + [balance], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i12.Balance getCachedBalanceSecondary() => (super.noSuchMethod( + Invocation.method( + #getCachedBalanceSecondary, + [], + ), + returnValue: _FakeBalance_9( + this, + Invocation.method( + #getCachedBalanceSecondary, + [], + ), + ), + ) as _i12.Balance); + @override + _i23.Future updateCachedBalanceSecondary(_i12.Balance? balance) => + (super.noSuchMethod( + Invocation.method( + #updateCachedBalanceSecondary, + [balance], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + List getWalletTokenContractAddresses() => (super.noSuchMethod( + Invocation.method( + #getWalletTokenContractAddresses, + [], + ), + returnValue: [], + ) as List); + @override + _i23.Future updateWalletTokenContractAddresses( + List? contractAddresses) => + (super.noSuchMethod( + Invocation.method( + #updateWalletTokenContractAddresses, + [contractAddresses], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + void initWalletDB({_i13.MainDB? mockableOverride}) => super.noSuchMethod( + Invocation.method( + #initWalletDB, + [], + {#mockableOverride: mockableOverride}, + ), + returnValueForMissingStub: null, + ); + @override + _i23.Future<_i16.Tuple2<_i18.Transaction, _i18.Address>> parseTransaction( + Map? txData, + dynamic electrumxClient, + List<_i18.Address>? myAddresses, + _i22.Coin? coin, + int? minConfirms, + String? walletId, + ) => + (super.noSuchMethod( + Invocation.method( + #parseTransaction, + [ + txData, + electrumxClient, + myAddresses, + coin, + minConfirms, + walletId, + ], + ), + returnValue: + _i23.Future<_i16.Tuple2<_i18.Transaction, _i18.Address>>.value( + _FakeTuple2_14<_i18.Transaction, _i18.Address>( + this, + Invocation.method( + #parseTransaction, + [ + txData, + electrumxClient, + myAddresses, + coin, + minConfirms, + walletId, + ], + ), + )), + ) as _i23.Future<_i16.Tuple2<_i18.Transaction, _i18.Address>>); + @override + void initPaynymWalletInterface({ + required String? walletId, + required String? walletName, + required _i14.NetworkType? network, + required _i22.Coin? coin, + required _i13.MainDB? db, + required _i10.ElectrumX? electrumXClient, + required _i7.SecureStorageInterface? secureStorage, + required int? dustLimit, + required int? dustLimitP2PKH, + required int? minConfirms, + required _i23.Future Function()? getMnemonicString, + required _i23.Future Function()? getMnemonicPassphrase, + required _i23.Future Function()? getChainHeight, + required _i23.Future Function()? getCurrentChangeAddress, + required int Function({ + required int feeRatePerKB, + required int vSize, + })? + estimateTxFee, + required _i23.Future> Function({ + required String address, + required _i15.Amount amount, + Map? args, + })? + prepareSend, + required _i23.Future Function({required String address})? getTxCount, + required _i23.Future> Function(List<_i18.UTXO>)? + fetchBuildTxData, + required _i23.Future Function()? refresh, + required _i23.Future Function()? checkChangeAddressForTransactions, + }) => + super.noSuchMethod( + Invocation.method( + #initPaynymWalletInterface, + [], + { + #walletId: walletId, + #walletName: walletName, + #network: network, + #coin: coin, + #db: db, + #electrumXClient: electrumXClient, + #secureStorage: secureStorage, + #dustLimit: dustLimit, + #dustLimitP2PKH: dustLimitP2PKH, + #minConfirms: minConfirms, + #getMnemonicString: getMnemonicString, + #getMnemonicPassphrase: getMnemonicPassphrase, + #getChainHeight: getChainHeight, + #getCurrentChangeAddress: getCurrentChangeAddress, + #estimateTxFee: estimateTxFee, + #prepareSend: prepareSend, + #getTxCount: getTxCount, + #fetchBuildTxData: fetchBuildTxData, + #refresh: refresh, + #checkChangeAddressForTransactions: + checkChangeAddressForTransactions, + }, + ), + returnValueForMissingStub: null, + ); + @override + _i23.Future<_i17.BIP32> getBip47BaseNode() => (super.noSuchMethod( + Invocation.method( + #getBip47BaseNode, + [], + ), + returnValue: _i23.Future<_i17.BIP32>.value(_FakeBIP32_15( + this, + Invocation.method( + #getBip47BaseNode, + [], + ), + )), + ) as _i23.Future<_i17.BIP32>); + @override + _i23.Future<_i30.Uint8List> getPrivateKeyForPaynymReceivingAddress({ + required String? paymentCodeString, + required int? index, + }) => + (super.noSuchMethod( + Invocation.method( + #getPrivateKeyForPaynymReceivingAddress, + [], + { + #paymentCodeString: paymentCodeString, + #index: index, + }, + ), + returnValue: _i23.Future<_i30.Uint8List>.value(_i30.Uint8List(0)), + ) as _i23.Future<_i30.Uint8List>); + @override + _i23.Future<_i18.Address> currentReceivingPaynymAddress({ + required _i19.PaymentCode? sender, + required bool? isSegwit, + }) => + (super.noSuchMethod( + Invocation.method( + #currentReceivingPaynymAddress, + [], + { + #sender: sender, + #isSegwit: isSegwit, + }, + ), + returnValue: _i23.Future<_i18.Address>.value(_FakeAddress_16( + this, + Invocation.method( + #currentReceivingPaynymAddress, + [], + { + #sender: sender, + #isSegwit: isSegwit, + }, + ), + )), + ) as _i23.Future<_i18.Address>); + @override + _i23.Future checkCurrentPaynymReceivingAddressForTransactions({ + required _i19.PaymentCode? sender, + required bool? isSegwit, + }) => + (super.noSuchMethod( + Invocation.method( + #checkCurrentPaynymReceivingAddressForTransactions, + [], + { + #sender: sender, + #isSegwit: isSegwit, + }, + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future checkAllCurrentReceivingPaynymAddressesForTransactions() => + (super.noSuchMethod( + Invocation.method( + #checkAllCurrentReceivingPaynymAddressesForTransactions, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future<_i17.BIP32> deriveNotificationBip32Node() => (super.noSuchMethod( + Invocation.method( + #deriveNotificationBip32Node, + [], + ), + returnValue: _i23.Future<_i17.BIP32>.value(_FakeBIP32_15( + this, + Invocation.method( + #deriveNotificationBip32Node, + [], + ), + )), + ) as _i23.Future<_i17.BIP32>); + @override + _i23.Future<_i19.PaymentCode> getPaymentCode({required bool? isSegwit}) => + (super.noSuchMethod( + Invocation.method( + #getPaymentCode, + [], + {#isSegwit: isSegwit}, + ), + returnValue: _i23.Future<_i19.PaymentCode>.value(_FakePaymentCode_17( + this, + Invocation.method( + #getPaymentCode, + [], + {#isSegwit: isSegwit}, + ), + )), + ) as _i23.Future<_i19.PaymentCode>); + @override + _i23.Future<_i30.Uint8List> signWithNotificationKey(_i30.Uint8List? data) => + (super.noSuchMethod( + Invocation.method( + #signWithNotificationKey, + [data], + ), + returnValue: _i23.Future<_i30.Uint8List>.value(_i30.Uint8List(0)), + ) as _i23.Future<_i30.Uint8List>); + @override + _i23.Future signStringWithNotificationKey(String? data) => + (super.noSuchMethod( + Invocation.method( + #signStringWithNotificationKey, + [data], + ), + returnValue: _i23.Future.value(''), + ) as _i23.Future); + @override + _i23.Future> preparePaymentCodeSend({ + required _i19.PaymentCode? paymentCode, + required bool? isSegwit, + required _i15.Amount? amount, + Map? args, + }) => + (super.noSuchMethod( + Invocation.method( + #preparePaymentCodeSend, + [], + { + #paymentCode: paymentCode, + #isSegwit: isSegwit, + #amount: amount, + #args: args, + }, + ), + returnValue: + _i23.Future>.value({}), + ) as _i23.Future>); + @override + _i23.Future<_i18.Address> nextUnusedSendAddressFrom({ + required _i19.PaymentCode? pCode, + required bool? isSegwit, + required _i17.BIP32? privateKeyNode, + int? startIndex = 0, + }) => + (super.noSuchMethod( + Invocation.method( + #nextUnusedSendAddressFrom, + [], + { + #pCode: pCode, + #isSegwit: isSegwit, + #privateKeyNode: privateKeyNode, + #startIndex: startIndex, + }, + ), + returnValue: _i23.Future<_i18.Address>.value(_FakeAddress_16( + this, + Invocation.method( + #nextUnusedSendAddressFrom, + [], + { + #pCode: pCode, + #isSegwit: isSegwit, + #privateKeyNode: privateKeyNode, + #startIndex: startIndex, + }, + ), + )), + ) as _i23.Future<_i18.Address>); + @override + _i23.Future> prepareNotificationTx({ + required int? selectedTxFeeRate, + required String? targetPaymentCodeString, + int? additionalOutputs = 0, + List<_i18.UTXO>? utxos, + }) => + (super.noSuchMethod( + Invocation.method( + #prepareNotificationTx, + [], + { + #selectedTxFeeRate: selectedTxFeeRate, + #targetPaymentCodeString: targetPaymentCodeString, + #additionalOutputs: additionalOutputs, + #utxos: utxos, + }, + ), + returnValue: + _i23.Future>.value({}), + ) as _i23.Future>); + @override + _i23.Future broadcastNotificationTx( + {required Map? preparedTx}) => + (super.noSuchMethod( + Invocation.method( + #broadcastNotificationTx, + [], + {#preparedTx: preparedTx}, + ), + returnValue: _i23.Future.value(''), + ) as _i23.Future); + @override + _i23.Future hasConnected(String? paymentCodeString) => + (super.noSuchMethod( + Invocation.method( + #hasConnected, + [paymentCodeString], + ), + returnValue: _i23.Future.value(false), + ) as _i23.Future); + @override + _i23.Future<_i19.PaymentCode?> unBlindedPaymentCodeFromTransaction( + {required _i18.Transaction? transaction}) => + (super.noSuchMethod( + Invocation.method( + #unBlindedPaymentCodeFromTransaction, + [], + {#transaction: transaction}, + ), + returnValue: _i23.Future<_i19.PaymentCode?>.value(), + ) as _i23.Future<_i19.PaymentCode?>); + @override + _i23.Future<_i19.PaymentCode?> unBlindedPaymentCodeFromTransactionBad( + {required _i18.Transaction? transaction}) => + (super.noSuchMethod( + Invocation.method( + #unBlindedPaymentCodeFromTransactionBad, + [], + {#transaction: transaction}, + ), + returnValue: _i23.Future<_i19.PaymentCode?>.value(), + ) as _i23.Future<_i19.PaymentCode?>); + @override + _i23.Future> + getAllPaymentCodesFromNotificationTransactions() => (super.noSuchMethod( + Invocation.method( + #getAllPaymentCodesFromNotificationTransactions, + [], + ), + returnValue: + _i23.Future>.value(<_i19.PaymentCode>[]), + ) as _i23.Future>); + @override + _i23.Future checkForNotificationTransactionsTo( + Set? otherCodeStrings) => + (super.noSuchMethod( + Invocation.method( + #checkForNotificationTransactionsTo, + [otherCodeStrings], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future restoreAllHistory({ + required int? maxUnusedAddressGap, + required int? maxNumberOfIndexesToCheck, + required Set? paymentCodeStrings, + }) => + (super.noSuchMethod( + Invocation.method( + #restoreAllHistory, + [], + { + #maxUnusedAddressGap: maxUnusedAddressGap, + #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + #paymentCodeStrings: paymentCodeStrings, + }, + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future restoreHistoryWith({ + required _i19.PaymentCode? other, + required bool? checkSegwitAsWell, + required int? maxUnusedAddressGap, + required int? maxNumberOfIndexesToCheck, + }) => + (super.noSuchMethod( + Invocation.method( + #restoreHistoryWith, + [], + { + #other: other, + #checkSegwitAsWell: checkSegwitAsWell, + #maxUnusedAddressGap: maxUnusedAddressGap, + #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + }, + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future<_i18.Address> getMyNotificationAddress() => (super.noSuchMethod( + Invocation.method( + #getMyNotificationAddress, + [], + ), + returnValue: _i23.Future<_i18.Address>.value(_FakeAddress_16( + this, + Invocation.method( + #getMyNotificationAddress, + [], + ), + )), + ) as _i23.Future<_i18.Address>); + @override + _i23.Future> lookupKey(String? paymentCodeString) => + (super.noSuchMethod( + Invocation.method( + #lookupKey, + [paymentCodeString], + ), + returnValue: _i23.Future>.value([]), + ) as _i23.Future>); + @override + _i23.Future paymentCodeStringByKey(String? key) => + (super.noSuchMethod( + Invocation.method( + #paymentCodeStringByKey, + [key], + ), + returnValue: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future storeCode(String? paymentCodeString) => + (super.noSuchMethod( + Invocation.method( + #storeCode, + [paymentCodeString], + ), + returnValue: _i23.Future.value(''), + ) as _i23.Future); + @override + void initCoinControlInterface({ + required String? walletId, + required String? walletName, + required _i22.Coin? coin, + required _i13.MainDB? db, + required _i23.Future Function()? getChainHeight, + required _i23.Future Function(_i12.Balance)? refreshedBalanceCallback, + }) => + super.noSuchMethod( + Invocation.method( + #initCoinControlInterface, + [], + { + #walletId: walletId, + #walletName: walletName, + #coin: coin, + #db: db, + #getChainHeight: getChainHeight, + #refreshedBalanceCallback: refreshedBalanceCallback, + }, + ), + returnValueForMissingStub: null, + ); + @override + _i23.Future refreshBalance({bool? notify = false}) => + (super.noSuchMethod( + Invocation.method( + #refreshBalance, + [], + {#notify: notify}, + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); } /// A class which mocks [LocaleService]. /// /// See the documentation for Mockito's code generation for more information. -class MockLocaleService extends _i1.Mock implements _i21.LocaleService { +class MockLocaleService extends _i1.Mock implements _i31.LocaleService { MockLocaleService() { _i1.throwOnMissingStub(this); } @@ -1558,17 +2184,17 @@ class MockLocaleService extends _i1.Mock implements _i21.LocaleService { returnValue: false, ) as bool); @override - _i16.Future loadLocale({bool? notify = true}) => (super.noSuchMethod( + _i23.Future loadLocale({bool? notify = true}) => (super.noSuchMethod( Invocation.method( #loadLocale, [], {#notify: notify}, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -1576,7 +2202,7 @@ class MockLocaleService extends _i1.Mock implements _i21.LocaleService { returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -1601,10 +2227,100 @@ class MockLocaleService extends _i1.Mock implements _i21.LocaleService { ); } +/// A class which mocks [ThemeService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockThemeService extends _i1.Mock implements _i32.ThemeService { + MockThemeService() { + _i1.throwOnMissingStub(this); + } + + @override + _i13.MainDB get db => (super.noSuchMethod( + Invocation.getter(#db), + returnValue: _FakeMainDB_10( + this, + Invocation.getter(#db), + ), + ) as _i13.MainDB); + @override + List<_i33.StackTheme> get installedThemes => (super.noSuchMethod( + Invocation.getter(#installedThemes), + returnValue: <_i33.StackTheme>[], + ) as List<_i33.StackTheme>); + @override + void init(_i13.MainDB? db) => super.noSuchMethod( + Invocation.method( + #init, + [db], + ), + returnValueForMissingStub: null, + ); + @override + _i23.Future install({required _i30.Uint8List? themeArchiveData}) => + (super.noSuchMethod( + Invocation.method( + #install, + [], + {#themeArchiveData: themeArchiveData}, + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future remove({required String? themeId}) => (super.noSuchMethod( + Invocation.method( + #remove, + [], + {#themeId: themeId}, + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future verifyInstalled({required String? themeId}) => + (super.noSuchMethod( + Invocation.method( + #verifyInstalled, + [], + {#themeId: themeId}, + ), + returnValue: _i23.Future.value(false), + ) as _i23.Future); + @override + _i23.Future> fetchThemes() => + (super.noSuchMethod( + Invocation.method( + #fetchThemes, + [], + ), + returnValue: _i23.Future>.value( + <_i32.StackThemeMetaData>[]), + ) as _i23.Future>); + @override + _i23.Future<_i30.Uint8List> fetchTheme( + {required _i32.StackThemeMetaData? themeMetaData}) => + (super.noSuchMethod( + Invocation.method( + #fetchTheme, + [], + {#themeMetaData: themeMetaData}, + ), + returnValue: _i23.Future<_i30.Uint8List>.value(_i30.Uint8List(0)), + ) as _i23.Future<_i30.Uint8List>); + @override + _i33.StackTheme? getTheme({required String? themeId}) => + (super.noSuchMethod(Invocation.method( + #getTheme, + [], + {#themeId: themeId}, + )) as _i33.StackTheme?); +} + /// A class which mocks [Prefs]. /// /// See the documentation for Mockito's code generation for more information. -class MockPrefs extends _i1.Mock implements _i17.Prefs { +class MockPrefs extends _i1.Mock implements _i24.Prefs { MockPrefs() { _i1.throwOnMissingStub(this); } @@ -1660,12 +2376,12 @@ class MockPrefs extends _i1.Mock implements _i17.Prefs { returnValueForMissingStub: null, ); @override - _i22.SyncingType get syncType => (super.noSuchMethod( + _i34.SyncingType get syncType => (super.noSuchMethod( Invocation.getter(#syncType), - returnValue: _i22.SyncingType.currentWalletOnly, - ) as _i22.SyncingType); + returnValue: _i34.SyncingType.currentWalletOnly, + ) as _i34.SyncingType); @override - set syncType(_i22.SyncingType? syncType) => super.noSuchMethod( + set syncType(_i34.SyncingType? syncType) => super.noSuchMethod( Invocation.setter( #syncType, syncType, @@ -1725,16 +2441,15 @@ class MockPrefs extends _i1.Mock implements _i17.Prefs { returnValueForMissingStub: null, ); @override - _i23.ExchangeRateType get exchangeRateType => (super.noSuchMethod( - Invocation.getter(#exchangeRateType), - returnValue: _i23.ExchangeRateType.estimated, - ) as _i23.ExchangeRateType); + bool get randomizePIN => (super.noSuchMethod( + Invocation.getter(#randomizePIN), + returnValue: false, + ) as bool); @override - set exchangeRateType(_i23.ExchangeRateType? exchangeRateType) => - super.noSuchMethod( + set randomizePIN(bool? randomizePIN) => super.noSuchMethod( Invocation.setter( - #exchangeRateType, - exchangeRateType, + #randomizePIN, + randomizePIN, ), returnValueForMissingStub: null, ); @@ -1812,12 +2527,12 @@ class MockPrefs extends _i1.Mock implements _i17.Prefs { returnValueForMissingStub: null, ); @override - _i24.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod( + _i35.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod( Invocation.getter(#backupFrequencyType), - returnValue: _i24.BackupFrequencyType.everyTenMinutes, - ) as _i24.BackupFrequencyType); + returnValue: _i35.BackupFrequencyType.everyTenMinutes, + ) as _i35.BackupFrequencyType); @override - set backupFrequencyType(_i24.BackupFrequencyType? backupFrequencyType) => + set backupFrequencyType(_i35.BackupFrequencyType? backupFrequencyType) => super.noSuchMethod( Invocation.setter( #backupFrequencyType, @@ -1882,38 +2597,124 @@ class MockPrefs extends _i1.Mock implements _i17.Prefs { returnValueForMissingStub: null, ); @override + bool get enableCoinControl => (super.noSuchMethod( + Invocation.getter(#enableCoinControl), + returnValue: false, + ) as bool); + @override + set enableCoinControl(bool? enableCoinControl) => super.noSuchMethod( + Invocation.setter( + #enableCoinControl, + enableCoinControl, + ), + returnValueForMissingStub: null, + ); + @override + bool get enableSystemBrightness => (super.noSuchMethod( + Invocation.getter(#enableSystemBrightness), + returnValue: false, + ) as bool); + @override + set enableSystemBrightness(bool? enableSystemBrightness) => + super.noSuchMethod( + Invocation.setter( + #enableSystemBrightness, + enableSystemBrightness, + ), + returnValueForMissingStub: null, + ); + @override + String get themeId => (super.noSuchMethod( + Invocation.getter(#themeId), + returnValue: '', + ) as String); + @override + set themeId(String? themeId) => super.noSuchMethod( + Invocation.setter( + #themeId, + themeId, + ), + returnValueForMissingStub: null, + ); + @override + String get systemBrightnessLightThemeId => (super.noSuchMethod( + Invocation.getter(#systemBrightnessLightThemeId), + returnValue: '', + ) as String); + @override + set systemBrightnessLightThemeId(String? systemBrightnessLightThemeId) => + super.noSuchMethod( + Invocation.setter( + #systemBrightnessLightThemeId, + systemBrightnessLightThemeId, + ), + returnValueForMissingStub: null, + ); + @override + String get systemBrightnessDarkThemeId => (super.noSuchMethod( + Invocation.getter(#systemBrightnessDarkThemeId), + returnValue: '', + ) as String); + @override + set systemBrightnessDarkThemeId(String? systemBrightnessDarkThemeId) => + super.noSuchMethod( + Invocation.setter( + #systemBrightnessDarkThemeId, + systemBrightnessDarkThemeId, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i16.Future init() => (super.noSuchMethod( + _i23.Future init() => (super.noSuchMethod( Invocation.method( #init, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future incrementCurrentNotificationIndex() => (super.noSuchMethod( + _i23.Future incrementCurrentNotificationIndex() => (super.noSuchMethod( Invocation.method( #incrementCurrentNotificationIndex, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future isExternalCallsSet() => (super.noSuchMethod( + _i23.Future isExternalCallsSet() => (super.noSuchMethod( Invocation.method( #isExternalCallsSet, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + _i23.Future saveUserID(String? userId) => (super.noSuchMethod( + Invocation.method( + #saveUserID, + [userId], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod( + Invocation.method( + #saveSignupEpoch, + [signupEpoch], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + void addListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -1921,7 +2722,7 @@ class MockPrefs extends _i1.Mock implements _i17.Prefs { returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -1964,23 +2765,23 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValueForMissingStub: null, ); @override - _i13.CoinServiceAPI get wallet => (super.noSuchMethod( + _i20.CoinServiceAPI get wallet => (super.noSuchMethod( Invocation.getter(#wallet), - returnValue: _FakeCoinServiceAPI_13( + returnValue: _FakeCoinServiceAPI_18( this, Invocation.getter(#wallet), ), - ) as _i13.CoinServiceAPI); + ) as _i20.CoinServiceAPI); @override bool get hasBackgroundRefreshListener => (super.noSuchMethod( Invocation.getter(#hasBackgroundRefreshListener), returnValue: false, ) as bool); @override - _i15.Coin get coin => (super.noSuchMethod( + _i22.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i15.Coin.bitcoin, - ) as _i15.Coin); + returnValue: _i22.Coin.bitcoin, + ) as _i22.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -2013,91 +2814,42 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValueForMissingStub: null, ); @override - _i16.Future<_i9.FeeObject> get fees => (super.noSuchMethod( + _i23.Future<_i9.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i16.Future<_i9.FeeObject>.value(_FakeFeeObject_8( + returnValue: _i23.Future<_i9.FeeObject>.value(_FakeFeeObject_6( this, Invocation.getter(#fees), )), - ) as _i16.Future<_i9.FeeObject>); + ) as _i23.Future<_i9.FeeObject>); @override - _i16.Future get maxFee => (super.noSuchMethod( + _i23.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future.value(0), + ) as _i23.Future); @override - _i16.Future get currentReceivingAddress => (super.noSuchMethod( + _i23.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future<_i10.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i16.Future<_i10.Decimal>.value(_FakeDecimal_7( + _i12.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_9( this, - Invocation.getter(#availableBalance), - )), - ) as _i16.Future<_i10.Decimal>); - @override - _i10.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_7( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i10.Decimal); + ) as _i12.Balance); @override - _i16.Future<_i10.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i16.Future<_i10.Decimal>.value(_FakeDecimal_7( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i16.Future<_i10.Decimal>); - @override - _i16.Future<_i10.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i16.Future<_i10.Decimal>.value(_FakeDecimal_7( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i16.Future<_i10.Decimal>); - @override - _i16.Future<_i10.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i16.Future<_i10.Decimal>.value(_FakeDecimal_7( - this, - Invocation.getter(#totalBalance), - )), - ) as _i16.Future<_i10.Decimal>); - @override - _i10.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_7( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i10.Decimal); - @override - _i16.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); - @override - _i16.Future<_i9.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i23.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i16.Future<_i9.TransactionData>.value(_FakeTransactionData_9( - this, - Invocation.getter(#transactionData), - )), - ) as _i16.Future<_i9.TransactionData>); + _i23.Future>.value(<_i18.Transaction>[]), + ) as _i23.Future>); @override - _i16.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: - _i16.Future>.value(<_i9.UtxoObject>[]), - ) as _i16.Future>); + _i23.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i23.Future>.value(<_i18.UTXO>[]), + ) as _i23.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -2117,29 +2869,74 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValue: '', ) as String); @override - _i16.Future> get mnemonic => (super.noSuchMethod( + _i23.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); + returnValue: _i23.Future>.value([]), + ) as _i23.Future>); + @override + _i23.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i23.Future.value(), + ) as _i23.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i23.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i23.Future.value(''), + ) as _i23.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i16.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i23.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -2149,9 +2946,9 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValueForMissingStub: null, ); @override - _i16.Future> prepareSend({ + _i23.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i15.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -2160,50 +2957,32 @@ class MockManager extends _i1.Mock implements _i6.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i23.Future>.value({}), + ) as _i23.Future>); @override - _i16.Future confirmSend({required Map? txData}) => + _i23.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); - @override - _i16.Future refresh() => (super.noSuchMethod( + _i23.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -2213,34 +2992,35 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValue: false, ) as bool); @override - _i16.Future testNetworkConnection() => (super.noSuchMethod( + _i23.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future initializeNew() => (super.noSuchMethod( + _i23.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future initializeExisting() => (super.noSuchMethod( + _i23.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future recoverFromMnemonic({ + _i23.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -2251,25 +3031,26 @@ class MockManager extends _i1.Mock implements _i6.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future exitCurrentWallet() => (super.noSuchMethod( + _i23.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future fullRescan( + _i23.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -2281,42 +3062,52 @@ class MockManager extends _i1.Mock implements _i6.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); - @override - _i16.Future estimateFeeFor( - int? satoshiAmount, + _i23.Future<_i15.Amount> estimateFeeFor( + _i15.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future<_i15.Amount>.value(_FakeAmount_13( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i23.Future<_i15.Amount>); @override - _i16.Future generateNewAddress() => (super.noSuchMethod( + _i23.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + _i23.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + void addListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -2324,7 +3115,7 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -2344,7 +3135,7 @@ class MockManager extends _i1.Mock implements _i6.Manager { /// A class which mocks [CoinServiceAPI]. /// /// See the documentation for Mockito's code generation for more information. -class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { +class MockCoinServiceAPI extends _i1.Mock implements _i20.CoinServiceAPI { @override set onIsActiveWalletChanged(void Function(bool)? _onIsActiveWalletChanged) => super.noSuchMethod( @@ -2355,10 +3146,10 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { returnValueForMissingStub: null, ); @override - _i15.Coin get coin => (super.noSuchMethod( + _i22.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i15.Coin.bitcoin, - ) as _i15.Coin); + returnValue: _i22.Coin.bitcoin, + ) as _i22.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -2391,75 +3182,42 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { returnValueForMissingStub: null, ); @override - _i16.Future<_i9.FeeObject> get fees => (super.noSuchMethod( + _i23.Future<_i9.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i16.Future<_i9.FeeObject>.value(_FakeFeeObject_8( + returnValue: _i23.Future<_i9.FeeObject>.value(_FakeFeeObject_6( this, Invocation.getter(#fees), )), - ) as _i16.Future<_i9.FeeObject>); + ) as _i23.Future<_i9.FeeObject>); @override - _i16.Future get maxFee => (super.noSuchMethod( + _i23.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future.value(0), + ) as _i23.Future); @override - _i16.Future get currentReceivingAddress => (super.noSuchMethod( + _i23.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future<_i10.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i16.Future<_i10.Decimal>.value(_FakeDecimal_7( + _i12.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_9( this, - Invocation.getter(#availableBalance), - )), - ) as _i16.Future<_i10.Decimal>); + Invocation.getter(#balance), + ), + ) as _i12.Balance); @override - _i16.Future<_i10.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i16.Future<_i10.Decimal>.value(_FakeDecimal_7( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i16.Future<_i10.Decimal>); - @override - _i16.Future<_i10.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i16.Future<_i10.Decimal>.value(_FakeDecimal_7( - this, - Invocation.getter(#totalBalance), - )), - ) as _i16.Future<_i10.Decimal>); - @override - _i16.Future<_i10.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i16.Future<_i10.Decimal>.value(_FakeDecimal_7( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i16.Future<_i10.Decimal>); - @override - _i16.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); - @override - _i16.Future<_i9.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i23.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i16.Future<_i9.TransactionData>.value(_FakeTransactionData_9( - this, - Invocation.getter(#transactionData), - )), - ) as _i16.Future<_i9.TransactionData>); + _i23.Future>.value(<_i18.Transaction>[]), + ) as _i23.Future>); @override - _i16.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: - _i16.Future>.value(<_i9.UtxoObject>[]), - ) as _i16.Future>); + _i23.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i23.Future>.value(<_i18.UTXO>[]), + ) as _i23.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -2479,10 +3237,20 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { returnValue: '', ) as String); @override - _i16.Future> get mnemonic => (super.noSuchMethod( + _i23.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); + returnValue: _i23.Future>.value([]), + ) as _i23.Future>); + @override + _i23.Future get mnemonicString => (super.noSuchMethod( + Invocation.getter(#mnemonicString), + returnValue: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i23.Future.value(), + ) as _i23.Future); @override bool get hasCalledExit => (super.noSuchMethod( Invocation.getter(#hasCalledExit), @@ -2494,9 +3262,14 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { returnValue: false, ) as bool); @override - _i16.Future> prepareSend({ + int get storedChainHeight => (super.noSuchMethod( + Invocation.getter(#storedChainHeight), + returnValue: 0, + ) as int); + @override + _i23.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i15.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -2505,59 +3278,41 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i23.Future>.value({}), + ) as _i23.Future>); @override - _i16.Future confirmSend({required Map? txData}) => + _i23.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future send({ - required String? toAddress, - required int? amount, - Map? args, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); - @override - _i16.Future refresh() => (super.noSuchMethod( + _i23.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i23.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -2567,16 +3322,17 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { returnValue: false, ) as bool); @override - _i16.Future testNetworkConnection() => (super.noSuchMethod( + _i23.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future recoverFromMnemonic({ + _i23.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -2587,43 +3343,44 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future initializeNew() => (super.noSuchMethod( + _i23.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future initializeExisting() => (super.noSuchMethod( + _i23.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future exit() => (super.noSuchMethod( + _i23.Future exit() => (super.noSuchMethod( Invocation.method( #exit, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future fullRescan( + _i23.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -2635,40 +3392,49 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { maxNumberOfIndexesToCheck, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future estimateFeeFor( - int? satoshiAmount, + _i23.Future<_i15.Amount> estimateFeeFor( + _i15.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future<_i15.Amount>.value(_FakeAmount_13( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i23.Future<_i15.Amount>); @override - _i16.Future generateNewAddress() => (super.noSuchMethod( + _i23.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future updateSentCachedTxData(Map? txData) => + _i23.Future updateSentCachedTxData(Map? txData) => (super.noSuchMethod( Invocation.method( #updateSentCachedTxData, [txData], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); } diff --git a/test/price_test.dart b/test/price_test.dart index 6b98b67d1..cc7427489 100644 --- a/test/price_test.dart +++ b/test/price_test.dart @@ -6,7 +6,7 @@ import 'package:hive_test/hive_test.dart'; import 'package:http/http.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; -import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/services/price.dart'; import 'price_test.mocks.dart'; @@ -26,11 +26,70 @@ void main() { when(client.get( Uri.parse( - "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero,particl&order=market_cap_desc&per_page=10&page=1&sparkline=false"), + "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids" + "=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin,bitcoin-cash" + ",namecoin,wownero,ethereum,particl&order=market_cap_desc&per_page=50" + "&page=1&sparkline=false"), headers: { 'Content-Type': 'application/json' })).thenAnswer((_) async => Response( - '[{"id":"bitcoin","symbol":"btc","name":"Bitcoin","image":"https://assets.coingecko.com/coins/images/1/large/bitcoin.png?1547033579","current_price":1.0,"market_cap":19128800,"market_cap_rank":1,"fully_diluted_valuation":21000000,"total_volume":1272132,"high_24h":1.0,"low_24h":1.0,"price_change_24h":0.0,"price_change_percentage_24h":0.0,"market_cap_change_24h":950.0,"market_cap_change_percentage_24h":0.00497,"circulating_supply":19128800.0,"total_supply":21000000.0,"max_supply":21000000.0,"ath":1.003301,"ath_change_percentage":-0.32896,"ath_date":"2019-10-15T16:00:56.136Z","atl":0.99895134,"atl_change_percentage":0.10498,"atl_date":"2019-10-21T00:00:00.000Z","roi":null,"last_updated":"2022-08-22T16:37:59.237Z"},{"id":"dogecoin","symbol":"doge","name":"Dogecoin","image":"https://assets.coingecko.com/coins/images/5/large/dogecoin.png?1547792256","current_price":3.15e-06,"market_cap":417916,"market_cap_rank":10,"fully_diluted_valuation":null,"total_volume":27498,"high_24h":3.26e-06,"low_24h":3.13e-06,"price_change_24h":-8.6889947714e-08,"price_change_percentage_24h":-2.68533,"market_cap_change_24h":-11370.894861206936,"market_cap_change_percentage_24h":-2.64879,"circulating_supply":132670764299.894,"total_supply":null,"max_supply":null,"ath":1.264e-05,"ath_change_percentage":-75.05046,"ath_date":"2021-05-07T23:04:53.026Z","atl":1.50936e-07,"atl_change_percentage":1989.69346,"atl_date":"2020-12-17T09:18:05.654Z","roi":null,"last_updated":"2022-08-22T16:38:15.113Z"},{"id":"monero","symbol":"xmr","name":"Monero","image":"https://assets.coingecko.com/coins/images/69/large/monero_logo.png?1547033729","current_price":0.00717236,"market_cap":130002,"market_cap_rank":29,"fully_diluted_valuation":null,"total_volume":4901,"high_24h":0.00731999,"low_24h":0.00707511,"price_change_24h":-5.6133543212467e-05,"price_change_percentage_24h":-0.77656,"market_cap_change_24h":-1007.8447677436197,"market_cap_change_percentage_24h":-0.76929,"circulating_supply":18147820.3764146,"total_supply":null,"max_supply":null,"ath":0.03475393,"ath_change_percentage":-79.32037,"ath_date":"2018-01-09T00:00:00.000Z","atl":0.00101492,"atl_change_percentage":608.13327,"atl_date":"2014-12-18T00:00:00.000Z","roi":null,"last_updated":"2022-08-22T16:38:26.347Z"},{"id":"zcoin","symbol":"firo","name":"Firo","image":"https://assets.coingecko.com/coins/images/479/large/firocoingecko.png?1636537544","current_price":0.0001096,"market_cap":1252,"market_cap_rank":604,"fully_diluted_valuation":2349,"total_volume":90.573,"high_24h":0.00011148,"low_24h":0.00010834,"price_change_24h":-9.87561775002e-07,"price_change_percentage_24h":-0.89304,"market_cap_change_24h":-10.046635178462793,"market_cap_change_percentage_24h":-0.79578,"circulating_supply":11411043.8354697,"total_supply":21400000.0,"max_supply":21400000.0,"ath":0.01616272,"ath_change_percentage":-99.3208,"ath_date":"2018-04-04T16:04:48.408Z","atl":4.268e-05,"atl_change_percentage":157.22799,"atl_date":"2022-05-12T07:28:47.088Z","roi":null,"last_updated":"2022-08-22T16:38:47.229Z"},{"id":"epic-cash","symbol":"epic","name":"Epic Cash","image":"https://assets.coingecko.com/coins/images/9520/large/Epic_Coin_NO_drop_shadow.png?1620122642","current_price":2.803e-05,"market_cap":415.109,"market_cap_rank":953,"fully_diluted_valuation":null,"total_volume":0.2371557,"high_24h":3.053e-05,"low_24h":2.581e-05,"price_change_24h":1.9e-06,"price_change_percentage_24h":7.27524,"market_cap_change_24h":28.26753,"market_cap_change_percentage_24h":7.30726,"circulating_supply":14808052.0,"total_supply":21000000.0,"max_supply":null,"ath":0.00013848,"ath_change_percentage":-79.75864,"ath_date":"2021-12-11T08:39:41.129Z","atl":5.74028e-07,"atl_change_percentage":4783.08078,"atl_date":"2020-03-13T16:55:01.177Z","roi":null,"last_updated":"2022-08-22T16:38:32.826Z"}]', + '[{"id":"bitcoin","symbol":"btc","name":"Bitcoin","image":"https://asse' + 'ts.coingecko.com/coins/images/1/large/bitcoin.png?1547033579","curr' + 'ent_price":1.0,"market_cap":19128800,"market_cap_rank":1,"fully_dil' + 'uted_valuation":21000000,"total_volume":1272132,"high_24h":1.0,"low' + '_24h":1.0,"price_change_24h":0.0,"price_change_percentage_24h":0.0,' + '"market_cap_change_24h":950.0,"market_cap_change_percentage_24h":0.0' + '0497,"circulating_supply":19128800.0,"total_supply":21000000.0,"max' + '_supply":21000000.0,"ath":1.003301,"ath_change_percentage":-0.32896' + ',"ath_date":"2019-10-15T16:00:56.136Z","atl":0.99895134,"atl_change_' + 'percentage":0.10498,"atl_date":"2019-10-21T00:00:00.000Z","roi":nul' + 'l,"last_updated":"2022-08-22T16:37:59.237Z"},{"id":"dogecoin","symb' + 'ol":"doge","name":"Dogecoin","image":"https://assets.coingecko.com/' + 'coins/images/5/large/dogecoin.png?1547792256","current_price":3.15e' + '-06,"market_cap":417916,"market_cap_rank":10,"fully_diluted_valuati' + 'on":null,"total_volume":27498,"high_24h":3.26e-06,"low_24h":3.13e-0' + '6,"price_change_24h":-8.6889947714e-08,"price_change_percentage_24h' + '":-2.68533,"market_cap_change_24h":-11370.894861206936,"market_cap_c' + 'hange_percentage_24h":-2.64879,"circulating_supply":132670764299.89' + '4,"total_supply":null,"max_supply":null,"ath":1.264e-05,"ath_change' + '_percentage":-75.05046,"ath_date":"2021-05-07T23:04:53.026Z","atl":' + '1.50936e-07,"atl_change_percentage":1989.69346,"atl_date":"2020-12-' + '17T09:18:05.654Z","roi":null,"last_updated":"2022-08-22T16:38:15.11' + '3Z"},{"id":"monero","symbol":"xmr","name":"Monero","image":"https:/' + '/assets.coingecko.com/coins/images/69/large/monero_logo.png?1547033' + '729","current_price":0.00717236,"market_cap":130002,"market_cap_ran' + 'k":29,"fully_diluted_valuation":null,"total_volume":4901,"high_24h":' + '0.00731999,"low_24h":0.00707511,"price_change_24h":-5.6133543212467' + 'e-05,"price_change_percentage_24h":-0.77656,"market_cap_change_24h"' + ':-1007.8447677436197,"market_cap_change_percentage_24h":-0.76929,"c' + 'irculating_supply":18147820.3764146,"total_supply":null,"max_supply' + '":null,"ath":0.03475393,"ath_change_percentage":-79.32037,"ath_date' + '":"2018-01-09T00:00:00.000Z","atl":0.00101492,"atl_change_percentag' + 'e":608.13327,"atl_date":"2014-12-18T00:00:00.000Z","roi":null,"las' + 't_updated":"2022-08-22T16:38:26.347Z"},{"id":"zcoin","symbol":"firo' + '","name":"Firo","image":"https://assets.coingecko.com/coins/images/' + '479/large/firocoingecko.png?1636537544","current_price":0.0001096,"' + 'market_cap":1252,"market_cap_rank":604,"fully_diluted_valuation":234' + '9,"total_volume":90.573,"high_24h":0.00011148,"low_24h":0.00010834,' + '"price_change_24h":-9.87561775002e-07,"price_change_percentage_24h' + '":-0.89304,"market_cap_change_24h":-10.046635178462793,"market_cap_' + 'change_percentage_24h":-0.79578,"circulating_supply":11411043.83546' + '97,"total_supply":21400000.0,"max_supply":21400000.0,"ath":0.016162' + '72,"ath_change_percentage":-99.3208,"ath_date":"2018-04-04T16:04:48.' + '408Z","atl":4.268e-05,"atl_change_percentage":157.22799,"atl_date":"' + '2022-05-12T07:28:47.088Z","roi":null,"last_updated":"2022-08-22T16' + ':38:47.229Z"},{"id":"epic-cash","symbol":"epic","name":"Epic Cash",' + '"image":"https://assets.coingecko.com/coins/images/9520/large/Epic_C' + 'oin_NO_drop_shadow.png?1620122642","current_price":2.803e-05,"marke' + 't_cap":415.109,"market_cap_rank":953,"fully_diluted_valuation":null' + ',"total_volume":0.2371557,"high_24h":3.053e-05,"low_24h":2.581e-05' + ',"price_change_24h":1.9e-06,"price_change_percentage_24h":7.27524,"' + 'market_cap_change_24h":28.26753,"market_cap_change_percentage_24h":' + '7.30726,"circulating_supply":14808052.0,"total_supply":21000000.0,"' + 'max_supply":null,"ath":0.00013848,"ath_change_percentage":-79.75864' + ',"ath_date":"2021-12-11T08:39:41.129Z","atl":5.74028e-07,"atl_chang' + 'e_percentage":4783.08078,"atl_date":"2020-03-13T16:55:01.177Z","roi' + '":null,"last_updated":"2022-08-22T16:38:32.826Z"}]', 200)); final priceAPI = PriceAPI(client); @@ -38,11 +97,26 @@ void main() { final price = await priceAPI.getPricesAnd24hChange(baseCurrency: "btc"); - expect(price.toString(), - '{Coin.bitcoin: [1, 0.0], Coin.bitcoincash: [0, 0.0], Coin.dogecoin: [0.00000315, -2.68533], Coin.epicCash: [0.00002803, 7.27524], Coin.firo: [0.0001096, -0.89304], Coin.litecoin: [0, 0.0], Coin.monero: [0.00717236, -0.77656], Coin.namecoin: [0, 0.0], Coin.particl: [0, 0.0], Coin.wownero: [0, 0.0], Coin.bitcoinTestNet: [0, 0.0], Coin.litecoinTestNet: [0, 0.0], Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], Coin.firoTestNet: [0, 0.0]}'); + expect( + price.toString(), + '{Coin.bitcoin: [1, 0.0], Coin.bitcoincash: [0, 0.0], ' + 'Coin.dogecoin: [0.00000315, -2.68533], ' + 'Coin.eCash: [0, 0.0], Coin.epicCash: [0.00002803, 7.27524], ' + 'Coin.ethereum: [0, 0.0], ' + 'Coin.firo: [0.0001096, -0.89304], Coin.litecoin: [0, 0.0], ' + 'Coin.monero: [0.00717236, -0.77656], Coin.namecoin: [0, 0.0], ' + 'Coin.particl: [0, 0.0], Coin.wownero: [0, 0.0], ' + 'Coin.bitcoinTestNet: [0, 0.0],' + ' Coin.litecoinTestNet: [0, 0.0], Coin.bitcoincashTestnet: [0, 0.0], ' + 'Coin.dogecoinTestNet: [0, 0.0], Coin.firoTestNet: [0, 0.0]}', + ); verify(client.get( Uri.parse( - "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero,particl&order=market_cap_desc&per_page=10&page=1&sparkline=false"), + "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc" + "&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," + "bitcoin-cash,namecoin,wownero,ethereum,particl" + "&order=market_cap_desc&per_page=50&page=1&sparkline=false", + ), headers: {'Content-Type': 'application/json'})).called(1); verifyNoMoreInteractions(client); @@ -53,11 +127,70 @@ void main() { when(client.get( Uri.parse( - "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero,particl&order=market_cap_desc&per_page=10&page=1&sparkline=false"), + "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&" + "ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," + "bitcoin-cash,namecoin,wownero,ethereum,particl" + "&order=market_cap_desc&per_page=50&page=1&sparkline=false"), headers: { 'Content-Type': 'application/json' })).thenAnswer((_) async => Response( - '[{"id":"bitcoin","symbol":"btc","name":"Bitcoin","image":"https://assets.coingecko.com/coins/images/1/large/bitcoin.png?1547033579","current_price":1.0,"market_cap":19128800,"market_cap_rank":1,"fully_diluted_valuation":21000000,"total_volume":1272132,"high_24h":1.0,"low_24h":1.0,"price_change_24h":0.0,"price_change_percentage_24h":0.0,"market_cap_change_24h":950.0,"market_cap_change_percentage_24h":0.00497,"circulating_supply":19128800.0,"total_supply":21000000.0,"max_supply":21000000.0,"ath":1.003301,"ath_change_percentage":-0.32896,"ath_date":"2019-10-15T16:00:56.136Z","atl":0.99895134,"atl_change_percentage":0.10498,"atl_date":"2019-10-21T00:00:00.000Z","roi":null,"last_updated":"2022-08-22T16:37:59.237Z"},{"id":"dogecoin","symbol":"doge","name":"Dogecoin","image":"https://assets.coingecko.com/coins/images/5/large/dogecoin.png?1547792256","current_price":3.15e-06,"market_cap":417916,"market_cap_rank":10,"fully_diluted_valuation":null,"total_volume":27498,"high_24h":3.26e-06,"low_24h":3.13e-06,"price_change_24h":-8.6889947714e-08,"price_change_percentage_24h":-2.68533,"market_cap_change_24h":-11370.894861206936,"market_cap_change_percentage_24h":-2.64879,"circulating_supply":132670764299.894,"total_supply":null,"max_supply":null,"ath":1.264e-05,"ath_change_percentage":-75.05046,"ath_date":"2021-05-07T23:04:53.026Z","atl":1.50936e-07,"atl_change_percentage":1989.69346,"atl_date":"2020-12-17T09:18:05.654Z","roi":null,"last_updated":"2022-08-22T16:38:15.113Z"},{"id":"monero","symbol":"xmr","name":"Monero","image":"https://assets.coingecko.com/coins/images/69/large/monero_logo.png?1547033729","current_price":0.00717236,"market_cap":130002,"market_cap_rank":29,"fully_diluted_valuation":null,"total_volume":4901,"high_24h":0.00731999,"low_24h":0.00707511,"price_change_24h":-5.6133543212467e-05,"price_change_percentage_24h":-0.77656,"market_cap_change_24h":-1007.8447677436197,"market_cap_change_percentage_24h":-0.76929,"circulating_supply":18147820.3764146,"total_supply":null,"max_supply":null,"ath":0.03475393,"ath_change_percentage":-79.32037,"ath_date":"2018-01-09T00:00:00.000Z","atl":0.00101492,"atl_change_percentage":608.13327,"atl_date":"2014-12-18T00:00:00.000Z","roi":null,"last_updated":"2022-08-22T16:38:26.347Z"},{"id":"zcoin","symbol":"firo","name":"Firo","image":"https://assets.coingecko.com/coins/images/479/large/firocoingecko.png?1636537544","current_price":0.0001096,"market_cap":1252,"market_cap_rank":604,"fully_diluted_valuation":2349,"total_volume":90.573,"high_24h":0.00011148,"low_24h":0.00010834,"price_change_24h":-9.87561775002e-07,"price_change_percentage_24h":-0.89304,"market_cap_change_24h":-10.046635178462793,"market_cap_change_percentage_24h":-0.79578,"circulating_supply":11411043.8354697,"total_supply":21400000.0,"max_supply":21400000.0,"ath":0.01616272,"ath_change_percentage":-99.3208,"ath_date":"2018-04-04T16:04:48.408Z","atl":4.268e-05,"atl_change_percentage":157.22799,"atl_date":"2022-05-12T07:28:47.088Z","roi":null,"last_updated":"2022-08-22T16:38:47.229Z"},{"id":"epic-cash","symbol":"epic","name":"Epic Cash","image":"https://assets.coingecko.com/coins/images/9520/large/Epic_Coin_NO_drop_shadow.png?1620122642","current_price":2.803e-05,"market_cap":415.109,"market_cap_rank":953,"fully_diluted_valuation":null,"total_volume":0.2371557,"high_24h":3.053e-05,"low_24h":2.581e-05,"price_change_24h":1.9e-06,"price_change_percentage_24h":7.27524,"market_cap_change_24h":28.26753,"market_cap_change_percentage_24h":7.30726,"circulating_supply":14808052.0,"total_supply":21000000.0,"max_supply":null,"ath":0.00013848,"ath_change_percentage":-79.75864,"ath_date":"2021-12-11T08:39:41.129Z","atl":5.74028e-07,"atl_change_percentage":4783.08078,"atl_date":"2020-03-13T16:55:01.177Z","roi":null,"last_updated":"2022-08-22T16:38:32.826Z"}]', + '[{"id":"bitcoin","symbol":"btc","name":"Bitcoin","image":"https://a' + 'ssets.coingecko.com/coins/images/1/large/bitcoin.png?1547033579","c' + 'urrent_price":1.0,"market_cap":19128800,"market_cap_rank":1,"fully_' + 'diluted_valuation":21000000,"total_volume":1272132,"high_24h":1.0,"' + 'low_24h":1.0,"price_change_24h":0.0,"price_change_percentage_24h":0' + '.0,"market_cap_change_24h":950.0,"market_cap_change_percentage_24h"' + ':0.00497,"circulating_supply":19128800.0,"total_supply":21000000.0,"' + 'max_supply":21000000.0,"ath":1.003301,"ath_change_percentage":-0.32' + '896,"ath_date":"2019-10-15T16:00:56.136Z","atl":0.99895134,"atl_cha' + 'nge_percentage":0.10498,"atl_date":"2019-10-21T00:00:00.000Z","roi"' + ':null,"last_updated":"2022-08-22T16:37:59.237Z"},{"id":"dogecoin","' + 'symbol":"doge","name":"Dogecoin","image":"https://assets.coingecko.' + 'com/coins/images/5/large/dogecoin.png?1547792256","current_price":3' + '.15e-06,"market_cap":417916,"market_cap_rank":10,"fully_diluted_val' + 'uation":null,"total_volume":27498,"high_24h":3.26e-06,"low_24h":3.1' + '3e-06,"price_change_24h":-8.6889947714e-08,"price_change_percentage' + '_24h":-2.68533,"market_cap_change_24h":-11370.894861206936,"market_' + 'cap_change_percentage_24h":-2.64879,"circulating_supply":1326707642' + '99.894,"total_supply":null,"max_supply":null,"ath":1.264e-05,"ath_c' + 'hange_percentage":-75.05046,"ath_date":"2021-05-07T23:04:53.026Z","' + 'atl":1.50936e-07,"atl_change_percentage":1989.69346,"atl_date":"202' + '0-12-17T09:18:05.654Z","roi":null,"last_updated":"2022-08-22T16:38:' + '15.113Z"},{"id":"monero","symbol":"xmr","name":"Monero","image":"ht' + 'tps://assets.coingecko.com/coins/images/69/large/monero_logo.png?15' + '47033729","current_price":0.00717236,"market_cap":130002,"market_cap' + '_rank":29,"fully_diluted_valuation":null,"total_volume":4901,"high' + '_24h":0.00731999,"low_24h":0.00707511,"price_change_24h":-5.613354' + '3212467e-05,"price_change_percentage_24h":-0.77656,"market_cap_chan' + 'ge_24h":-1007.8447677436197,"market_cap_change_percentage_24h":-0.7' + '6929,"circulating_supply":18147820.3764146,"total_supply":null,"ma' + 'x_supply":null,"ath":0.03475393,"ath_change_percentage":-79.32037,"' + 'ath_date":"2018-01-09T00:00:00.000Z","atl":0.00101492,"atl_change_' + 'percentage":608.13327,"atl_date":"2014-12-18T00:00:00.000Z","roi":n' + 'ull,"last_updated":"2022-08-22T16:38:26.347Z"},{"id":"zcoin","symbo' + 'l":"firo","name":"Firo","image":"https://assets.coingecko.com/coins' + '/images/479/large/firocoingecko.png?1636537544","current_price":0.0' + '001096,"market_cap":1252,"market_cap_rank":604,"fully_diluted_valu' + 'ation":2349,"total_volume":90.573,"high_24h":0.00011148,"low_24h":0' + '.00010834,"price_change_24h":-9.87561775002e-07,"price_change_perce' + 'ntage_24h":-0.89304,"market_cap_change_24h":-10.046635178462793,"ma' + 'rket_cap_change_percentage_24h":-0.79578,"circulating_supply":11411' + '043.8354697,"total_supply":21400000.0,"max_supply":21400000.0,"ath"' + ':0.01616272,"ath_change_percentage":-99.3208,"ath_date":"2018-04-04' + 'T16:04:48.408Z","atl":4.268e-05,"atl_change_percentage":157.22799,' + '"atl_date":"2022-05-12T07:28:47.088Z","roi":null,"last_updated":"2' + '022-08-22T16:38:47.229Z"},{"id":"epic-cash","symbol":"epic","name":"' + 'Epic Cash","image":"https://assets.coingecko.com/coins/images/9520/' + 'large/Epic_Coin_NO_drop_shadow.png?1620122642","current_price":2.80' + '3e-05,"market_cap":415.109,"market_cap_rank":953,"fully_diluted_val' + 'uation":null,"total_volume":0.2371557,"high_24h":3.053e-05,"low_24h' + '":2.581e-05,"price_change_24h":1.9e-06,"price_change_percentage_24' + 'h":7.27524,"market_cap_change_24h":28.26753,"market_cap_change_perc' + 'entage_24h":7.30726,"circulating_supply":14808052.0,"total_supply":' + '21000000.0,"max_supply":null,"ath":0.00013848,"ath_change_percentag' + 'e":-79.75864,"ath_date":"2021-12-11T08:39:41.129Z","atl":5.74028e-0' + '7,"atl_change_percentage":4783.08078,"atl_date":"2020-03-13T16:55:01' + '.177Z","roi":null,"last_updated":"2022-08-22T16:38:32.826Z"}]', 200)); final priceAPI = PriceAPI(client); @@ -70,13 +203,24 @@ void main() { final cachedPrice = await priceAPI.getPricesAnd24hChange(baseCurrency: "btc"); - expect(cachedPrice.toString(), - '{Coin.bitcoin: [1, 0.0], Coin.bitcoincash: [0, 0.0], Coin.dogecoin: [0.00000315, -2.68533], Coin.epicCash: [0.00002803, 7.27524], Coin.firo: [0.0001096, -0.89304], Coin.litecoin: [0, 0.0], Coin.monero: [0.00717236, -0.77656], Coin.namecoin: [0, 0.0], Coin.particl: [0, 0.0], Coin.wownero: [0, 0.0], Coin.bitcoinTestNet: [0, 0.0], Coin.litecoinTestNet: [0, 0.0], Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], Coin.firoTestNet: [0, 0.0]}'); + expect( + cachedPrice.toString(), + '{Coin.bitcoin: [1, 0.0], Coin.bitcoincash: [0, 0.0],' + ' Coin.dogecoin: [0.00000315, -2.68533], Coin.eCash: [0, 0.0], Coin.epicCash: [0.00002803, 7.27524],' + ' Coin.ethereum: [0, 0.0], Coin.firo: [0.0001096, -0.89304], ' + 'Coin.litecoin: [0, 0.0], Coin.monero: [0.00717236, -0.77656], ' + 'Coin.namecoin: [0, 0.0], Coin.particl: [0, 0.0], Coin.wownero: [0, 0.0], ' + 'Coin.bitcoinTestNet: [0, 0.0], Coin.litecoinTestNet: [0, 0.0], ' + 'Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], ' + 'Coin.firoTestNet: [0, 0.0]}'); // verify only called once during filling of cache verify(client.get( Uri.parse( - "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero,particl&order=market_cap_desc&per_page=10&page=1&sparkline=false"), + "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids" + "=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," + "bitcoin-cash,namecoin,wownero,ethereum,particl" + "&order=market_cap_desc&per_page=50&page=1&sparkline=false"), headers: {'Content-Type': 'application/json'})).called(1); verifyNoMoreInteractions(client); @@ -87,11 +231,71 @@ void main() { when(client.get( Uri.parse( - "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero,particl&order=market_cap_desc&per_page=10&page=1&sparkline=false"), + "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc" + "&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," + "bitcoin-cash,namecoin,wownero,ethereum,particl" + "&order=market_cap_desc&per_page=50&page=1&sparkline=false"), headers: { 'Content-Type': 'application/json' })).thenAnswer((_) async => Response( - '[{"id":"bitcoin","symbol":"btc","name":com/coins/images/1/large/bitcoin.png?1547033579","current_price":1.0,"market_cap":19128800,"market_cap_rank":1,"fully_diluted_valuation":21000000,"total_volume":1272132,"high_24h":1.0,"low_24h":1.0,"price_change_24h":0.0,"price_change_percentage_24h":0.0,"market_cap_change_24h":950.0,"market_cap_change_percentage_24h":0.00497,"circulating_supply":19128800.0,"total_supply":21000000.0,"max_supply":21000000.0,"ath":1.003301,"ath_change_percentage":-0.32896,"ath_date":"2019-10-15T16:00:56.136Z","atl":0.99895134,"atl_change_percentage":0.10498,"atl_date":"2019-10-21T00:00:00.000Z","roi":null,"last_updated":"2022-08-22T16:37:59.237Z"},{"id":"dogecoin","symbol":"doge","name":"Dogecoin","image":"https://assets.coingecko.com/coins/images/5/large/dogecoin.png?1547792256","current_price":3.15e-06,"market_cap":417916,"market_cap_rank":10,"fully_diluted_valuation":null,"total_volume":27498,"high_24h":3.26e-06,"low_24h":3.13e-06,"price_change_24h":-8.6889947714e-08,"price_change_percentage_24h":-2.68533,"market_cap_change_24h":-11370.894861206936,"market_cap_change_percentage_24h":-2.64879,"circulating_supply":132670764299.894,"total_supply":null,"max_supply":null,"ath":1.264e-05,"ath_change_percentage":-75.05046,"ath_date":"2021-05-07T23:04:53.026Z","atl":1.50936e-07,"atl_change_percentage":1989.69346,"atl_date":"2020-12-17T09:18:05.654Z","roi":null,"last_updated":"2022-08-22T16:38:15.113Z"},{"id":"monero","symbol":"xmr","name":"Monero","image":"https://assets.coingecko.com/coins/images/69/large/monero_logo.png?1547033729","current_price":0.00717236,"market_cap":130002,"market_cap_rank":29,"fully_diluted_valuation":null,"total_volume":4901,"high_24h":0.00731999,"low_24h":0.00707511,"price_change_24h":-5.6133543212467e-05,"price_change_percentage_24h":-0.77656,"market_cap_change_24h":-1007.8447677436197,"market_cap_change_percentage_24h":-0.76929,"circulating_supply":18147820.3764146,"total_supply":null,"max_supply":null,"ath":0.03475393,"ath_change_percentage":-79.32037,"ath_date":"2018-01-09T00:00:00.000Z","atl":0.00101492,"atl_change_percentage":608.13327,"atl_date":"2014-12-18T00:00:00.000Z","roi":null,"last_updated":"2022-08-22T16:38:26.347Z"},{"id":"zcoin","symbol":"firo","name":"Firo","image":"https://assets.coingecko.com/coins/images/479/large/firocoingecko.png?1636537544","current_price":0.0001096,"market_cap":1252,"market_cap_rank":604,"fully_diluted_valuation":2349,"total_volume":90.573,"high_24h":0.00011148,"low_24h":0.00010834,"price_change_24h":-9.87561775002e-07,"price_change_percentage_24h":-0.89304,"market_cap_change_24h":-10.046635178462793,"market_cap_change_percentage_24h":-0.79578,"circulating_supply":11411043.8354697,"total_supply":21400000.0,"max_supply":21400000.0,"ath":0.01616272,"ath_change_percentage":-99.3208,"ath_date":"2018-04-04T16:04:48.408Z","atl":4.268e-05,"atl_change_percentage":157.22799,"atl_date":"2022-05-12T07:28:47.088Z","roi":null,"last_updated":"2022-08-22T16:38:47.229Z"},{"id":"epic-cash","symbol":"epic","name":"Epic Cash","image":"https://assets.coingecko.com/coins/images/9520/large/Epic_Coin_NO_drop_shadow.png?1620122642","current_price":2.803e-05,"market_cap":415.109,"market_cap_rank":953,"fully_diluted_valuation":null,"total_volume":0.2371557,"high_24h":3.053e-05,"low_24h":2.581e-05,"price_change_24h":1.9e-06,"price_change_percentage_24h":7.27524,"market_cap_change_24h":28.26753,"market_cap_change_percentage_24h":7.30726,"circulating_supply":14808052.0,"total_supply":21000000.0,"max_supply":null,"ath":0.00013848,"ath_change_percentage":-79.75864,"ath_date":"2021-12-11T08:39:41.129Z","atl":5.74028e-07,"atl_change_percentage":4783.08078,"atl_date":"2020-03-13T16:55:01.177Z","roi":null,"last_updated":"2022-08-22T16:38:32.826Z"}]', + '[{"id":"bitcoin","symbol":"btc","name":com/coins/images/1/large/' + 'bitcoin.png?1547033579","current_price":1.0,"market_cap":19128800' + ',"market_cap_rank":1,"fully_diluted_valuation":21000000,"total_volum' + 'e":1272132,"high_24h":1.0,"low_24h":1.0,"price_change_24h":0.0,"pri' + 'ce_change_percentage_24h":0.0,"market_cap_change_24h":950.0,"market_' + 'cap_change_percentage_24h":0.00497,"circulating_supply":19128800.0,"t' + 'otal_supply":21000000.0,"max_supply":21000000.0,"ath":1.003301,"ath' + '_change_percentage":-0.32896,"ath_date":"2019-10-15T16:00:56.136Z",' + '"atl":0.99895134,"atl_change_percentage":0.10498,"atl_date":' + '"2019-10-21T00:00:00.000Z","roi":null,' + '"last_updated":"2022-08-22T16:37:59.237Z"},{"id":"dogecoin"' + ',"symbol":"doge","name":"Dogecoin","image":' + '"https://assets.coingecko.com/coins/images/5/large/dogecoin.png?1547792256",' + '"current_price":3.15e-06,"market_cap":417916,"market_cap_rank":10' + ',"fully_diluted_valuation":null,"total_volume":27498,"high_24h":3' + '.26e-06,"low_24h":3.13e-06,"price_change_24h":-8.6889947714e-08,"' + 'price_change_percentage_24h":-2.68533,"market_cap_change_24h":-11' + '370.894861206936,"market_cap_change_percentage_24h":-2.64879,"cir' + 'culating_supply":132670764299.894,"total_supply":null,"max_supply' + '":null,"ath":1.264e-05,"ath_change_percentage":-75.05046,"ath_date' + '":"2021-05-07T23:04:53.026Z","atl":1.50936e-07,"atl_change_percen' + 'tage":1989.69346,"atl_date":"2020-12-17T09:18:05.654Z","roi":null,' + '"last_updated":"2022-08-22T16:38:15.113Z"},{"id":"monero","symbol"' + ':"xmr","name":"Monero","image":"https://assets.coingecko.com/coins' + '/images/69/large/monero_logo.png?1547033729","current_price":0.007' + '17236,"market_cap":130002,"market_cap_rank":29,"fully_diluted_valu' + 'ation":null,"total_volume":4901,"high_24h":0.00731999,"low_24h":0.' + '00707511,"price_change_24h":-5.6133543212467e-05,"price_change_per' + 'centage_24h":-0.77656,"market_cap_change_24h":-1007.8447677436197' + ',"market_cap_change_percentage_24h":-0.76929,"circulating_supply":' + '18147820.3764146,"total_supply":null,"max_supply":null,"ath":0.034' + '75393,"ath_change_percentage":-79.32037,"ath_date":"2018-01-09T00:' + '00:00.000Z","atl":0.00101492,"atl_change_percentage":608.13327,"at' + 'l_date":"2014-12-18T00:00:00.000Z","roi":null,"last_updated":"2022' + '-08-22T16:38:26.347Z"},{"id":"zcoin","symbol":"firo","name":"Firo"' + ',"image":"https://assets.coingecko.com/coins/images/479/large/firo' + 'coingecko.png?1636537544","current_price":0.0001096,"market_cap":1' + '252,"market_cap_rank":604,"fully_diluted_valuation":2349,"total_vo' + 'lume":90.573,"high_24h":0.00011148,"low_24h":0.00010834,"price_chang' + 'e_24h":-9.87561775002e-07,"price_change_percentage_24h":-0.89304,' + '"market_cap_change_24h":-10.046635178462793,"market_cap_change_per' + 'centage_24h":-0.79578,"circulating_supply":11411043.8354697,"tota' + 'l_supply":21400000.0,"max_supply":21400000.0,"ath":0.01616272,"ath' + '_change_percentage":-99.3208,"ath_date":"2018-04-04T16:04:48.408Z"' + ',"atl":4.268e-05,"atl_change_percentage":157.22799,"atl_date":"202' + '2-05-12T07:28:47.088Z","roi":null,"last_updated":"2022-08-22T16:3' + '8:47.229Z"},{"id":"epic-cash","symbol":"epic","name":"Epic Cash",' + '"image":"https://assets.coingecko.com/coins/images/9520/large/' + 'Epic_Coin_NO_drop_shadow.png?1620122642","current_price":2.803e-0' + '5,"market_cap":415.109,"market_cap_rank":953,"fully_diluted_valuat' + 'ion":null,"total_volume":0.2371557,"high_24h":3.053e-05,"low_24h":' + '2.581e-05,"price_change_24h":1.9e-06,"price_change_percentage_24h"' + ':7.27524,"market_cap_change_24h":28.26753,"market_cap_change_per' + 'centage_24h":7.30726,"circulating_supply":14808052.0,"total_suppl' + 'y":21000000.0,"max_supply":null,"ath":0.00013848,"ath_change_perce' + 'ntage":-79.75864,"ath_date":"2021-12-11T08:39:41.129Z","atl":5.74' + '028e-07,"atl_change_percentage":4783.08078,"atl_date":"2020-03-13T' + '16:55:01.177Z","roi":null,"last_updated":"2022-08-22T16:38:32.826Z"}]', 200)); final priceAPI = PriceAPI(client); @@ -99,8 +303,15 @@ void main() { final price = await priceAPI.getPricesAnd24hChange(baseCurrency: "btc"); - expect(price.toString(), - '{Coin.bitcoin: [0, 0.0], Coin.bitcoincash: [0, 0.0], Coin.dogecoin: [0, 0.0], Coin.epicCash: [0, 0.0], Coin.firo: [0, 0.0], Coin.litecoin: [0, 0.0], Coin.monero: [0, 0.0], Coin.namecoin: [0, 0.0], Coin.particl: [0, 0.0], Coin.wownero: [0, 0.0], Coin.bitcoinTestNet: [0, 0.0], Coin.litecoinTestNet: [0, 0.0], Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], Coin.firoTestNet: [0, 0.0]}'); + expect( + price.toString(), + '{Coin.bitcoin: [0, 0.0], Coin.bitcoincash: [0, 0.0], Coin.dogecoin: ' + '[0, 0.0], Coin.eCash: [0, 0.0], Coin.epicCash: [0, 0.0], Coin.ethereum: [0, 0.0],' + ' Coin.firo: [0, 0.0], Coin.litecoin: [0, 0.0], Coin.monero: [0, 0.0],' + ' Coin.namecoin: [0, 0.0], Coin.particl: [0, 0.0], Coin.wownero: [0, 0.0],' + ' Coin.bitcoinTestNet: [0, 0.0], Coin.litecoinTestNet: [0, 0.0], ' + 'Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0],' + ' Coin.firoTestNet: [0, 0.0]}'); }); test("no internet available", () async { @@ -108,7 +319,10 @@ void main() { when(client.get( Uri.parse( - "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc&ids=monero,bitcoin,litecoin,epic-cash,zcoin,dogecoin,bitcoin-cash,namecoin,wownero,particl&order=market_cap_desc&per_page=10&page=1&sparkline=false"), + "https://api.coingecko.com/api/v3/coins/markets?vs_currency=btc" + "&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin," + "bitcoin-cash,namecoin,wownero,ethereum,particl" + "&order=market_cap_desc&per_page=50&page=1&sparkline=false"), headers: { 'Content-Type': 'application/json' })).thenThrow(const SocketException( @@ -119,8 +333,15 @@ void main() { final price = await priceAPI.getPricesAnd24hChange(baseCurrency: "btc"); - expect(price.toString(), - '{Coin.bitcoin: [0, 0.0], Coin.bitcoincash: [0, 0.0], Coin.dogecoin: [0, 0.0], Coin.epicCash: [0, 0.0], Coin.firo: [0, 0.0], Coin.litecoin: [0, 0.0], Coin.monero: [0, 0.0], Coin.namecoin: [0, 0.0], Coin.particl: [0, 0.0], Coin.wownero: [0, 0.0], Coin.bitcoinTestNet: [0, 0.0], Coin.litecoinTestNet: [0, 0.0], Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], Coin.firoTestNet: [0, 0.0]}'); + expect( + price.toString(), + '{Coin.bitcoin: [0, 0.0], Coin.bitcoincash: [0, 0.0], ' + 'Coin.dogecoin: [0, 0.0], Coin.eCash: [0, 0.0], Coin.epicCash: [0, 0.0], Coin.ethereum: [0, 0.0],' + ' Coin.firo: [0, 0.0], Coin.litecoin: [0, 0.0], Coin.monero: [0, 0.0],' + ' Coin.namecoin: [0, 0.0], Coin.particl: [0, 0.0], Coin.wownero: [0, 0.0],' + ' Coin.bitcoinTestNet: [0, 0.0], Coin.litecoinTestNet: [0, 0.0], ' + 'Coin.bitcoincashTestnet: [0, 0.0], Coin.dogecoinTestNet: [0, 0.0], ' + 'Coin.firoTestNet: [0, 0.0]}'); }); tearDown(() async { diff --git a/test/sample_data/light/assets/dummy.png b/test/sample_data/light/assets/dummy.png new file mode 100644 index 000000000..8c9e7dc27 Binary files /dev/null and b/test/sample_data/light/assets/dummy.png differ diff --git a/test/sample_data/light/assets/dummy.svg b/test/sample_data/light/assets/dummy.svg new file mode 100644 index 000000000..416416b45 --- /dev/null +++ b/test/sample_data/light/assets/dummy.svg @@ -0,0 +1,4 @@ + + + + diff --git a/test/sample_data/theme_json.dart b/test/sample_data/theme_json.dart new file mode 100644 index 000000000..6f7601b42 --- /dev/null +++ b/test/sample_data/theme_json.dart @@ -0,0 +1,236 @@ +const Map lightThemeJsonMap = { + "name": "Light", + "id": "light", + "brightness": "light", + "colors": { + "coin": { + "bitcoin": "0xFFFCC17B", + "litecoin": "0xFF7FA6E1", + "bitcoincash": "0xFF7BCFB8", + "firo": "0xFFFF897A", + "dogecoin": "0xFFFFE079", + "epicCash": "0xFFC5C7CB", + "ethereum": "0xFFA7ADE9", + "monero": "0xFFFF9E6B", + "namecoin": "0xFF91B1E1", + "wownero": "0xFFED80C1", + "particl": "0xFF8175BD" + }, + "background": "0xFFF7F7F7", + "background_app_bar": "0xFFF7F7F7", + "overlay": "0xFF111215", + "accent_color_blue": "0xFF0052DF", + "accent_color_green": "0xFF4CC0A0", + "accent_color_yellow": "0xFFF7D65D", + "accent_color_red": "0xFFD34E50", + "accent_color_orange": "0xFFFEA68D", + "accent_color_dark": "0xFF232323", + "shadow": "0x0F2D3132", + "text_dark_one": "0xFF232323", + "text_dark_two": "0xFF414141", + "text_dark_three": "0xFF747778", + "text_white": "0xFFFFFFFF", + "text_favorite": "0xFF232323", + "text_error": "0xFF930006", + "text_restore": "0xFF111215", + "text_subtitle_one": "0xFF8E9192", + "text_subtitle_two": "0xFFA9ACAC", + "text_subtitle_three": "0xFFC4C7C7", + "text_subtitle_four": "0xFFE0E3E3", + "text_subtitle_five": "0xFFEEEFF1", + "text_subtitle_six": "0xFFF5F5F5", + "button_back_primary": "0xFF232323", + "button_back_secondary": "0xFFE0E3E3", + "button_back_primary_disabled": "0xFFD7D7D7", + "button_back_secondary_disabled": "0xFFF0F1F1", + "button_back_border": "0xFF232323", + "button_back_border_disabled": "0xFFB6B6B6", + "button_back_border_secondary": "0xFFE0E3E3", + "button_back_border_secondary_disabled": "0xFFF0F1F1", + "number_back_default": "0xFFFFFFFF", + "numpad_back_default": "0xFF232323", + "bottom_nav_back": "0xFFFFFFFF", + "button_text_primary": "0xFFFFFFFF", + "button_text_secondary": "0xFF232323", + "button_text_primary_disabled": "0xFFF8F8F8", + "button_text_secondary_disabled": "0xFFB7B7B7", + "button_text_border": "0xFF232323", + "button_text_disabled": "0xFFB6B6B6", + "button_text_borderless": "0xFF0052DF", + "button_text_borderless_disabled": "0xFFB6B6B6", + "number_text_default": "0xFF232323", + "numpad_text_default": "0xFFFFFFFF", + "bottom_nav_text": "0xFF232323", + "custom_text_button_enabled_text": "0xFF0052DF", + "custom_text_button_disabled_text": "0xFF8E9192", + "switch_bg_on": "0xFF0052DF", + "switch_bg_off": "0xFFD8E4FB", + "switch_bg_disabled": "0xFFC5C6C9", + "switch_circle_on": "0xFFDAE2FF", + "switch_circle_off": "0xFFFBFCFF", + "switch_circle_disabled": "0xFFFBFCFF", + "step_indicator_bg_check": "0xFFD9E2FF", + "step_indicator_bg_number": "0xFFD9E2FF", + "step_indicator_bg_inactive": "0xFFCDCDCD", + "step_indicator_bg_lines": "0xFF0056D2", + "step_indicator_bg_lines_inactive": "0xFFCDCDCD", + "step_indicator_icon_text": "0xFF0056D2", + "step_indicator_icon_number": "0xFF0056D2", + "step_indicator_icon_inactive": "0xFFF7F7F7", + "checkbox_bg_checked": "0xFF0056D2", + "checkbox_border_empty": "0xFF8E9192", + "checkbox_bg_disabled": "0xFFADC7EC", + "checkbox_icon_checked": "0xFFFFFFFF", + "checkbox_icon_disabled": "0xFFFFFFFF", + "checkbox_text_label": "0xFF232323", + "snack_bar_back_success": "0xFFB9E9D4", + "snack_bar_back_error": "0xFFFFDAD4", + "snack_bar_back_info": "0xFFDAE2FF", + "snack_bar_text_success": "0xFF006C4D", + "snack_bar_text_error": "0xFF930006", + "snack_bar_text_info": "0xFF002A78", + "bottom_nav_icon_back": "0xFFA2A2A2", + "bottom_nav_icon_icon": "0xFF232323", + "bottom_nav_icon_icon_highlighted": "0xFF232323", + "top_nav_icon_primary": "0xFF232323", + "top_nav_icon_green": "0xFF00A578", + "top_nav_icon_yellow": "0xFFF4C517", + "top_nav_icon_red": "0xFFC00205", + "settings_icon_back": "0xFFE0E3E3", + "settings_icon_icon": "0xFF232323", + "settings_icon_back_two": "0xFF94D6C4", + "settings_icon_element": "0xFF00A578", + "text_field_active_bg": "0xFFEEEFF1", + "text_field_default_bg": "0xFFEEEFF1", + "text_field_error_bg": "0xFFFFDAD4", + "text_field_success_bg": "0xFFB9E9D4", + "text_field_error_border": "0xFFFFDAD4", + "text_field_success_border": "0xFFB9E9D4", + "text_field_active_search_icon_left": "0xFFA9ACAC", + "text_field_default_search_icon_left": "0xFFA9ACAC", + "text_field_error_search_icon_left": "0xFF930006", + "text_field_success_search_icon_left": "0xFF006C4D", + "text_field_active_text": "0xFF232323", + "text_field_default_text": "0xFFA9ACAC", + "text_field_error_text": "0xFF000000", + "text_field_success_text": "0xFF000000", + "text_field_active_label": "0xFFA9ACAC", + "text_field_error_label": "0xFF930006", + "text_field_success_label": "0xFF006C4D", + "text_field_active_search_icon_right": "0xFF747778", + "text_field_default_search_icon_right": "0xFF747778", + "text_field_error_search_icon_right": "0xFF930006", + "text_field_success_search_icon_right": "0xFF006C4D", + "settings_item_level_two_active_bg": "0xFFFFFFFF", + "settings_item_level_two_active_text": "0xFF232323", + "settings_item_level_two_active_sub": "0xFF8E9192", + "radio_button_icon_border": "0xFF0056D2", + "radio_button_icon_border_disabled": "0xFF8F909A", + "radio_button_border_enabled": "0xFF0056D2", + "radio_button_border_disabled": "0xFF8F909A", + "radio_button_icon_circle": "0xFF0056D2", + "radio_button_icon_enabled": "0xFF0056D2", + "radio_button_text_enabled": "0xFF44464E", + "radio_button_text_disabled": "0xFF44464E", + "radio_button_label_enabled": "0xFF8E9192", + "radio_button_label_disabled": "0xFF8E9192", + "info_item_bg": "0xFFFFFFFF", + "info_item_label": "0xFF8E9192", + "info_item_text": "0xFF232323", + "info_item_icons": "0xFF0056D2", + "popup_bg": "0xFFFFFFFF", + "currency_list_item_bg": "0xFFF9F9FC", + "sw_bg": "0xFFFFFFFF", + "sw_mid": "0xFFFFFFFF", + "sw_bottom": "0xFF232323", + "bottom_nav_shadow": "0xFF282E33", + "favorite_star_active": "0xFF0056D2", + "favorite_star_inactive": "0xFFC4C7C7", + "splash": "0x358E9192", + "highlight": "0x44A9ACAC", + "warning_foreground": "0xFF232323", + "warning_background": "0xFFFFDAD3", + "loading_overlay_text_color": "0xFFF7F7F7", + "my_stack_contact_icon_bg": "0xFFEEEFF1", + "text_confirm_total_amount": "0xFF232323", + "text_selected_word_table_iterm": "0xFF232323", + "rate_type_toggle_color_on": "0xFFEEEFF1", + "rate_type_toggle_color_off": "0xFFFFFFFF", + "rate_type_toggle_desktop_color_on": "0xFFEEEFF1", + "rate_type_toggle_desktop_color_off": "0xFFE0E3E3", + "eth_tag_text": "0xFFFFFFFF", + "eth_tag_bg": "0xFF4D5798", + "eth_wallet_tag_text": "0xFF4D5798", + "eth_wallet_tag_bg": "0xFFF0F3FD", + "token_summary_text_primary": "0xFF232323", + "token_summary_text_secondary": "0xFF8488AB", + "token_summary_bg": "0xFFE9EAFF", + "token_summary_button_bg": "0xFFFFFFFF", + "token_summary_icon": "0xFF424A97", + "box_shadows": { + "standard": { + "color": "0x0F2D3132", + "spread_radius": 3.0, + "blur_radius": 4.0 + }, + "home_view_button_bar": { + "color": "0x0F2D3132", + "spread_radius": 3.0, + "blur_radius": 4.0 + } + } + }, + "assets": { + "bitcoin": "dummy.svg", + "litecoin": "dummy.svg", + "bitcoincash": "dummy.svg", + "dogecoin": "dummy.svg", + "epicCash": "dummy.svg", + "ethereum": "dummy.svg", + "firo": "dummy.svg", + "monero": "dummy.svg", + "wownero": "dummy.svg", + "namecoin": "dummy.svg", + "particl": "dummy.svg", + "bitcoin_image": "dummy.svg", + "litecoin_image": "dummy.svg", + "bitcoincash_image": "dummy.svg", + "dogecoin_image": "dummy.svg", + "epicCash_image": "dummy.svg.svg", + "ethereum_image": "dummy.svg", + "firo_image": "dummy.svg", + "monero_image": "dummy.svg", + "wownero_image": "dummy.svg", + "namecoin_image": "dummy.svg", + "particl_image": "dummy.svg", + "bitcoin_image_secondary": "dummy.svg", + "litecoin_image_secondary": "dummy.svg", + "bitcoincash_image_secondary": "dummy.svg", + "dogecoin_image_secondary": "dummy.svg", + "epicCash_image_secondary": "dummy.svg.svg", + "ethereum_image_secondary": "dummy.svg", + "firo_image_secondary": "dummy.svg", + "monero_image_secondary": "dummy.svg", + "wownero_image_secondary": "dummy.svg", + "namecoin_image_secondary": "dummy.svg", + "particl_image_secondary": "dummy.svg", + "bell_new": "dummy.svg", + "persona_incognito": "dummy.svg.svg", + "persona_easy": "dummy.svg", + "stack": "dummy.svg", + "stack_icon": "dummy.svg", + "receive": "dummy.svg", + "receive_pending": "dummy.svg", + "receive_cancelled": "dummy.svg", + "send": "sdummy.svg", + "tx_exchange": "dummy.svg", + "tx_exchange_pending": "dummy.svg", + "tx_exchange_failed": "dummy.svg", + "buy": "dummy.svg", + "exchange": "dummy.svg", + "send_pending": "dummy.svg", + "send_cancelled": "dummy.svg", + "theme_selector": "dummy.svg", + "theme_preview": "dummy.png" + } +}; diff --git a/test/sample_data/theme_json_v2.dart b/test/sample_data/theme_json_v2.dart new file mode 100644 index 000000000..3f9fbfb7f --- /dev/null +++ b/test/sample_data/theme_json_v2.dart @@ -0,0 +1,252 @@ +const Map lightThemeJsonMap = { + "name": "Light", + "version": 2, + "id": "light", + "brightness": "light", + "colors": { + // keys for coin colors must match the Coin enum name value exactly + "coin": { + "bitcoin": "0xFFFCC17B", + "litecoin": "0xFF7FA6E1", + "bitcoincash": "0xFF7BCFB8", + "firo": "0xFFFF897A", + "dogecoin": "0xFFFFE079", + "eCash": "0xFFC5C7CB", + "epicCash": "0xFFC5C7CB", + "ethereum": "0xFFA7ADE9", + "monero": "0xFFFF9E6B", + "namecoin": "0xFF91B1E1", + "wownero": "0xFFED80C1", + "particl": "0xFF8175BD" + }, + "background": "0xFFF7F7F7", + "background_app_bar": "0xFFF7F7F7", + "overlay": "0xFF111215", + "accent_color_blue": "0xFF0052DF", + "accent_color_green": "0xFF4CC0A0", + "accent_color_yellow": "0xFFF7D65D", + "accent_color_red": "0xFFD34E50", + "accent_color_orange": "0xFFFEA68D", + "accent_color_dark": "0xFF232323", + "shadow": "0x0F2D3132", + "text_dark_one": "0xFF232323", + "text_dark_two": "0xFF414141", + "text_dark_three": "0xFF747778", + "text_white": "0xFFFFFFFF", + "text_favorite": "0xFF232323", + "text_error": "0xFF930006", + "text_restore": "0xFF111215", + "text_subtitle_one": "0xFF8E9192", + "text_subtitle_two": "0xFFA9ACAC", + "text_subtitle_three": "0xFFC4C7C7", + "text_subtitle_four": "0xFFE0E3E3", + "text_subtitle_five": "0xFFEEEFF1", + "text_subtitle_six": "0xFFF5F5F5", + "button_back_primary": "0xFF232323", + "button_back_secondary": "0xFFE0E3E3", + "button_back_primary_disabled": "0xFFD7D7D7", + "button_back_secondary_disabled": "0xFFF0F1F1", + "button_back_border": "0xFF232323", + "button_back_border_disabled": "0xFFB6B6B6", + "button_back_border_secondary": "0xFFE0E3E3", + "button_back_border_secondary_disabled": "0xFFF0F1F1", + "number_back_default": "0xFFFFFFFF", + "numpad_back_default": "0xFF232323", + "bottom_nav_back": "0xFFFFFFFF", + "button_text_primary": "0xFFFFFFFF", + "button_text_secondary": "0xFF232323", + "button_text_primary_disabled": "0xFFF8F8F8", + "button_text_secondary_disabled": "0xFFB7B7B7", + "button_text_border": "0xFF232323", + "button_text_disabled": "0xFFB6B6B6", + "button_text_borderless": "0xFF0052DF", + "button_text_borderless_disabled": "0xFFB6B6B6", + "number_text_default": "0xFF232323", + "numpad_text_default": "0xFFFFFFFF", + "bottom_nav_text": "0xFF232323", + "custom_text_button_enabled_text": "0xFF0052DF", + "custom_text_button_disabled_text": "0xFF8E9192", + "switch_bg_on": "0xFF0052DF", + "switch_bg_off": "0xFFD8E4FB", + "switch_bg_disabled": "0xFFC5C6C9", + "switch_circle_on": "0xFFDAE2FF", + "switch_circle_off": "0xFFFBFCFF", + "switch_circle_disabled": "0xFFFBFCFF", + "step_indicator_bg_check": "0xFFD9E2FF", + "step_indicator_bg_number": "0xFFD9E2FF", + "step_indicator_bg_inactive": "0xFFCDCDCD", + "step_indicator_bg_lines": "0xFF0056D2", + "step_indicator_bg_lines_inactive": "0xFFCDCDCD", + "step_indicator_icon_text": "0xFF0056D2", + "step_indicator_icon_number": "0xFF0056D2", + "step_indicator_icon_inactive": "0xFFF7F7F7", + "checkbox_bg_checked": "0xFF0056D2", + "checkbox_border_empty": "0xFF8E9192", + "checkbox_bg_disabled": "0xFFADC7EC", + "checkbox_icon_checked": "0xFFFFFFFF", + "checkbox_icon_disabled": "0xFFFFFFFF", + "checkbox_text_label": "0xFF232323", + "snack_bar_back_success": "0xFFB9E9D4", + "snack_bar_back_error": "0xFFFFDAD4", + "snack_bar_back_info": "0xFFDAE2FF", + "snack_bar_text_success": "0xFF006C4D", + "snack_bar_text_error": "0xFF930006", + "snack_bar_text_info": "0xFF002A78", + "bottom_nav_icon_back": "0xFFA2A2A2", + "bottom_nav_icon_icon": "0xFF232323", + "bottom_nav_icon_icon_highlighted": "0xFF232323", + "top_nav_icon_primary": "0xFF232323", + "top_nav_icon_green": "0xFF00A578", + "top_nav_icon_yellow": "0xFFF4C517", + "top_nav_icon_red": "0xFFC00205", + "settings_icon_back": "0xFFE0E3E3", + "settings_icon_icon": "0xFF232323", + "settings_icon_back_two": "0xFF94D6C4", + "settings_icon_element": "0xFF00A578", + "text_field_active_bg": "0xFFEEEFF1", + "text_field_default_bg": "0xFFEEEFF1", + "text_field_error_bg": "0xFFFFDAD4", + "text_field_success_bg": "0xFFB9E9D4", + "text_field_error_border": "0xFFFFDAD4", + "text_field_success_border": "0xFFB9E9D4", + "text_field_active_search_icon_left": "0xFFA9ACAC", + "text_field_default_search_icon_left": "0xFFA9ACAC", + "text_field_error_search_icon_left": "0xFF930006", + "text_field_success_search_icon_left": "0xFF006C4D", + "text_field_active_text": "0xFF232323", + "text_field_default_text": "0xFFA9ACAC", + "text_field_error_text": "0xFF000000", + "text_field_success_text": "0xFF000000", + "text_field_active_label": "0xFFA9ACAC", + "text_field_error_label": "0xFF930006", + "text_field_success_label": "0xFF006C4D", + "text_field_active_search_icon_right": "0xFF747778", + "text_field_default_search_icon_right": "0xFF747778", + "text_field_error_search_icon_right": "0xFF930006", + "text_field_success_search_icon_right": "0xFF006C4D", + "settings_item_level_two_active_bg": "0xFFFFFFFF", + "settings_item_level_two_active_text": "0xFF232323", + "settings_item_level_two_active_sub": "0xFF8E9192", + "radio_button_icon_border": "0xFF0056D2", + "radio_button_icon_border_disabled": "0xFF8F909A", + "radio_button_border_enabled": "0xFF0056D2", + "radio_button_border_disabled": "0xFF8F909A", + "radio_button_icon_circle": "0xFF0056D2", + "radio_button_icon_enabled": "0xFF0056D2", + "radio_button_text_enabled": "0xFF44464E", + "radio_button_text_disabled": "0xFF44464E", + "radio_button_label_enabled": "0xFF8E9192", + "radio_button_label_disabled": "0xFF8E9192", + "info_item_bg": "0xFFFFFFFF", + "info_item_label": "0xFF8E9192", + "info_item_text": "0xFF232323", + "info_item_icons": "0xFF0056D2", + "popup_bg": "0xFFFFFFFF", + "currency_list_item_bg": "0xFFF9F9FC", + "sw_bg": "0xFFFFFFFF", + "sw_mid": "0xFFFFFFFF", + "sw_bottom": "0xFF232323", + "bottom_nav_shadow": "0xFF282E33", + "favorite_star_active": "0xFF0056D2", + "favorite_star_inactive": "0xFFC4C7C7", + "splash": "0x358E9192", + "highlight": "0x44A9ACAC", + "warning_foreground": "0xFF232323", + "warning_background": "0xFFFFDAD3", + "loading_overlay_text_color": "0xFFF7F7F7", + "my_stack_contact_icon_bg": "0xFFEEEFF1", + "text_confirm_total_amount": "0xFF232323", + "text_selected_word_table_iterm": "0xFF232323", + "rate_type_toggle_color_on": "0xFFEEEFF1", + "rate_type_toggle_color_off": "0xFFFFFFFF", + "rate_type_toggle_desktop_color_on": "0xFFEEEFF1", + "rate_type_toggle_desktop_color_off": "0xFFE0E3E3", + "eth_tag_text": "0xFFFFFFFF", + "eth_tag_bg": "0xFF4D5798", + "eth_wallet_tag_text": "0xFF4D5798", + "eth_wallet_tag_bg": "0xFFF0F3FD", + "token_summary_text_primary": "0xFF232323", + "token_summary_text_secondary": "0xFF8488AB", + "token_summary_bg": "0xFFE9EAFF", + "token_summary_button_bg": "0xFFFFFFFF", + "token_summary_icon": "0xFF424A97", + "box_shadows": { + "standard": { + "color": "0x0F2D3132", + "spread_radius": 3.0, + "blur_radius": 4.0 + }, + "home_view_button_bar": { + "color": "0x0F2D3132", + "spread_radius": 3.0, + "blur_radius": 4.0 + } + } + }, + "assets": { + "coin_placeholder": "dummy.svg", + // keys for coin assets must match the Coin enum name value exactly + "coins": { + "icons": { + "bitcoin": "dummy.svg", + "litecoin": "dummy.svg", + "bitcoincash": "dummy.svg", + "dogecoin": "dummy.svg", + "eCash": "dummy.svg", + "epicCash": "dummy.svg", + "ethereum": "dummy.svg", + "firo": "dummy.svg", + "monero": "dummy.svg", + "wownero": "dummy.svg", + "namecoin": "dummy.svg", + "particl": "dummy.svg" + }, + "images": { + "bitcoin": "dummy.svg", + "litecoin": "dummy.svg", + "bitcoincash": "dummy.svg", + "dogecoin": "dummy.svg", + "eCash": "dummy.svg", + "epicCash": "dummy.svg", + "ethereum": "dummy.svg", + "firo": "dummy.svg", + "monero": "dummy.svg", + "wownero": "dummy.svg", + "namecoin": "dummy.svg", + "particl": "dummy.svg" + }, + "secondaries": { + "bitcoin": "dummy.svg", + "litecoin": "dummy.svg", + "bitcoincash": "dummy.svg", + "dogecoin": "dummy.svg", + "eCash": "dummy.svg", + "epicCash": "dummy.svg", + "ethereum": "dummy.svg", + "firo": "dummy.svg", + "monero": "dummy.svg", + "wownero": "dummy.svg", + "namecoin": "dummy.svg", + "particl": "dummy.svg" + } + }, + "bell_new": "dummy.svg", + "persona_incognito": "dummy.svg.svg", + "persona_easy": "dummy.svg", + "stack": "dummy.svg", + "stack_icon": "dummy.svg", + "receive": "dummy.svg", + "receive_pending": "dummy.svg", + "receive_cancelled": "dummy.svg", + "send": "dummy.svg", + "tx_exchange": "dummy.svg", + "tx_exchange_pending": "dummy.svg", + "tx_exchange_failed": "dummy.svg", + "buy": "dummy.svg", + "exchange": "dummy.svg", + "send_pending": "dummy.svg", + "send_cancelled": "dummy.svg", + "theme_selector": "dummy.svg", + "theme_preview": "dummy.png" + } +}; diff --git a/test/screen_tests/address_book_view/address_book_view_screen_test.mocks.dart b/test/screen_tests/address_book_view/address_book_view_screen_test.mocks.dart index 754121b87..68c48dd34 100644 --- a/test/screen_tests/address_book_view/address_book_view_screen_test.mocks.dart +++ b/test/screen_tests/address_book_view/address_book_view_screen_test.mocks.dart @@ -7,7 +7,7 @@ import 'dart:async' as _i4; import 'dart:ui' as _i5; import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/models/contact.dart' as _i2; +import 'package:stackwallet/models/isar/models/contact_entry.dart' as _i2; import 'package:stackwallet/services/address_book_service.dart' as _i3; // ignore_for_file: type=lint @@ -21,8 +21,8 @@ import 'package:stackwallet/services/address_book_service.dart' as _i3; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class -class _FakeContact_0 extends _i1.SmartFake implements _i2.Contact { - _FakeContact_0( +class _FakeContactEntry_0 extends _i1.SmartFake implements _i2.ContactEntry { + _FakeContactEntry_0( Object parent, Invocation parentInvocation, ) : super( @@ -37,46 +37,43 @@ class _FakeContact_0 extends _i1.SmartFake implements _i2.Contact { class MockAddressBookService extends _i1.Mock implements _i3.AddressBookService { @override - List<_i2.Contact> get contacts => (super.noSuchMethod( + List<_i2.ContactEntry> get contacts => (super.noSuchMethod( Invocation.getter(#contacts), - returnValue: <_i2.Contact>[], - ) as List<_i2.Contact>); - @override - _i4.Future> get addressBookEntries => (super.noSuchMethod( - Invocation.getter(#addressBookEntries), - returnValue: _i4.Future>.value(<_i2.Contact>[]), - ) as _i4.Future>); + returnValue: <_i2.ContactEntry>[], + ) as List<_i2.ContactEntry>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i2.Contact getContactById(String? id) => (super.noSuchMethod( + _i2.ContactEntry getContactById(String? id) => (super.noSuchMethod( Invocation.method( #getContactById, [id], ), - returnValue: _FakeContact_0( + returnValue: _FakeContactEntry_0( this, Invocation.method( #getContactById, [id], ), ), - ) as _i2.Contact); + ) as _i2.ContactEntry); @override - _i4.Future> search(String? text) => (super.noSuchMethod( + _i4.Future> search(String? text) => + (super.noSuchMethod( Invocation.method( #search, [text], ), - returnValue: _i4.Future>.value(<_i2.Contact>[]), - ) as _i4.Future>); + returnValue: + _i4.Future>.value(<_i2.ContactEntry>[]), + ) as _i4.Future>); @override bool matches( String? term, - _i2.Contact? contact, + _i2.ContactEntry? contact, ) => (super.noSuchMethod( Invocation.method( @@ -89,7 +86,7 @@ class MockAddressBookService extends _i1.Mock returnValue: false, ) as bool); @override - _i4.Future addContact(_i2.Contact? contact) => (super.noSuchMethod( + _i4.Future addContact(_i2.ContactEntry? contact) => (super.noSuchMethod( Invocation.method( #addContact, [contact], @@ -97,7 +94,7 @@ class MockAddressBookService extends _i1.Mock returnValue: _i4.Future.value(false), ) as _i4.Future); @override - _i4.Future editContact(_i2.Contact? editedContact) => + _i4.Future editContact(_i2.ContactEntry? editedContact) => (super.noSuchMethod( Invocation.method( #editContact, diff --git a/test/screen_tests/address_book_view/subviews/add_address_book_view_screen_test.mocks.dart b/test/screen_tests/address_book_view/subviews/add_address_book_view_screen_test.mocks.dart index c1f322a08..80a28f732 100644 --- a/test/screen_tests/address_book_view/subviews/add_address_book_view_screen_test.mocks.dart +++ b/test/screen_tests/address_book_view/subviews/add_address_book_view_screen_test.mocks.dart @@ -3,19 +3,21 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i8; -import 'dart:ui' as _i10; +import 'dart:async' as _i9; +import 'dart:ui' as _i11; import 'package:barcode_scan2/barcode_scan2.dart' as _i2; -import 'package:decimal/decimal.dart' as _i6; import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/models/contact.dart' as _i3; +import 'package:stackwallet/models/balance.dart' as _i6; +import 'package:stackwallet/models/isar/models/contact_entry.dart' as _i3; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i14; import 'package:stackwallet/models/models.dart' as _i5; -import 'package:stackwallet/services/address_book_service.dart' as _i9; +import 'package:stackwallet/services/address_book_service.dart' as _i10; import 'package:stackwallet/services/coins/coin_service.dart' as _i4; -import 'package:stackwallet/services/coins/manager.dart' as _i11; -import 'package:stackwallet/utilities/barcode_scanner_interface.dart' as _i7; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i12; +import 'package:stackwallet/services/coins/manager.dart' as _i12; +import 'package:stackwallet/utilities/amount/amount.dart' as _i7; +import 'package:stackwallet/utilities/barcode_scanner_interface.dart' as _i8; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i13; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -38,8 +40,8 @@ class _FakeScanResult_0 extends _i1.SmartFake implements _i2.ScanResult { ); } -class _FakeContact_1 extends _i1.SmartFake implements _i3.Contact { - _FakeContact_1( +class _FakeContactEntry_1 extends _i1.SmartFake implements _i3.ContactEntry { + _FakeContactEntry_1( Object parent, Invocation parentInvocation, ) : super( @@ -69,8 +71,8 @@ class _FakeFeeObject_3 extends _i1.SmartFake implements _i5.FeeObject { ); } -class _FakeDecimal_4 extends _i1.SmartFake implements _i6.Decimal { - _FakeDecimal_4( +class _FakeBalance_4 extends _i1.SmartFake implements _i6.Balance { + _FakeBalance_4( Object parent, Invocation parentInvocation, ) : super( @@ -79,9 +81,8 @@ class _FakeDecimal_4 extends _i1.SmartFake implements _i6.Decimal { ); } -class _FakeTransactionData_5 extends _i1.SmartFake - implements _i5.TransactionData { - _FakeTransactionData_5( +class _FakeAmount_5 extends _i1.SmartFake implements _i7.Amount { + _FakeAmount_5( Object parent, Invocation parentInvocation, ) : super( @@ -94,13 +95,13 @@ class _FakeTransactionData_5 extends _i1.SmartFake /// /// See the documentation for Mockito's code generation for more information. class MockBarcodeScannerWrapper extends _i1.Mock - implements _i7.BarcodeScannerWrapper { + implements _i8.BarcodeScannerWrapper { MockBarcodeScannerWrapper() { _i1.throwOnMissingStub(this); } @override - _i8.Future<_i2.ScanResult> scan( + _i9.Future<_i2.ScanResult> scan( {_i2.ScanOptions? options = const _i2.ScanOptions()}) => (super.noSuchMethod( Invocation.method( @@ -108,7 +109,7 @@ class MockBarcodeScannerWrapper extends _i1.Mock [], {#options: options}, ), - returnValue: _i8.Future<_i2.ScanResult>.value(_FakeScanResult_0( + returnValue: _i9.Future<_i2.ScanResult>.value(_FakeScanResult_0( this, Invocation.method( #scan, @@ -116,55 +117,52 @@ class MockBarcodeScannerWrapper extends _i1.Mock {#options: options}, ), )), - ) as _i8.Future<_i2.ScanResult>); + ) as _i9.Future<_i2.ScanResult>); } /// A class which mocks [AddressBookService]. /// /// See the documentation for Mockito's code generation for more information. class MockAddressBookService extends _i1.Mock - implements _i9.AddressBookService { + implements _i10.AddressBookService { @override - List<_i3.Contact> get contacts => (super.noSuchMethod( + List<_i3.ContactEntry> get contacts => (super.noSuchMethod( Invocation.getter(#contacts), - returnValue: <_i3.Contact>[], - ) as List<_i3.Contact>); - @override - _i8.Future> get addressBookEntries => (super.noSuchMethod( - Invocation.getter(#addressBookEntries), - returnValue: _i8.Future>.value(<_i3.Contact>[]), - ) as _i8.Future>); + returnValue: <_i3.ContactEntry>[], + ) as List<_i3.ContactEntry>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i3.Contact getContactById(String? id) => (super.noSuchMethod( + _i3.ContactEntry getContactById(String? id) => (super.noSuchMethod( Invocation.method( #getContactById, [id], ), - returnValue: _FakeContact_1( + returnValue: _FakeContactEntry_1( this, Invocation.method( #getContactById, [id], ), ), - ) as _i3.Contact); + ) as _i3.ContactEntry); @override - _i8.Future> search(String? text) => (super.noSuchMethod( + _i9.Future> search(String? text) => + (super.noSuchMethod( Invocation.method( #search, [text], ), - returnValue: _i8.Future>.value(<_i3.Contact>[]), - ) as _i8.Future>); + returnValue: + _i9.Future>.value(<_i3.ContactEntry>[]), + ) as _i9.Future>); @override bool matches( String? term, - _i3.Contact? contact, + _i3.ContactEntry? contact, ) => (super.noSuchMethod( Invocation.method( @@ -177,33 +175,33 @@ class MockAddressBookService extends _i1.Mock returnValue: false, ) as bool); @override - _i8.Future addContact(_i3.Contact? contact) => (super.noSuchMethod( + _i9.Future addContact(_i3.ContactEntry? contact) => (super.noSuchMethod( Invocation.method( #addContact, [contact], ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); @override - _i8.Future editContact(_i3.Contact? editedContact) => + _i9.Future editContact(_i3.ContactEntry? editedContact) => (super.noSuchMethod( Invocation.method( #editContact, [editedContact], ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); @override - _i8.Future removeContact(String? id) => (super.noSuchMethod( + _i9.Future removeContact(String? id) => (super.noSuchMethod( Invocation.method( #removeContact, [id], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i11.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -211,7 +209,7 @@ class MockAddressBookService extends _i1.Mock returnValueForMissingStub: null, ); @override - void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i11.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -239,7 +237,7 @@ class MockAddressBookService extends _i1.Mock /// A class which mocks [Manager]. /// /// See the documentation for Mockito's code generation for more information. -class MockManager extends _i1.Mock implements _i11.Manager { +class MockManager extends _i1.Mock implements _i12.Manager { @override bool get isActiveWallet => (super.noSuchMethod( Invocation.getter(#isActiveWallet), @@ -267,10 +265,10 @@ class MockManager extends _i1.Mock implements _i11.Manager { returnValue: false, ) as bool); @override - _i12.Coin get coin => (super.noSuchMethod( + _i13.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i12.Coin.bitcoin, - ) as _i12.Coin); + returnValue: _i13.Coin.bitcoin, + ) as _i13.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -303,90 +301,42 @@ class MockManager extends _i1.Mock implements _i11.Manager { returnValueForMissingStub: null, ); @override - _i8.Future<_i5.FeeObject> get fees => (super.noSuchMethod( + _i9.Future<_i5.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i8.Future<_i5.FeeObject>.value(_FakeFeeObject_3( + returnValue: _i9.Future<_i5.FeeObject>.value(_FakeFeeObject_3( this, Invocation.getter(#fees), )), - ) as _i8.Future<_i5.FeeObject>); + ) as _i9.Future<_i5.FeeObject>); @override - _i8.Future get maxFee => (super.noSuchMethod( + _i9.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i8.Future.value(0), - ) as _i8.Future); + returnValue: _i9.Future.value(0), + ) as _i9.Future); @override - _i8.Future get currentReceivingAddress => (super.noSuchMethod( + _i9.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i8.Future.value(''), - ) as _i8.Future); + returnValue: _i9.Future.value(''), + ) as _i9.Future); @override - _i8.Future<_i6.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i8.Future<_i6.Decimal>.value(_FakeDecimal_4( + _i6.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_4( this, - Invocation.getter(#availableBalance), - )), - ) as _i8.Future<_i6.Decimal>); - @override - _i6.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_4( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i6.Decimal); + ) as _i6.Balance); @override - _i8.Future<_i6.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i8.Future<_i6.Decimal>.value(_FakeDecimal_4( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i8.Future<_i6.Decimal>); - @override - _i8.Future<_i6.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i8.Future<_i6.Decimal>.value(_FakeDecimal_4( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i8.Future<_i6.Decimal>); - @override - _i8.Future<_i6.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i8.Future<_i6.Decimal>.value(_FakeDecimal_4( - this, - Invocation.getter(#totalBalance), - )), - ) as _i8.Future<_i6.Decimal>); - @override - _i6.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_4( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i6.Decimal); - @override - _i8.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i8.Future>.value([]), - ) as _i8.Future>); - @override - _i8.Future<_i5.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i9.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i8.Future<_i5.TransactionData>.value(_FakeTransactionData_5( - this, - Invocation.getter(#transactionData), - )), - ) as _i8.Future<_i5.TransactionData>); + _i9.Future>.value(<_i14.Transaction>[]), + ) as _i9.Future>); @override - _i8.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: _i8.Future>.value(<_i5.UtxoObject>[]), - ) as _i8.Future>); + _i9.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i9.Future>.value(<_i14.UTXO>[]), + ) as _i9.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -406,29 +356,74 @@ class MockManager extends _i1.Mock implements _i11.Manager { returnValue: '', ) as String); @override - _i8.Future> get mnemonic => (super.noSuchMethod( + _i9.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i8.Future>.value([]), - ) as _i8.Future>); + returnValue: _i9.Future>.value([]), + ) as _i9.Future>); + @override + _i9.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i9.Future.value(), + ) as _i9.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i9.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i9.Future.value(''), + ) as _i9.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i8.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i9.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -438,9 +433,9 @@ class MockManager extends _i1.Mock implements _i11.Manager { returnValueForMissingStub: null, ); @override - _i8.Future> prepareSend({ + _i9.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i7.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -449,50 +444,32 @@ class MockManager extends _i1.Mock implements _i11.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i8.Future>.value({}), - ) as _i8.Future>); + _i9.Future>.value({}), + ) as _i9.Future>); @override - _i8.Future confirmSend({required Map? txData}) => + _i9.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i8.Future.value(''), - ) as _i8.Future); + returnValue: _i9.Future.value(''), + ) as _i9.Future); @override - _i8.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i8.Future.value(''), - ) as _i8.Future); - @override - _i8.Future refresh() => (super.noSuchMethod( + _i9.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -502,34 +479,35 @@ class MockManager extends _i1.Mock implements _i11.Manager { returnValue: false, ) as bool); @override - _i8.Future testNetworkConnection() => (super.noSuchMethod( + _i9.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); @override - _i8.Future initializeNew() => (super.noSuchMethod( + _i9.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future initializeExisting() => (super.noSuchMethod( + _i9.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future recoverFromMnemonic({ + _i9.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -540,25 +518,26 @@ class MockManager extends _i1.Mock implements _i11.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future exitCurrentWallet() => (super.noSuchMethod( + _i9.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future fullRescan( + _i9.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -570,42 +549,52 @@ class MockManager extends _i1.Mock implements _i11.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); - @override - _i8.Future estimateFeeFor( - int? satoshiAmount, + _i9.Future<_i7.Amount> estimateFeeFor( + _i7.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i8.Future.value(0), - ) as _i8.Future); + returnValue: _i9.Future<_i7.Amount>.value(_FakeAmount_5( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i9.Future<_i7.Amount>); @override - _i8.Future generateNewAddress() => (super.noSuchMethod( + _i9.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); @override - void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( + _i9.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override + void addListener(_i11.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -613,7 +602,7 @@ class MockManager extends _i1.Mock implements _i11.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i11.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/screen_tests/address_book_view/subviews/address_book_entry_details_view_screen_test.mocks.dart b/test/screen_tests/address_book_view/subviews/address_book_entry_details_view_screen_test.mocks.dart index d86533c22..935ea5526 100644 --- a/test/screen_tests/address_book_view/subviews/address_book_entry_details_view_screen_test.mocks.dart +++ b/test/screen_tests/address_book_view/subviews/address_book_entry_details_view_screen_test.mocks.dart @@ -3,19 +3,21 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i7; -import 'dart:ui' as _i8; +import 'dart:async' as _i8; +import 'dart:ui' as _i9; -import 'package:decimal/decimal.dart' as _i5; import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/models/contact.dart' as _i2; +import 'package:stackwallet/models/balance.dart' as _i5; +import 'package:stackwallet/models/isar/models/contact_entry.dart' as _i2; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i12; import 'package:stackwallet/models/models.dart' as _i4; -import 'package:stackwallet/services/address_book_service.dart' as _i6; +import 'package:stackwallet/services/address_book_service.dart' as _i7; import 'package:stackwallet/services/coins/coin_service.dart' as _i3; -import 'package:stackwallet/services/coins/manager.dart' as _i9; -import 'package:stackwallet/services/locale_service.dart' as _i12; -import 'package:stackwallet/services/notes_service.dart' as _i11; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i10; +import 'package:stackwallet/services/coins/manager.dart' as _i10; +import 'package:stackwallet/services/locale_service.dart' as _i14; +import 'package:stackwallet/services/notes_service.dart' as _i13; +import 'package:stackwallet/utilities/amount/amount.dart' as _i6; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i11; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -28,8 +30,8 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i10; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class -class _FakeContact_0 extends _i1.SmartFake implements _i2.Contact { - _FakeContact_0( +class _FakeContactEntry_0 extends _i1.SmartFake implements _i2.ContactEntry { + _FakeContactEntry_0( Object parent, Invocation parentInvocation, ) : super( @@ -59,8 +61,8 @@ class _FakeFeeObject_2 extends _i1.SmartFake implements _i4.FeeObject { ); } -class _FakeDecimal_3 extends _i1.SmartFake implements _i5.Decimal { - _FakeDecimal_3( +class _FakeBalance_3 extends _i1.SmartFake implements _i5.Balance { + _FakeBalance_3( Object parent, Invocation parentInvocation, ) : super( @@ -69,9 +71,8 @@ class _FakeDecimal_3 extends _i1.SmartFake implements _i5.Decimal { ); } -class _FakeTransactionData_4 extends _i1.SmartFake - implements _i4.TransactionData { - _FakeTransactionData_4( +class _FakeAmount_4 extends _i1.SmartFake implements _i6.Amount { + _FakeAmount_4( Object parent, Invocation parentInvocation, ) : super( @@ -84,48 +85,45 @@ class _FakeTransactionData_4 extends _i1.SmartFake /// /// See the documentation for Mockito's code generation for more information. class MockAddressBookService extends _i1.Mock - implements _i6.AddressBookService { + implements _i7.AddressBookService { @override - List<_i2.Contact> get contacts => (super.noSuchMethod( + List<_i2.ContactEntry> get contacts => (super.noSuchMethod( Invocation.getter(#contacts), - returnValue: <_i2.Contact>[], - ) as List<_i2.Contact>); - @override - _i7.Future> get addressBookEntries => (super.noSuchMethod( - Invocation.getter(#addressBookEntries), - returnValue: _i7.Future>.value(<_i2.Contact>[]), - ) as _i7.Future>); + returnValue: <_i2.ContactEntry>[], + ) as List<_i2.ContactEntry>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i2.Contact getContactById(String? id) => (super.noSuchMethod( + _i2.ContactEntry getContactById(String? id) => (super.noSuchMethod( Invocation.method( #getContactById, [id], ), - returnValue: _FakeContact_0( + returnValue: _FakeContactEntry_0( this, Invocation.method( #getContactById, [id], ), ), - ) as _i2.Contact); + ) as _i2.ContactEntry); @override - _i7.Future> search(String? text) => (super.noSuchMethod( + _i8.Future> search(String? text) => + (super.noSuchMethod( Invocation.method( #search, [text], ), - returnValue: _i7.Future>.value(<_i2.Contact>[]), - ) as _i7.Future>); + returnValue: + _i8.Future>.value(<_i2.ContactEntry>[]), + ) as _i8.Future>); @override bool matches( String? term, - _i2.Contact? contact, + _i2.ContactEntry? contact, ) => (super.noSuchMethod( Invocation.method( @@ -138,33 +136,33 @@ class MockAddressBookService extends _i1.Mock returnValue: false, ) as bool); @override - _i7.Future addContact(_i2.Contact? contact) => (super.noSuchMethod( + _i8.Future addContact(_i2.ContactEntry? contact) => (super.noSuchMethod( Invocation.method( #addContact, [contact], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future editContact(_i2.Contact? editedContact) => + _i8.Future editContact(_i2.ContactEntry? editedContact) => (super.noSuchMethod( Invocation.method( #editContact, [editedContact], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future removeContact(String? id) => (super.noSuchMethod( + _i8.Future removeContact(String? id) => (super.noSuchMethod( Invocation.method( #removeContact, [id], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -172,7 +170,7 @@ class MockAddressBookService extends _i1.Mock returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -200,7 +198,7 @@ class MockAddressBookService extends _i1.Mock /// A class which mocks [Manager]. /// /// See the documentation for Mockito's code generation for more information. -class MockManager extends _i1.Mock implements _i9.Manager { +class MockManager extends _i1.Mock implements _i10.Manager { @override bool get isActiveWallet => (super.noSuchMethod( Invocation.getter(#isActiveWallet), @@ -228,10 +226,10 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValue: false, ) as bool); @override - _i10.Coin get coin => (super.noSuchMethod( + _i11.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i10.Coin.bitcoin, - ) as _i10.Coin); + returnValue: _i11.Coin.bitcoin, + ) as _i11.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -264,90 +262,42 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValueForMissingStub: null, ); @override - _i7.Future<_i4.FeeObject> get fees => (super.noSuchMethod( + _i8.Future<_i4.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i7.Future<_i4.FeeObject>.value(_FakeFeeObject_2( + returnValue: _i8.Future<_i4.FeeObject>.value(_FakeFeeObject_2( this, Invocation.getter(#fees), )), - ) as _i7.Future<_i4.FeeObject>); + ) as _i8.Future<_i4.FeeObject>); @override - _i7.Future get maxFee => (super.noSuchMethod( + _i8.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future.value(0), + ) as _i8.Future); @override - _i7.Future get currentReceivingAddress => (super.noSuchMethod( + _i8.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future<_i5.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i7.Future<_i5.Decimal>.value(_FakeDecimal_3( + _i5.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_3( this, - Invocation.getter(#availableBalance), - )), - ) as _i7.Future<_i5.Decimal>); - @override - _i5.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_3( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i5.Decimal); + ) as _i5.Balance); @override - _i7.Future<_i5.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i7.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i7.Future<_i5.Decimal>); - @override - _i7.Future<_i5.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i7.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i7.Future<_i5.Decimal>); - @override - _i7.Future<_i5.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i7.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#totalBalance), - )), - ) as _i7.Future<_i5.Decimal>); - @override - _i5.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_3( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i5.Decimal); - @override - _i7.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); - @override - _i7.Future<_i4.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i8.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i7.Future<_i4.TransactionData>.value(_FakeTransactionData_4( - this, - Invocation.getter(#transactionData), - )), - ) as _i7.Future<_i4.TransactionData>); + _i8.Future>.value(<_i12.Transaction>[]), + ) as _i8.Future>); @override - _i7.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: _i7.Future>.value(<_i4.UtxoObject>[]), - ) as _i7.Future>); + _i8.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i8.Future>.value(<_i12.UTXO>[]), + ) as _i8.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -367,29 +317,74 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValue: '', ) as String); @override - _i7.Future> get mnemonic => (super.noSuchMethod( + _i8.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); + @override + _i8.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i8.Future.value(), + ) as _i8.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i8.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i8.Future.value(''), + ) as _i8.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i7.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i8.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -399,9 +394,9 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValueForMissingStub: null, ); @override - _i7.Future> prepareSend({ + _i8.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i6.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -410,50 +405,32 @@ class MockManager extends _i1.Mock implements _i9.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i7.Future>.value({}), - ) as _i7.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future confirmSend({required Map? txData}) => + _i8.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); - @override - _i7.Future refresh() => (super.noSuchMethod( + _i8.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -463,34 +440,35 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValue: false, ) as bool); @override - _i7.Future testNetworkConnection() => (super.noSuchMethod( + _i8.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future initializeNew() => (super.noSuchMethod( + _i8.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future initializeExisting() => (super.noSuchMethod( + _i8.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future recoverFromMnemonic({ + _i8.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -501,25 +479,26 @@ class MockManager extends _i1.Mock implements _i9.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future exitCurrentWallet() => (super.noSuchMethod( + _i8.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future fullRescan( + _i8.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -531,42 +510,52 @@ class MockManager extends _i1.Mock implements _i9.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); - @override - _i7.Future estimateFeeFor( - int? satoshiAmount, + _i8.Future<_i6.Amount> estimateFeeFor( + _i6.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future<_i6.Amount>.value(_FakeAmount_4( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i8.Future<_i6.Amount>); @override - _i7.Future generateNewAddress() => (super.noSuchMethod( + _i8.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + _i8.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -574,7 +563,7 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -594,7 +583,7 @@ class MockManager extends _i1.Mock implements _i9.Manager { /// A class which mocks [NotesService]. /// /// See the documentation for Mockito's code generation for more information. -class MockNotesService extends _i1.Mock implements _i11.NotesService { +class MockNotesService extends _i1.Mock implements _i13.NotesService { @override String get walletId => (super.noSuchMethod( Invocation.getter(#walletId), @@ -606,34 +595,34 @@ class MockNotesService extends _i1.Mock implements _i11.NotesService { returnValue: {}, ) as Map); @override - _i7.Future> get notes => (super.noSuchMethod( + _i8.Future> get notes => (super.noSuchMethod( Invocation.getter(#notes), - returnValue: _i7.Future>.value({}), - ) as _i7.Future>); + returnValue: _i8.Future>.value({}), + ) as _i8.Future>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i7.Future> search(String? text) => (super.noSuchMethod( + _i8.Future> search(String? text) => (super.noSuchMethod( Invocation.method( #search, [text], ), - returnValue: _i7.Future>.value({}), - ) as _i7.Future>); + returnValue: _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future getNoteFor({required String? txid}) => (super.noSuchMethod( + _i8.Future getNoteFor({required String? txid}) => (super.noSuchMethod( Invocation.method( #getNoteFor, [], {#txid: txid}, ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future editOrAddNote({ + _i8.Future editOrAddNote({ required String? txid, required String? note, }) => @@ -646,21 +635,21 @@ class MockNotesService extends _i1.Mock implements _i11.NotesService { #note: note, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future deleteNote({required String? txid}) => (super.noSuchMethod( + _i8.Future deleteNote({required String? txid}) => (super.noSuchMethod( Invocation.method( #deleteNote, [], {#txid: txid}, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -668,7 +657,7 @@ class MockNotesService extends _i1.Mock implements _i11.NotesService { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -696,7 +685,7 @@ class MockNotesService extends _i1.Mock implements _i11.NotesService { /// A class which mocks [LocaleService]. /// /// See the documentation for Mockito's code generation for more information. -class MockLocaleService extends _i1.Mock implements _i12.LocaleService { +class MockLocaleService extends _i1.Mock implements _i14.LocaleService { @override String get locale => (super.noSuchMethod( Invocation.getter(#locale), @@ -708,17 +697,17 @@ class MockLocaleService extends _i1.Mock implements _i12.LocaleService { returnValue: false, ) as bool); @override - _i7.Future loadLocale({bool? notify = true}) => (super.noSuchMethod( + _i8.Future loadLocale({bool? notify = true}) => (super.noSuchMethod( Invocation.method( #loadLocale, [], {#notify: notify}, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -726,7 +715,7 @@ class MockLocaleService extends _i1.Mock implements _i12.LocaleService { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/screen_tests/address_book_view/subviews/edit_address_book_entry_view_screen_test.mocks.dart b/test/screen_tests/address_book_view/subviews/edit_address_book_entry_view_screen_test.mocks.dart index 403d3523f..8874682ae 100644 --- a/test/screen_tests/address_book_view/subviews/edit_address_book_entry_view_screen_test.mocks.dart +++ b/test/screen_tests/address_book_view/subviews/edit_address_book_entry_view_screen_test.mocks.dart @@ -3,17 +3,19 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i7; -import 'dart:ui' as _i8; +import 'dart:async' as _i8; +import 'dart:ui' as _i9; -import 'package:decimal/decimal.dart' as _i5; import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/models/contact.dart' as _i2; +import 'package:stackwallet/models/balance.dart' as _i5; +import 'package:stackwallet/models/isar/models/contact_entry.dart' as _i2; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i12; import 'package:stackwallet/models/models.dart' as _i4; -import 'package:stackwallet/services/address_book_service.dart' as _i6; +import 'package:stackwallet/services/address_book_service.dart' as _i7; import 'package:stackwallet/services/coins/coin_service.dart' as _i3; -import 'package:stackwallet/services/coins/manager.dart' as _i9; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i10; +import 'package:stackwallet/services/coins/manager.dart' as _i10; +import 'package:stackwallet/utilities/amount/amount.dart' as _i6; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i11; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -26,8 +28,8 @@ import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i10; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class -class _FakeContact_0 extends _i1.SmartFake implements _i2.Contact { - _FakeContact_0( +class _FakeContactEntry_0 extends _i1.SmartFake implements _i2.ContactEntry { + _FakeContactEntry_0( Object parent, Invocation parentInvocation, ) : super( @@ -57,8 +59,8 @@ class _FakeFeeObject_2 extends _i1.SmartFake implements _i4.FeeObject { ); } -class _FakeDecimal_3 extends _i1.SmartFake implements _i5.Decimal { - _FakeDecimal_3( +class _FakeBalance_3 extends _i1.SmartFake implements _i5.Balance { + _FakeBalance_3( Object parent, Invocation parentInvocation, ) : super( @@ -67,9 +69,8 @@ class _FakeDecimal_3 extends _i1.SmartFake implements _i5.Decimal { ); } -class _FakeTransactionData_4 extends _i1.SmartFake - implements _i4.TransactionData { - _FakeTransactionData_4( +class _FakeAmount_4 extends _i1.SmartFake implements _i6.Amount { + _FakeAmount_4( Object parent, Invocation parentInvocation, ) : super( @@ -82,48 +83,45 @@ class _FakeTransactionData_4 extends _i1.SmartFake /// /// See the documentation for Mockito's code generation for more information. class MockAddressBookService extends _i1.Mock - implements _i6.AddressBookService { + implements _i7.AddressBookService { @override - List<_i2.Contact> get contacts => (super.noSuchMethod( + List<_i2.ContactEntry> get contacts => (super.noSuchMethod( Invocation.getter(#contacts), - returnValue: <_i2.Contact>[], - ) as List<_i2.Contact>); - @override - _i7.Future> get addressBookEntries => (super.noSuchMethod( - Invocation.getter(#addressBookEntries), - returnValue: _i7.Future>.value(<_i2.Contact>[]), - ) as _i7.Future>); + returnValue: <_i2.ContactEntry>[], + ) as List<_i2.ContactEntry>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i2.Contact getContactById(String? id) => (super.noSuchMethod( + _i2.ContactEntry getContactById(String? id) => (super.noSuchMethod( Invocation.method( #getContactById, [id], ), - returnValue: _FakeContact_0( + returnValue: _FakeContactEntry_0( this, Invocation.method( #getContactById, [id], ), ), - ) as _i2.Contact); + ) as _i2.ContactEntry); @override - _i7.Future> search(String? text) => (super.noSuchMethod( + _i8.Future> search(String? text) => + (super.noSuchMethod( Invocation.method( #search, [text], ), - returnValue: _i7.Future>.value(<_i2.Contact>[]), - ) as _i7.Future>); + returnValue: + _i8.Future>.value(<_i2.ContactEntry>[]), + ) as _i8.Future>); @override bool matches( String? term, - _i2.Contact? contact, + _i2.ContactEntry? contact, ) => (super.noSuchMethod( Invocation.method( @@ -136,33 +134,33 @@ class MockAddressBookService extends _i1.Mock returnValue: false, ) as bool); @override - _i7.Future addContact(_i2.Contact? contact) => (super.noSuchMethod( + _i8.Future addContact(_i2.ContactEntry? contact) => (super.noSuchMethod( Invocation.method( #addContact, [contact], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future editContact(_i2.Contact? editedContact) => + _i8.Future editContact(_i2.ContactEntry? editedContact) => (super.noSuchMethod( Invocation.method( #editContact, [editedContact], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future removeContact(String? id) => (super.noSuchMethod( + _i8.Future removeContact(String? id) => (super.noSuchMethod( Invocation.method( #removeContact, [id], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -170,7 +168,7 @@ class MockAddressBookService extends _i1.Mock returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -198,7 +196,7 @@ class MockAddressBookService extends _i1.Mock /// A class which mocks [Manager]. /// /// See the documentation for Mockito's code generation for more information. -class MockManager extends _i1.Mock implements _i9.Manager { +class MockManager extends _i1.Mock implements _i10.Manager { @override bool get isActiveWallet => (super.noSuchMethod( Invocation.getter(#isActiveWallet), @@ -226,10 +224,10 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValue: false, ) as bool); @override - _i10.Coin get coin => (super.noSuchMethod( + _i11.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i10.Coin.bitcoin, - ) as _i10.Coin); + returnValue: _i11.Coin.bitcoin, + ) as _i11.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -262,90 +260,42 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValueForMissingStub: null, ); @override - _i7.Future<_i4.FeeObject> get fees => (super.noSuchMethod( + _i8.Future<_i4.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i7.Future<_i4.FeeObject>.value(_FakeFeeObject_2( + returnValue: _i8.Future<_i4.FeeObject>.value(_FakeFeeObject_2( this, Invocation.getter(#fees), )), - ) as _i7.Future<_i4.FeeObject>); + ) as _i8.Future<_i4.FeeObject>); @override - _i7.Future get maxFee => (super.noSuchMethod( + _i8.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future.value(0), + ) as _i8.Future); @override - _i7.Future get currentReceivingAddress => (super.noSuchMethod( + _i8.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future<_i5.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i7.Future<_i5.Decimal>.value(_FakeDecimal_3( + _i5.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_3( this, - Invocation.getter(#availableBalance), - )), - ) as _i7.Future<_i5.Decimal>); - @override - _i5.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_3( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i5.Decimal); + ) as _i5.Balance); @override - _i7.Future<_i5.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i7.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i7.Future<_i5.Decimal>); - @override - _i7.Future<_i5.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i7.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i7.Future<_i5.Decimal>); - @override - _i7.Future<_i5.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i7.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#totalBalance), - )), - ) as _i7.Future<_i5.Decimal>); - @override - _i5.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_3( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i5.Decimal); - @override - _i7.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); - @override - _i7.Future<_i4.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i8.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i7.Future<_i4.TransactionData>.value(_FakeTransactionData_4( - this, - Invocation.getter(#transactionData), - )), - ) as _i7.Future<_i4.TransactionData>); + _i8.Future>.value(<_i12.Transaction>[]), + ) as _i8.Future>); @override - _i7.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: _i7.Future>.value(<_i4.UtxoObject>[]), - ) as _i7.Future>); + _i8.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i8.Future>.value(<_i12.UTXO>[]), + ) as _i8.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -365,29 +315,74 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValue: '', ) as String); @override - _i7.Future> get mnemonic => (super.noSuchMethod( + _i8.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); + @override + _i8.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i8.Future.value(), + ) as _i8.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i8.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i8.Future.value(''), + ) as _i8.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i7.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i8.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -397,9 +392,9 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValueForMissingStub: null, ); @override - _i7.Future> prepareSend({ + _i8.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i6.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -408,50 +403,32 @@ class MockManager extends _i1.Mock implements _i9.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i7.Future>.value({}), - ) as _i7.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future confirmSend({required Map? txData}) => + _i8.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); - @override - _i7.Future refresh() => (super.noSuchMethod( + _i8.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -461,34 +438,35 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValue: false, ) as bool); @override - _i7.Future testNetworkConnection() => (super.noSuchMethod( + _i8.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future initializeNew() => (super.noSuchMethod( + _i8.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future initializeExisting() => (super.noSuchMethod( + _i8.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future recoverFromMnemonic({ + _i8.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -499,25 +477,26 @@ class MockManager extends _i1.Mock implements _i9.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future exitCurrentWallet() => (super.noSuchMethod( + _i8.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future fullRescan( + _i8.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -529,42 +508,52 @@ class MockManager extends _i1.Mock implements _i9.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); - @override - _i7.Future estimateFeeFor( - int? satoshiAmount, + _i8.Future<_i6.Amount> estimateFeeFor( + _i6.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future<_i6.Amount>.value(_FakeAmount_4( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i8.Future<_i6.Amount>); @override - _i7.Future generateNewAddress() => (super.noSuchMethod( + _i8.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + _i8.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -572,7 +561,7 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/screen_tests/exchange/exchange_view_test.mocks.dart b/test/screen_tests/exchange/exchange_view_test.mocks.dart index 8c0d72f55..e51fd926f 100644 --- a/test/screen_tests/exchange/exchange_view_test.mocks.dart +++ b/test/screen_tests/exchange/exchange_view_test.mocks.dart @@ -3,37 +3,33 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i7; -import 'dart:ui' as _i8; +import 'dart:async' as _i6; +import 'dart:ui' as _i7; -import 'package:decimal/decimal.dart' as _i15; -import 'package:http/http.dart' as _i13; +import 'package:decimal/decimal.dart' as _i14; +import 'package:http/http.dart' as _i12; import 'package:mockito/mockito.dart' as _i1; import 'package:stackwallet/models/exchange/change_now/cn_exchange_estimate.dart' - as _i18; -import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart' - as _i20; -import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart' - as _i21; -import 'package:stackwallet/models/exchange/response_objects/currency.dart' - as _i14; -import 'package:stackwallet/models/exchange/response_objects/estimate.dart' as _i17; -import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart' +import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart' as _i19; -import 'package:stackwallet/models/exchange/response_objects/pair.dart' as _i22; -import 'package:stackwallet/models/exchange/response_objects/range.dart' +import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart' + as _i20; +import 'package:stackwallet/models/exchange/response_objects/estimate.dart' as _i16; -import 'package:stackwallet/models/exchange/response_objects/trade.dart' - as _i10; -import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart' - as _i5; +import 'package:stackwallet/models/exchange/response_objects/fixed_rate_market.dart' + as _i18; +import 'package:stackwallet/models/exchange/response_objects/range.dart' + as _i15; +import 'package:stackwallet/models/exchange/response_objects/trade.dart' as _i9; +import 'package:stackwallet/models/isar/exchange_cache/currency.dart' as _i13; +import 'package:stackwallet/models/isar/exchange_cache/pair.dart' as _i21; import 'package:stackwallet/services/exchange/change_now/change_now_api.dart' - as _i12; + as _i11; import 'package:stackwallet/services/exchange/exchange_response.dart' as _i2; -import 'package:stackwallet/services/trade_notes_service.dart' as _i11; -import 'package:stackwallet/services/trade_service.dart' as _i9; -import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i6; +import 'package:stackwallet/services/trade_notes_service.dart' as _i10; +import 'package:stackwallet/services/trade_service.dart' as _i8; +import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i5; import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i4; import 'package:stackwallet/utilities/prefs.dart' as _i3; @@ -183,16 +179,15 @@ class MockPrefs extends _i1.Mock implements _i3.Prefs { returnValueForMissingStub: null, ); @override - _i5.ExchangeRateType get exchangeRateType => (super.noSuchMethod( - Invocation.getter(#exchangeRateType), - returnValue: _i5.ExchangeRateType.estimated, - ) as _i5.ExchangeRateType); + bool get randomizePIN => (super.noSuchMethod( + Invocation.getter(#randomizePIN), + returnValue: false, + ) as bool); @override - set exchangeRateType(_i5.ExchangeRateType? exchangeRateType) => - super.noSuchMethod( + set randomizePIN(bool? randomizePIN) => super.noSuchMethod( Invocation.setter( - #exchangeRateType, - exchangeRateType, + #randomizePIN, + randomizePIN, ), returnValueForMissingStub: null, ); @@ -270,12 +265,12 @@ class MockPrefs extends _i1.Mock implements _i3.Prefs { returnValueForMissingStub: null, ); @override - _i6.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod( + _i5.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod( Invocation.getter(#backupFrequencyType), - returnValue: _i6.BackupFrequencyType.everyTenMinutes, - ) as _i6.BackupFrequencyType); + returnValue: _i5.BackupFrequencyType.everyTenMinutes, + ) as _i5.BackupFrequencyType); @override - set backupFrequencyType(_i6.BackupFrequencyType? backupFrequencyType) => + set backupFrequencyType(_i5.BackupFrequencyType? backupFrequencyType) => super.noSuchMethod( Invocation.setter( #backupFrequencyType, @@ -340,38 +335,124 @@ class MockPrefs extends _i1.Mock implements _i3.Prefs { returnValueForMissingStub: null, ); @override + bool get enableCoinControl => (super.noSuchMethod( + Invocation.getter(#enableCoinControl), + returnValue: false, + ) as bool); + @override + set enableCoinControl(bool? enableCoinControl) => super.noSuchMethod( + Invocation.setter( + #enableCoinControl, + enableCoinControl, + ), + returnValueForMissingStub: null, + ); + @override + bool get enableSystemBrightness => (super.noSuchMethod( + Invocation.getter(#enableSystemBrightness), + returnValue: false, + ) as bool); + @override + set enableSystemBrightness(bool? enableSystemBrightness) => + super.noSuchMethod( + Invocation.setter( + #enableSystemBrightness, + enableSystemBrightness, + ), + returnValueForMissingStub: null, + ); + @override + String get themeId => (super.noSuchMethod( + Invocation.getter(#themeId), + returnValue: '', + ) as String); + @override + set themeId(String? themeId) => super.noSuchMethod( + Invocation.setter( + #themeId, + themeId, + ), + returnValueForMissingStub: null, + ); + @override + String get systemBrightnessLightThemeId => (super.noSuchMethod( + Invocation.getter(#systemBrightnessLightThemeId), + returnValue: '', + ) as String); + @override + set systemBrightnessLightThemeId(String? systemBrightnessLightThemeId) => + super.noSuchMethod( + Invocation.setter( + #systemBrightnessLightThemeId, + systemBrightnessLightThemeId, + ), + returnValueForMissingStub: null, + ); + @override + String get systemBrightnessDarkThemeId => (super.noSuchMethod( + Invocation.getter(#systemBrightnessDarkThemeId), + returnValue: '', + ) as String); + @override + set systemBrightnessDarkThemeId(String? systemBrightnessDarkThemeId) => + super.noSuchMethod( + Invocation.setter( + #systemBrightnessDarkThemeId, + systemBrightnessDarkThemeId, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i7.Future init() => (super.noSuchMethod( + _i6.Future init() => (super.noSuchMethod( Invocation.method( #init, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); @override - _i7.Future incrementCurrentNotificationIndex() => (super.noSuchMethod( + _i6.Future incrementCurrentNotificationIndex() => (super.noSuchMethod( Invocation.method( #incrementCurrentNotificationIndex, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); @override - _i7.Future isExternalCallsSet() => (super.noSuchMethod( + _i6.Future isExternalCallsSet() => (super.noSuchMethod( Invocation.method( #isExternalCallsSet, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i6.Future.value(false), + ) as _i6.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + _i6.Future saveUserID(String? userId) => (super.noSuchMethod( + Invocation.method( + #saveUserID, + [userId], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod( + Invocation.method( + #saveSignupEpoch, + [signupEpoch], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + void addListener(_i7.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -379,7 +460,7 @@ class MockPrefs extends _i1.Mock implements _i3.Prefs { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i7.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -407,24 +488,29 @@ class MockPrefs extends _i1.Mock implements _i3.Prefs { /// A class which mocks [TradesService]. /// /// See the documentation for Mockito's code generation for more information. -class MockTradesService extends _i1.Mock implements _i9.TradesService { +class MockTradesService extends _i1.Mock implements _i8.TradesService { MockTradesService() { _i1.throwOnMissingStub(this); } @override - List<_i10.Trade> get trades => (super.noSuchMethod( + List<_i9.Trade> get trades => (super.noSuchMethod( Invocation.getter(#trades), - returnValue: <_i10.Trade>[], - ) as List<_i10.Trade>); + returnValue: <_i9.Trade>[], + ) as List<_i9.Trade>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i7.Future add({ - required _i10.Trade? trade, + _i9.Trade? get(String? tradeId) => (super.noSuchMethod(Invocation.method( + #get, + [tradeId], + )) as _i9.Trade?); + @override + _i6.Future add({ + required _i9.Trade? trade, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -436,12 +522,12 @@ class MockTradesService extends _i1.Mock implements _i9.TradesService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); @override - _i7.Future edit({ - required _i10.Trade? trade, + _i6.Future edit({ + required _i9.Trade? trade, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -453,12 +539,12 @@ class MockTradesService extends _i1.Mock implements _i9.TradesService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); @override - _i7.Future delete({ - required _i10.Trade? trade, + _i6.Future delete({ + required _i9.Trade? trade, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -470,11 +556,11 @@ class MockTradesService extends _i1.Mock implements _i9.TradesService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); @override - _i7.Future deleteByUuid({ + _i6.Future deleteByUuid({ required String? uuid, required bool? shouldNotifyListeners, }) => @@ -487,11 +573,11 @@ class MockTradesService extends _i1.Mock implements _i9.TradesService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i7.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -499,7 +585,7 @@ class MockTradesService extends _i1.Mock implements _i9.TradesService { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i7.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -527,7 +613,7 @@ class MockTradesService extends _i1.Mock implements _i9.TradesService { /// A class which mocks [TradeNotesService]. /// /// See the documentation for Mockito's code generation for more information. -class MockTradeNotesService extends _i1.Mock implements _i11.TradeNotesService { +class MockTradeNotesService extends _i1.Mock implements _i10.TradeNotesService { MockTradeNotesService() { _i1.throwOnMissingStub(this); } @@ -552,7 +638,7 @@ class MockTradeNotesService extends _i1.Mock implements _i11.TradeNotesService { returnValue: '', ) as String); @override - _i7.Future set({ + _i6.Future set({ required String? tradeId, required String? note, }) => @@ -565,21 +651,21 @@ class MockTradeNotesService extends _i1.Mock implements _i11.TradeNotesService { #note: note, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); @override - _i7.Future delete({required String? tradeId}) => (super.noSuchMethod( + _i6.Future delete({required String? tradeId}) => (super.noSuchMethod( Invocation.method( #delete, [], {#tradeId: tradeId}, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i7.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -587,7 +673,7 @@ class MockTradeNotesService extends _i1.Mock implements _i11.TradeNotesService { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i7.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -615,13 +701,13 @@ class MockTradeNotesService extends _i1.Mock implements _i11.TradeNotesService { /// A class which mocks [ChangeNowAPI]. /// /// See the documentation for Mockito's code generation for more information. -class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { +class MockChangeNowAPI extends _i1.Mock implements _i11.ChangeNowAPI { MockChangeNowAPI() { _i1.throwOnMissingStub(this); } @override - set client(_i13.Client? _client) => super.noSuchMethod( + set client(_i12.Client? _client) => super.noSuchMethod( Invocation.setter( #client, _client, @@ -629,7 +715,7 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { returnValueForMissingStub: null, ); @override - _i7.Future<_i2.ExchangeResponse>> getAvailableCurrencies({ + _i6.Future<_i2.ExchangeResponse>> getAvailableCurrencies({ bool? fixedRate, bool? active, }) => @@ -643,8 +729,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { }, ), returnValue: - _i7.Future<_i2.ExchangeResponse>>.value( - _FakeExchangeResponse_0>( + _i6.Future<_i2.ExchangeResponse>>.value( + _FakeExchangeResponse_0>( this, Invocation.method( #getAvailableCurrencies, @@ -655,9 +741,26 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { }, ), )), - ) as _i7.Future<_i2.ExchangeResponse>>); + ) as _i6.Future<_i2.ExchangeResponse>>); @override - _i7.Future<_i2.ExchangeResponse>> getPairedCurrencies({ + _i6.Future<_i2.ExchangeResponse>> getCurrenciesV2() => + (super.noSuchMethod( + Invocation.method( + #getCurrenciesV2, + [], + ), + returnValue: + _i6.Future<_i2.ExchangeResponse>>.value( + _FakeExchangeResponse_0>( + this, + Invocation.method( + #getCurrenciesV2, + [], + ), + )), + ) as _i6.Future<_i2.ExchangeResponse>>); + @override + _i6.Future<_i2.ExchangeResponse>> getPairedCurrencies({ required String? ticker, bool? fixedRate, }) => @@ -671,8 +774,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { }, ), returnValue: - _i7.Future<_i2.ExchangeResponse>>.value( - _FakeExchangeResponse_0>( + _i6.Future<_i2.ExchangeResponse>>.value( + _FakeExchangeResponse_0>( this, Invocation.method( #getPairedCurrencies, @@ -683,9 +786,9 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { }, ), )), - ) as _i7.Future<_i2.ExchangeResponse>>); + ) as _i6.Future<_i2.ExchangeResponse>>); @override - _i7.Future<_i2.ExchangeResponse<_i15.Decimal>> getMinimalExchangeAmount({ + _i6.Future<_i2.ExchangeResponse<_i14.Decimal>> getMinimalExchangeAmount({ required String? fromTicker, required String? toTicker, String? apiKey, @@ -700,8 +803,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { #apiKey: apiKey, }, ), - returnValue: _i7.Future<_i2.ExchangeResponse<_i15.Decimal>>.value( - _FakeExchangeResponse_0<_i15.Decimal>( + returnValue: _i6.Future<_i2.ExchangeResponse<_i14.Decimal>>.value( + _FakeExchangeResponse_0<_i14.Decimal>( this, Invocation.method( #getMinimalExchangeAmount, @@ -713,9 +816,9 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { }, ), )), - ) as _i7.Future<_i2.ExchangeResponse<_i15.Decimal>>); + ) as _i6.Future<_i2.ExchangeResponse<_i14.Decimal>>); @override - _i7.Future<_i2.ExchangeResponse<_i16.Range>> getRange({ + _i6.Future<_i2.ExchangeResponse<_i15.Range>> getRange({ required String? fromTicker, required String? toTicker, required bool? isFixedRate, @@ -732,8 +835,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { #apiKey: apiKey, }, ), - returnValue: _i7.Future<_i2.ExchangeResponse<_i16.Range>>.value( - _FakeExchangeResponse_0<_i16.Range>( + returnValue: _i6.Future<_i2.ExchangeResponse<_i15.Range>>.value( + _FakeExchangeResponse_0<_i15.Range>( this, Invocation.method( #getRange, @@ -746,12 +849,12 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { }, ), )), - ) as _i7.Future<_i2.ExchangeResponse<_i16.Range>>); + ) as _i6.Future<_i2.ExchangeResponse<_i15.Range>>); @override - _i7.Future<_i2.ExchangeResponse<_i17.Estimate>> getEstimatedExchangeAmount({ + _i6.Future<_i2.ExchangeResponse<_i16.Estimate>> getEstimatedExchangeAmount({ required String? fromTicker, required String? toTicker, - required _i15.Decimal? fromAmount, + required _i14.Decimal? fromAmount, String? apiKey, }) => (super.noSuchMethod( @@ -765,8 +868,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { #apiKey: apiKey, }, ), - returnValue: _i7.Future<_i2.ExchangeResponse<_i17.Estimate>>.value( - _FakeExchangeResponse_0<_i17.Estimate>( + returnValue: _i6.Future<_i2.ExchangeResponse<_i16.Estimate>>.value( + _FakeExchangeResponse_0<_i16.Estimate>( this, Invocation.method( #getEstimatedExchangeAmount, @@ -779,13 +882,13 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { }, ), )), - ) as _i7.Future<_i2.ExchangeResponse<_i17.Estimate>>); + ) as _i6.Future<_i2.ExchangeResponse<_i16.Estimate>>); @override - _i7.Future<_i2.ExchangeResponse<_i17.Estimate>> + _i6.Future<_i2.ExchangeResponse<_i16.Estimate>> getEstimatedExchangeAmountFixedRate({ required String? fromTicker, required String? toTicker, - required _i15.Decimal? fromAmount, + required _i14.Decimal? fromAmount, required bool? reversed, bool? useRateId = true, String? apiKey, @@ -803,8 +906,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { #apiKey: apiKey, }, ), - returnValue: _i7.Future<_i2.ExchangeResponse<_i17.Estimate>>.value( - _FakeExchangeResponse_0<_i17.Estimate>( + returnValue: _i6.Future<_i2.ExchangeResponse<_i16.Estimate>>.value( + _FakeExchangeResponse_0<_i16.Estimate>( this, Invocation.method( #getEstimatedExchangeAmountFixedRate, @@ -819,17 +922,17 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { }, ), )), - ) as _i7.Future<_i2.ExchangeResponse<_i17.Estimate>>); + ) as _i6.Future<_i2.ExchangeResponse<_i16.Estimate>>); @override - _i7.Future<_i2.ExchangeResponse<_i18.CNExchangeEstimate>> + _i6.Future<_i2.ExchangeResponse<_i17.CNExchangeEstimate>> getEstimatedExchangeAmountV2({ required String? fromTicker, required String? toTicker, - required _i18.CNEstimateType? fromOrTo, - required _i15.Decimal? amount, + required _i17.CNEstimateType? fromOrTo, + required _i14.Decimal? amount, String? fromNetwork, String? toNetwork, - _i18.CNFlowType? flow = _i18.CNFlowType.standard, + _i17.CNFlowType? flow = _i17.CNFlowType.standard, String? apiKey, }) => (super.noSuchMethod( @@ -848,8 +951,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { }, ), returnValue: - _i7.Future<_i2.ExchangeResponse<_i18.CNExchangeEstimate>>.value( - _FakeExchangeResponse_0<_i18.CNExchangeEstimate>( + _i6.Future<_i2.ExchangeResponse<_i17.CNExchangeEstimate>>.value( + _FakeExchangeResponse_0<_i17.CNExchangeEstimate>( this, Invocation.method( #getEstimatedExchangeAmountV2, @@ -866,18 +969,18 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { }, ), )), - ) as _i7.Future<_i2.ExchangeResponse<_i18.CNExchangeEstimate>>); + ) as _i6.Future<_i2.ExchangeResponse<_i17.CNExchangeEstimate>>); @override - _i7.Future<_i2.ExchangeResponse>> + _i6.Future<_i2.ExchangeResponse>> getAvailableFixedRateMarkets({String? apiKey}) => (super.noSuchMethod( Invocation.method( #getAvailableFixedRateMarkets, [], {#apiKey: apiKey}, ), - returnValue: _i7.Future< - _i2.ExchangeResponse>>.value( - _FakeExchangeResponse_0>( + returnValue: _i6.Future< + _i2.ExchangeResponse>>.value( + _FakeExchangeResponse_0>( this, Invocation.method( #getAvailableFixedRateMarkets, @@ -885,14 +988,14 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { {#apiKey: apiKey}, ), )), - ) as _i7.Future<_i2.ExchangeResponse>>); + ) as _i6.Future<_i2.ExchangeResponse>>); @override - _i7.Future<_i2.ExchangeResponse<_i20.ExchangeTransaction>> + _i6.Future<_i2.ExchangeResponse<_i19.ExchangeTransaction>> createStandardExchangeTransaction({ required String? fromTicker, required String? toTicker, required String? receivingAddress, - required _i15.Decimal? amount, + required _i14.Decimal? amount, String? extraId = r'', String? userId = r'', String? contactEmail = r'', @@ -917,9 +1020,9 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { #apiKey: apiKey, }, ), - returnValue: _i7.Future< - _i2.ExchangeResponse<_i20.ExchangeTransaction>>.value( - _FakeExchangeResponse_0<_i20.ExchangeTransaction>( + returnValue: _i6.Future< + _i2.ExchangeResponse<_i19.ExchangeTransaction>>.value( + _FakeExchangeResponse_0<_i19.ExchangeTransaction>( this, Invocation.method( #createStandardExchangeTransaction, @@ -938,14 +1041,14 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { }, ), )), - ) as _i7.Future<_i2.ExchangeResponse<_i20.ExchangeTransaction>>); + ) as _i6.Future<_i2.ExchangeResponse<_i19.ExchangeTransaction>>); @override - _i7.Future<_i2.ExchangeResponse<_i20.ExchangeTransaction>> + _i6.Future<_i2.ExchangeResponse<_i19.ExchangeTransaction>> createFixedRateExchangeTransaction({ required String? fromTicker, required String? toTicker, required String? receivingAddress, - required _i15.Decimal? amount, + required _i14.Decimal? amount, required String? rateId, required bool? reversed, String? extraId = r'', @@ -974,9 +1077,9 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { #apiKey: apiKey, }, ), - returnValue: _i7.Future< - _i2.ExchangeResponse<_i20.ExchangeTransaction>>.value( - _FakeExchangeResponse_0<_i20.ExchangeTransaction>( + returnValue: _i6.Future< + _i2.ExchangeResponse<_i19.ExchangeTransaction>>.value( + _FakeExchangeResponse_0<_i19.ExchangeTransaction>( this, Invocation.method( #createFixedRateExchangeTransaction, @@ -997,9 +1100,9 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { }, ), )), - ) as _i7.Future<_i2.ExchangeResponse<_i20.ExchangeTransaction>>); + ) as _i6.Future<_i2.ExchangeResponse<_i19.ExchangeTransaction>>); @override - _i7.Future<_i2.ExchangeResponse<_i21.ExchangeTransactionStatus>> + _i6.Future<_i2.ExchangeResponse<_i20.ExchangeTransactionStatus>> getTransactionStatus({ required String? id, String? apiKey, @@ -1013,9 +1116,9 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { #apiKey: apiKey, }, ), - returnValue: _i7.Future< - _i2.ExchangeResponse<_i21.ExchangeTransactionStatus>>.value( - _FakeExchangeResponse_0<_i21.ExchangeTransactionStatus>( + returnValue: _i6.Future< + _i2.ExchangeResponse<_i20.ExchangeTransactionStatus>>.value( + _FakeExchangeResponse_0<_i20.ExchangeTransactionStatus>( this, Invocation.method( #getTransactionStatus, @@ -1026,10 +1129,10 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { }, ), )), - ) as _i7 - .Future<_i2.ExchangeResponse<_i21.ExchangeTransactionStatus>>); + ) as _i6 + .Future<_i2.ExchangeResponse<_i20.ExchangeTransactionStatus>>); @override - _i7.Future<_i2.ExchangeResponse>> + _i6.Future<_i2.ExchangeResponse>> getAvailableFloatingRatePairs({bool? includePartners = false}) => (super.noSuchMethod( Invocation.method( @@ -1038,8 +1141,8 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { {#includePartners: includePartners}, ), returnValue: - _i7.Future<_i2.ExchangeResponse>>.value( - _FakeExchangeResponse_0>( + _i6.Future<_i2.ExchangeResponse>>.value( + _FakeExchangeResponse_0>( this, Invocation.method( #getAvailableFloatingRatePairs, @@ -1047,5 +1150,5 @@ class MockChangeNowAPI extends _i1.Mock implements _i12.ChangeNowAPI { {#includePartners: includePartners}, ), )), - ) as _i7.Future<_i2.ExchangeResponse>>); + ) as _i6.Future<_i2.ExchangeResponse>>); } diff --git a/test/screen_tests/lockscreen_view_screen_test.mocks.dart b/test/screen_tests/lockscreen_view_screen_test.mocks.dart index 2ea9822b6..acdd22a94 100644 --- a/test/screen_tests/lockscreen_view_screen_test.mocks.dart +++ b/test/screen_tests/lockscreen_view_screen_test.mocks.dart @@ -3,18 +3,20 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i7; -import 'dart:ui' as _i9; +import 'dart:async' as _i8; +import 'dart:ui' as _i10; -import 'package:decimal/decimal.dart' as _i5; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/models/balance.dart' as _i5; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i14; import 'package:stackwallet/models/models.dart' as _i4; -import 'package:stackwallet/models/node_model.dart' as _i11; +import 'package:stackwallet/models/node_model.dart' as _i12; import 'package:stackwallet/services/coins/coin_service.dart' as _i3; -import 'package:stackwallet/services/coins/manager.dart' as _i12; -import 'package:stackwallet/services/node_service.dart' as _i10; -import 'package:stackwallet/services/wallets_service.dart' as _i6; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i8; +import 'package:stackwallet/services/coins/manager.dart' as _i13; +import 'package:stackwallet/services/node_service.dart' as _i11; +import 'package:stackwallet/services/wallets_service.dart' as _i7; +import 'package:stackwallet/utilities/amount/amount.dart' as _i6; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i9; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' as _i2; @@ -61,8 +63,8 @@ class _FakeFeeObject_2 extends _i1.SmartFake implements _i4.FeeObject { ); } -class _FakeDecimal_3 extends _i1.SmartFake implements _i5.Decimal { - _FakeDecimal_3( +class _FakeBalance_3 extends _i1.SmartFake implements _i5.Balance { + _FakeBalance_3( Object parent, Invocation parentInvocation, ) : super( @@ -71,9 +73,8 @@ class _FakeDecimal_3 extends _i1.SmartFake implements _i5.Decimal { ); } -class _FakeTransactionData_4 extends _i1.SmartFake - implements _i4.TransactionData { - _FakeTransactionData_4( +class _FakeAmount_4 extends _i1.SmartFake implements _i6.Amount { + _FakeAmount_4( Object parent, Invocation parentInvocation, ) : super( @@ -85,21 +86,21 @@ class _FakeTransactionData_4 extends _i1.SmartFake /// A class which mocks [WalletsService]. /// /// See the documentation for Mockito's code generation for more information. -class MockWalletsService extends _i1.Mock implements _i6.WalletsService { +class MockWalletsService extends _i1.Mock implements _i7.WalletsService { @override - _i7.Future> get walletNames => + _i8.Future> get walletNames => (super.noSuchMethod( Invocation.getter(#walletNames), - returnValue: _i7.Future>.value( - {}), - ) as _i7.Future>); + returnValue: _i8.Future>.value( + {}), + ) as _i8.Future>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i7.Future renameWallet({ + _i8.Future renameWallet({ required String? from, required String? to, required bool? shouldNotifyListeners, @@ -114,13 +115,21 @@ class MockWalletsService extends _i1.Mock implements _i6.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future addExistingStackWallet({ + Map fetchWalletsData() => (super.noSuchMethod( + Invocation.method( + #fetchWalletsData, + [], + ), + returnValue: {}, + ) as Map); + @override + _i8.Future addExistingStackWallet({ required String? name, required String? walletId, - required _i8.Coin? coin, + required _i9.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -134,13 +143,13 @@ class MockWalletsService extends _i1.Mock implements _i6.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future addNewWallet({ + _i8.Future addNewWallet({ required String? name, - required _i8.Coin? coin, + required _i9.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -153,46 +162,46 @@ class MockWalletsService extends _i1.Mock implements _i6.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future> getFavoriteWalletIds() => (super.noSuchMethod( + _i8.Future> getFavoriteWalletIds() => (super.noSuchMethod( Invocation.method( #getFavoriteWalletIds, [], ), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); @override - _i7.Future saveFavoriteWalletIds(List? walletIds) => + _i8.Future saveFavoriteWalletIds(List? walletIds) => (super.noSuchMethod( Invocation.method( #saveFavoriteWalletIds, [walletIds], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future addFavorite(String? walletId) => (super.noSuchMethod( + _i8.Future addFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #addFavorite, [walletId], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future removeFavorite(String? walletId) => (super.noSuchMethod( + _i8.Future removeFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #removeFavorite, [walletId], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future moveFavorite({ + _i8.Future moveFavorite({ required int? fromIndex, required int? toIndex, }) => @@ -205,48 +214,48 @@ class MockWalletsService extends _i1.Mock implements _i6.WalletsService { #toIndex: toIndex, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future checkForDuplicate(String? name) => (super.noSuchMethod( + _i8.Future checkForDuplicate(String? name) => (super.noSuchMethod( Invocation.method( #checkForDuplicate, [name], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future getWalletId(String? walletName) => (super.noSuchMethod( + _i8.Future getWalletId(String? walletName) => (super.noSuchMethod( Invocation.method( #getWalletId, [walletName], ), - returnValue: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future isMnemonicVerified({required String? walletId}) => + _i8.Future isMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #isMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future setMnemonicVerified({required String? walletId}) => + _i8.Future setMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #setMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future deleteWallet( + _i8.Future deleteWallet( String? name, bool? shouldNotifyListeners, ) => @@ -258,20 +267,20 @@ class MockWalletsService extends _i1.Mock implements _i6.WalletsService { shouldNotifyListeners, ], ), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future.value(0), + ) as _i8.Future); @override - _i7.Future refreshWallets(bool? shouldNotifyListeners) => + _i8.Future refreshWallets(bool? shouldNotifyListeners) => (super.noSuchMethod( Invocation.method( #refreshWallets, [shouldNotifyListeners], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -279,7 +288,7 @@ class MockWalletsService extends _i1.Mock implements _i6.WalletsService { returnValueForMissingStub: null, ); @override - void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -307,7 +316,7 @@ class MockWalletsService extends _i1.Mock implements _i6.WalletsService { /// A class which mocks [NodeService]. /// /// See the documentation for Mockito's code generation for more information. -class MockNodeService extends _i1.Mock implements _i10.NodeService { +class MockNodeService extends _i1.Mock implements _i11.NodeService { @override _i2.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod( Invocation.getter(#secureStorageInterface), @@ -317,33 +326,33 @@ class MockNodeService extends _i1.Mock implements _i10.NodeService { ), ) as _i2.SecureStorageInterface); @override - List<_i11.NodeModel> get primaryNodes => (super.noSuchMethod( + List<_i12.NodeModel> get primaryNodes => (super.noSuchMethod( Invocation.getter(#primaryNodes), - returnValue: <_i11.NodeModel>[], - ) as List<_i11.NodeModel>); + returnValue: <_i12.NodeModel>[], + ) as List<_i12.NodeModel>); @override - List<_i11.NodeModel> get nodes => (super.noSuchMethod( + List<_i12.NodeModel> get nodes => (super.noSuchMethod( Invocation.getter(#nodes), - returnValue: <_i11.NodeModel>[], - ) as List<_i11.NodeModel>); + returnValue: <_i12.NodeModel>[], + ) as List<_i12.NodeModel>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i7.Future updateDefaults() => (super.noSuchMethod( + _i8.Future updateDefaults() => (super.noSuchMethod( Invocation.method( #updateDefaults, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future setPrimaryNodeFor({ - required _i8.Coin? coin, - required _i11.NodeModel? node, + _i8.Future setPrimaryNodeFor({ + required _i9.Coin? coin, + required _i12.NodeModel? node, bool? shouldNotifyListeners = false, }) => (super.noSuchMethod( @@ -356,44 +365,44 @@ class MockNodeService extends _i1.Mock implements _i10.NodeService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i11.NodeModel? getPrimaryNodeFor({required _i8.Coin? coin}) => + _i12.NodeModel? getPrimaryNodeFor({required _i9.Coin? coin}) => (super.noSuchMethod(Invocation.method( #getPrimaryNodeFor, [], {#coin: coin}, - )) as _i11.NodeModel?); + )) as _i12.NodeModel?); @override - List<_i11.NodeModel> getNodesFor(_i8.Coin? coin) => (super.noSuchMethod( + List<_i12.NodeModel> getNodesFor(_i9.Coin? coin) => (super.noSuchMethod( Invocation.method( #getNodesFor, [coin], ), - returnValue: <_i11.NodeModel>[], - ) as List<_i11.NodeModel>); + returnValue: <_i12.NodeModel>[], + ) as List<_i12.NodeModel>); @override - _i11.NodeModel? getNodeById({required String? id}) => + _i12.NodeModel? getNodeById({required String? id}) => (super.noSuchMethod(Invocation.method( #getNodeById, [], {#id: id}, - )) as _i11.NodeModel?); + )) as _i12.NodeModel?); @override - List<_i11.NodeModel> failoverNodesFor({required _i8.Coin? coin}) => + List<_i12.NodeModel> failoverNodesFor({required _i9.Coin? coin}) => (super.noSuchMethod( Invocation.method( #failoverNodesFor, [], {#coin: coin}, ), - returnValue: <_i11.NodeModel>[], - ) as List<_i11.NodeModel>); + returnValue: <_i12.NodeModel>[], + ) as List<_i12.NodeModel>); @override - _i7.Future add( - _i11.NodeModel? node, + _i8.Future add( + _i12.NodeModel? node, String? password, bool? shouldNotifyListeners, ) => @@ -406,11 +415,11 @@ class MockNodeService extends _i1.Mock implements _i10.NodeService { shouldNotifyListeners, ], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future delete( + _i8.Future delete( String? id, bool? shouldNotifyListeners, ) => @@ -422,11 +431,11 @@ class MockNodeService extends _i1.Mock implements _i10.NodeService { shouldNotifyListeners, ], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future setEnabledState( + _i8.Future setEnabledState( String? id, bool? enabled, bool? shouldNotifyListeners, @@ -440,12 +449,12 @@ class MockNodeService extends _i1.Mock implements _i10.NodeService { shouldNotifyListeners, ], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future edit( - _i11.NodeModel? editedNode, + _i8.Future edit( + _i12.NodeModel? editedNode, String? password, bool? shouldNotifyListeners, ) => @@ -458,20 +467,20 @@ class MockNodeService extends _i1.Mock implements _i10.NodeService { shouldNotifyListeners, ], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future updateCommunityNodes() => (super.noSuchMethod( + _i8.Future updateCommunityNodes() => (super.noSuchMethod( Invocation.method( #updateCommunityNodes, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -479,7 +488,7 @@ class MockNodeService extends _i1.Mock implements _i10.NodeService { returnValueForMissingStub: null, ); @override - void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -507,7 +516,7 @@ class MockNodeService extends _i1.Mock implements _i10.NodeService { /// A class which mocks [Manager]. /// /// See the documentation for Mockito's code generation for more information. -class MockManager extends _i1.Mock implements _i12.Manager { +class MockManager extends _i1.Mock implements _i13.Manager { @override bool get isActiveWallet => (super.noSuchMethod( Invocation.getter(#isActiveWallet), @@ -535,10 +544,10 @@ class MockManager extends _i1.Mock implements _i12.Manager { returnValue: false, ) as bool); @override - _i8.Coin get coin => (super.noSuchMethod( + _i9.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i8.Coin.bitcoin, - ) as _i8.Coin); + returnValue: _i9.Coin.bitcoin, + ) as _i9.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -571,90 +580,42 @@ class MockManager extends _i1.Mock implements _i12.Manager { returnValueForMissingStub: null, ); @override - _i7.Future<_i4.FeeObject> get fees => (super.noSuchMethod( + _i8.Future<_i4.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i7.Future<_i4.FeeObject>.value(_FakeFeeObject_2( + returnValue: _i8.Future<_i4.FeeObject>.value(_FakeFeeObject_2( this, Invocation.getter(#fees), )), - ) as _i7.Future<_i4.FeeObject>); + ) as _i8.Future<_i4.FeeObject>); @override - _i7.Future get maxFee => (super.noSuchMethod( + _i8.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future.value(0), + ) as _i8.Future); @override - _i7.Future get currentReceivingAddress => (super.noSuchMethod( + _i8.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future<_i5.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i7.Future<_i5.Decimal>.value(_FakeDecimal_3( + _i5.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_3( this, - Invocation.getter(#availableBalance), - )), - ) as _i7.Future<_i5.Decimal>); - @override - _i5.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_3( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i5.Decimal); + ) as _i5.Balance); @override - _i7.Future<_i5.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i7.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i7.Future<_i5.Decimal>); - @override - _i7.Future<_i5.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i7.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i7.Future<_i5.Decimal>); - @override - _i7.Future<_i5.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i7.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#totalBalance), - )), - ) as _i7.Future<_i5.Decimal>); - @override - _i5.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_3( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i5.Decimal); - @override - _i7.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); - @override - _i7.Future<_i4.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i8.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i7.Future<_i4.TransactionData>.value(_FakeTransactionData_4( - this, - Invocation.getter(#transactionData), - )), - ) as _i7.Future<_i4.TransactionData>); + _i8.Future>.value(<_i14.Transaction>[]), + ) as _i8.Future>); @override - _i7.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: _i7.Future>.value(<_i4.UtxoObject>[]), - ) as _i7.Future>); + _i8.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i8.Future>.value(<_i14.UTXO>[]), + ) as _i8.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -674,29 +635,74 @@ class MockManager extends _i1.Mock implements _i12.Manager { returnValue: '', ) as String); @override - _i7.Future> get mnemonic => (super.noSuchMethod( + _i8.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); + @override + _i8.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i8.Future.value(), + ) as _i8.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i8.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i8.Future.value(''), + ) as _i8.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i7.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i8.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -706,9 +712,9 @@ class MockManager extends _i1.Mock implements _i12.Manager { returnValueForMissingStub: null, ); @override - _i7.Future> prepareSend({ + _i8.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i6.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -717,50 +723,32 @@ class MockManager extends _i1.Mock implements _i12.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i7.Future>.value({}), - ) as _i7.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future confirmSend({required Map? txData}) => + _i8.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); - @override - _i7.Future refresh() => (super.noSuchMethod( + _i8.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -770,34 +758,35 @@ class MockManager extends _i1.Mock implements _i12.Manager { returnValue: false, ) as bool); @override - _i7.Future testNetworkConnection() => (super.noSuchMethod( + _i8.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future initializeNew() => (super.noSuchMethod( + _i8.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future initializeExisting() => (super.noSuchMethod( + _i8.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future recoverFromMnemonic({ + _i8.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -808,25 +797,26 @@ class MockManager extends _i1.Mock implements _i12.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future exitCurrentWallet() => (super.noSuchMethod( + _i8.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future fullRescan( + _i8.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -838,42 +828,52 @@ class MockManager extends _i1.Mock implements _i12.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); - @override - _i7.Future estimateFeeFor( - int? satoshiAmount, + _i8.Future<_i6.Amount> estimateFeeFor( + _i6.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future<_i6.Amount>.value(_FakeAmount_4( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i8.Future<_i6.Amount>); @override - _i7.Future generateNewAddress() => (super.noSuchMethod( + _i8.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( + _i8.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -881,7 +881,7 @@ class MockManager extends _i1.Mock implements _i12.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/screen_tests/main_view_tests/main_view_screen_testA_test.mocks.dart b/test/screen_tests/main_view_tests/main_view_screen_testA_test.mocks.dart index b2168f408..23054bf14 100644 --- a/test/screen_tests/main_view_tests/main_view_screen_testA_test.mocks.dart +++ b/test/screen_tests/main_view_tests/main_view_screen_testA_test.mocks.dart @@ -3,18 +3,20 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i6; -import 'dart:ui' as _i8; +import 'dart:async' as _i7; +import 'dart:ui' as _i9; -import 'package:decimal/decimal.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/models/balance.dart' as _i4; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i11; import 'package:stackwallet/models/models.dart' as _i3; import 'package:stackwallet/services/coins/coin_service.dart' as _i2; -import 'package:stackwallet/services/coins/manager.dart' as _i9; -import 'package:stackwallet/services/locale_service.dart' as _i11; -import 'package:stackwallet/services/notes_service.dart' as _i10; -import 'package:stackwallet/services/wallets_service.dart' as _i5; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i7; +import 'package:stackwallet/services/coins/manager.dart' as _i10; +import 'package:stackwallet/services/locale_service.dart' as _i13; +import 'package:stackwallet/services/notes_service.dart' as _i12; +import 'package:stackwallet/services/wallets_service.dart' as _i6; +import 'package:stackwallet/utilities/amount/amount.dart' as _i5; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i8; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -48,8 +50,8 @@ class _FakeFeeObject_1 extends _i1.SmartFake implements _i3.FeeObject { ); } -class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { - _FakeDecimal_2( +class _FakeBalance_2 extends _i1.SmartFake implements _i4.Balance { + _FakeBalance_2( Object parent, Invocation parentInvocation, ) : super( @@ -58,9 +60,8 @@ class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { ); } -class _FakeTransactionData_3 extends _i1.SmartFake - implements _i3.TransactionData { - _FakeTransactionData_3( +class _FakeAmount_3 extends _i1.SmartFake implements _i5.Amount { + _FakeAmount_3( Object parent, Invocation parentInvocation, ) : super( @@ -72,21 +73,21 @@ class _FakeTransactionData_3 extends _i1.SmartFake /// A class which mocks [WalletsService]. /// /// See the documentation for Mockito's code generation for more information. -class MockWalletsService extends _i1.Mock implements _i5.WalletsService { +class MockWalletsService extends _i1.Mock implements _i6.WalletsService { @override - _i6.Future> get walletNames => + _i7.Future> get walletNames => (super.noSuchMethod( Invocation.getter(#walletNames), - returnValue: _i6.Future>.value( - {}), - ) as _i6.Future>); + returnValue: _i7.Future>.value( + {}), + ) as _i7.Future>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i6.Future renameWallet({ + _i7.Future renameWallet({ required String? from, required String? to, required bool? shouldNotifyListeners, @@ -101,13 +102,21 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - _i6.Future addExistingStackWallet({ + Map fetchWalletsData() => (super.noSuchMethod( + Invocation.method( + #fetchWalletsData, + [], + ), + returnValue: {}, + ) as Map); + @override + _i7.Future addExistingStackWallet({ required String? name, required String? walletId, - required _i7.Coin? coin, + required _i8.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -121,13 +130,13 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future addNewWallet({ + _i7.Future addNewWallet({ required String? name, - required _i7.Coin? coin, + required _i8.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -140,46 +149,46 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future> getFavoriteWalletIds() => (super.noSuchMethod( + _i7.Future> getFavoriteWalletIds() => (super.noSuchMethod( Invocation.method( #getFavoriteWalletIds, [], ), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i7.Future>.value([]), + ) as _i7.Future>); @override - _i6.Future saveFavoriteWalletIds(List? walletIds) => + _i7.Future saveFavoriteWalletIds(List? walletIds) => (super.noSuchMethod( Invocation.method( #saveFavoriteWalletIds, [walletIds], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future addFavorite(String? walletId) => (super.noSuchMethod( + _i7.Future addFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #addFavorite, [walletId], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future removeFavorite(String? walletId) => (super.noSuchMethod( + _i7.Future removeFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #removeFavorite, [walletId], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future moveFavorite({ + _i7.Future moveFavorite({ required int? fromIndex, required int? toIndex, }) => @@ -192,48 +201,48 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { #toIndex: toIndex, }, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future checkForDuplicate(String? name) => (super.noSuchMethod( + _i7.Future checkForDuplicate(String? name) => (super.noSuchMethod( Invocation.method( #checkForDuplicate, [name], ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - _i6.Future getWalletId(String? walletName) => (super.noSuchMethod( + _i7.Future getWalletId(String? walletName) => (super.noSuchMethod( Invocation.method( #getWalletId, [walletName], ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future isMnemonicVerified({required String? walletId}) => + _i7.Future isMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #isMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - _i6.Future setMnemonicVerified({required String? walletId}) => + _i7.Future setMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #setMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future deleteWallet( + _i7.Future deleteWallet( String? name, bool? shouldNotifyListeners, ) => @@ -245,20 +254,20 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { shouldNotifyListeners, ], ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i7.Future.value(0), + ) as _i7.Future); @override - _i6.Future refreshWallets(bool? shouldNotifyListeners) => + _i7.Future refreshWallets(bool? shouldNotifyListeners) => (super.noSuchMethod( Invocation.method( #refreshWallets, [shouldNotifyListeners], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -266,7 +275,7 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -294,7 +303,7 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { /// A class which mocks [Manager]. /// /// See the documentation for Mockito's code generation for more information. -class MockManager extends _i1.Mock implements _i9.Manager { +class MockManager extends _i1.Mock implements _i10.Manager { @override bool get isActiveWallet => (super.noSuchMethod( Invocation.getter(#isActiveWallet), @@ -322,10 +331,10 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValue: false, ) as bool); @override - _i7.Coin get coin => (super.noSuchMethod( + _i8.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i7.Coin.bitcoin, - ) as _i7.Coin); + returnValue: _i8.Coin.bitcoin, + ) as _i8.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -358,90 +367,42 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValueForMissingStub: null, ); @override - _i6.Future<_i3.FeeObject> get fees => (super.noSuchMethod( + _i7.Future<_i3.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i6.Future<_i3.FeeObject>.value(_FakeFeeObject_1( + returnValue: _i7.Future<_i3.FeeObject>.value(_FakeFeeObject_1( this, Invocation.getter(#fees), )), - ) as _i6.Future<_i3.FeeObject>); + ) as _i7.Future<_i3.FeeObject>); @override - _i6.Future get maxFee => (super.noSuchMethod( + _i7.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i7.Future.value(0), + ) as _i7.Future); @override - _i6.Future get currentReceivingAddress => (super.noSuchMethod( + _i7.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i6.Future.value(''), - ) as _i6.Future); + returnValue: _i7.Future.value(''), + ) as _i7.Future); @override - _i6.Future<_i4.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i6.Future<_i4.Decimal>.value(_FakeDecimal_2( + _i4.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_2( this, - Invocation.getter(#availableBalance), - )), - ) as _i6.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i4.Decimal); + ) as _i4.Balance); @override - _i6.Future<_i4.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i6.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i6.Future<_i4.Decimal>); - @override - _i6.Future<_i4.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i6.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i6.Future<_i4.Decimal>); - @override - _i6.Future<_i4.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i6.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#totalBalance), - )), - ) as _i6.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i4.Decimal); - @override - _i6.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); - @override - _i6.Future<_i3.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i7.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i6.Future<_i3.TransactionData>.value(_FakeTransactionData_3( - this, - Invocation.getter(#transactionData), - )), - ) as _i6.Future<_i3.TransactionData>); + _i7.Future>.value(<_i11.Transaction>[]), + ) as _i7.Future>); @override - _i6.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: _i6.Future>.value(<_i3.UtxoObject>[]), - ) as _i6.Future>); + _i7.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i7.Future>.value(<_i11.UTXO>[]), + ) as _i7.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -461,29 +422,74 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValue: '', ) as String); @override - _i6.Future> get mnemonic => (super.noSuchMethod( + _i7.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i7.Future>.value([]), + ) as _i7.Future>); + @override + _i7.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i7.Future.value(), + ) as _i7.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i7.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i7.Future.value(''), + ) as _i7.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i6.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i7.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -493,9 +499,9 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValueForMissingStub: null, ); @override - _i6.Future> prepareSend({ + _i7.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i5.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -504,50 +510,32 @@ class MockManager extends _i1.Mock implements _i9.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i7.Future>.value({}), + ) as _i7.Future>); @override - _i6.Future confirmSend({required Map? txData}) => + _i7.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i6.Future.value(''), - ) as _i6.Future); + returnValue: _i7.Future.value(''), + ) as _i7.Future); @override - _i6.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i6.Future.value(''), - ) as _i6.Future); - @override - _i6.Future refresh() => (super.noSuchMethod( + _i7.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -557,34 +545,35 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValue: false, ) as bool); @override - _i6.Future testNetworkConnection() => (super.noSuchMethod( + _i7.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - _i6.Future initializeNew() => (super.noSuchMethod( + _i7.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future initializeExisting() => (super.noSuchMethod( + _i7.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future recoverFromMnemonic({ + _i7.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -595,25 +584,26 @@ class MockManager extends _i1.Mock implements _i9.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future exitCurrentWallet() => (super.noSuchMethod( + _i7.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future fullRescan( + _i7.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -625,42 +615,52 @@ class MockManager extends _i1.Mock implements _i9.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); - @override - _i6.Future estimateFeeFor( - int? satoshiAmount, + _i7.Future<_i5.Amount> estimateFeeFor( + _i5.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i7.Future<_i5.Amount>.value(_FakeAmount_3( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i7.Future<_i5.Amount>); @override - _i6.Future generateNewAddress() => (super.noSuchMethod( + _i7.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + _i7.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -668,7 +668,7 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -688,7 +688,7 @@ class MockManager extends _i1.Mock implements _i9.Manager { /// A class which mocks [NotesService]. /// /// See the documentation for Mockito's code generation for more information. -class MockNotesService extends _i1.Mock implements _i10.NotesService { +class MockNotesService extends _i1.Mock implements _i12.NotesService { @override String get walletId => (super.noSuchMethod( Invocation.getter(#walletId), @@ -700,34 +700,34 @@ class MockNotesService extends _i1.Mock implements _i10.NotesService { returnValue: {}, ) as Map); @override - _i6.Future> get notes => (super.noSuchMethod( + _i7.Future> get notes => (super.noSuchMethod( Invocation.getter(#notes), - returnValue: _i6.Future>.value({}), - ) as _i6.Future>); + returnValue: _i7.Future>.value({}), + ) as _i7.Future>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i6.Future> search(String? text) => (super.noSuchMethod( + _i7.Future> search(String? text) => (super.noSuchMethod( Invocation.method( #search, [text], ), - returnValue: _i6.Future>.value({}), - ) as _i6.Future>); + returnValue: _i7.Future>.value({}), + ) as _i7.Future>); @override - _i6.Future getNoteFor({required String? txid}) => (super.noSuchMethod( + _i7.Future getNoteFor({required String? txid}) => (super.noSuchMethod( Invocation.method( #getNoteFor, [], {#txid: txid}, ), - returnValue: _i6.Future.value(''), - ) as _i6.Future); + returnValue: _i7.Future.value(''), + ) as _i7.Future); @override - _i6.Future editOrAddNote({ + _i7.Future editOrAddNote({ required String? txid, required String? note, }) => @@ -740,21 +740,21 @@ class MockNotesService extends _i1.Mock implements _i10.NotesService { #note: note, }, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future deleteNote({required String? txid}) => (super.noSuchMethod( + _i7.Future deleteNote({required String? txid}) => (super.noSuchMethod( Invocation.method( #deleteNote, [], {#txid: txid}, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -762,7 +762,7 @@ class MockNotesService extends _i1.Mock implements _i10.NotesService { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -790,7 +790,7 @@ class MockNotesService extends _i1.Mock implements _i10.NotesService { /// A class which mocks [LocaleService]. /// /// See the documentation for Mockito's code generation for more information. -class MockLocaleService extends _i1.Mock implements _i11.LocaleService { +class MockLocaleService extends _i1.Mock implements _i13.LocaleService { @override String get locale => (super.noSuchMethod( Invocation.getter(#locale), @@ -802,17 +802,17 @@ class MockLocaleService extends _i1.Mock implements _i11.LocaleService { returnValue: false, ) as bool); @override - _i6.Future loadLocale({bool? notify = true}) => (super.noSuchMethod( + _i7.Future loadLocale({bool? notify = true}) => (super.noSuchMethod( Invocation.method( #loadLocale, [], {#notify: notify}, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -820,7 +820,7 @@ class MockLocaleService extends _i1.Mock implements _i11.LocaleService { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/screen_tests/main_view_tests/main_view_screen_testB_test.mocks.dart b/test/screen_tests/main_view_tests/main_view_screen_testB_test.mocks.dart index fdc326e3c..d511d0e22 100644 --- a/test/screen_tests/main_view_tests/main_view_screen_testB_test.mocks.dart +++ b/test/screen_tests/main_view_tests/main_view_screen_testB_test.mocks.dart @@ -3,18 +3,20 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i6; -import 'dart:ui' as _i8; +import 'dart:async' as _i7; +import 'dart:ui' as _i9; -import 'package:decimal/decimal.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/models/balance.dart' as _i4; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i11; import 'package:stackwallet/models/models.dart' as _i3; import 'package:stackwallet/services/coins/coin_service.dart' as _i2; -import 'package:stackwallet/services/coins/manager.dart' as _i9; -import 'package:stackwallet/services/locale_service.dart' as _i11; -import 'package:stackwallet/services/notes_service.dart' as _i10; -import 'package:stackwallet/services/wallets_service.dart' as _i5; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i7; +import 'package:stackwallet/services/coins/manager.dart' as _i10; +import 'package:stackwallet/services/locale_service.dart' as _i13; +import 'package:stackwallet/services/notes_service.dart' as _i12; +import 'package:stackwallet/services/wallets_service.dart' as _i6; +import 'package:stackwallet/utilities/amount/amount.dart' as _i5; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i8; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -48,8 +50,8 @@ class _FakeFeeObject_1 extends _i1.SmartFake implements _i3.FeeObject { ); } -class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { - _FakeDecimal_2( +class _FakeBalance_2 extends _i1.SmartFake implements _i4.Balance { + _FakeBalance_2( Object parent, Invocation parentInvocation, ) : super( @@ -58,9 +60,8 @@ class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { ); } -class _FakeTransactionData_3 extends _i1.SmartFake - implements _i3.TransactionData { - _FakeTransactionData_3( +class _FakeAmount_3 extends _i1.SmartFake implements _i5.Amount { + _FakeAmount_3( Object parent, Invocation parentInvocation, ) : super( @@ -72,21 +73,21 @@ class _FakeTransactionData_3 extends _i1.SmartFake /// A class which mocks [WalletsService]. /// /// See the documentation for Mockito's code generation for more information. -class MockWalletsService extends _i1.Mock implements _i5.WalletsService { +class MockWalletsService extends _i1.Mock implements _i6.WalletsService { @override - _i6.Future> get walletNames => + _i7.Future> get walletNames => (super.noSuchMethod( Invocation.getter(#walletNames), - returnValue: _i6.Future>.value( - {}), - ) as _i6.Future>); + returnValue: _i7.Future>.value( + {}), + ) as _i7.Future>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i6.Future renameWallet({ + _i7.Future renameWallet({ required String? from, required String? to, required bool? shouldNotifyListeners, @@ -101,13 +102,21 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - _i6.Future addExistingStackWallet({ + Map fetchWalletsData() => (super.noSuchMethod( + Invocation.method( + #fetchWalletsData, + [], + ), + returnValue: {}, + ) as Map); + @override + _i7.Future addExistingStackWallet({ required String? name, required String? walletId, - required _i7.Coin? coin, + required _i8.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -121,13 +130,13 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future addNewWallet({ + _i7.Future addNewWallet({ required String? name, - required _i7.Coin? coin, + required _i8.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -140,46 +149,46 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future> getFavoriteWalletIds() => (super.noSuchMethod( + _i7.Future> getFavoriteWalletIds() => (super.noSuchMethod( Invocation.method( #getFavoriteWalletIds, [], ), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i7.Future>.value([]), + ) as _i7.Future>); @override - _i6.Future saveFavoriteWalletIds(List? walletIds) => + _i7.Future saveFavoriteWalletIds(List? walletIds) => (super.noSuchMethod( Invocation.method( #saveFavoriteWalletIds, [walletIds], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future addFavorite(String? walletId) => (super.noSuchMethod( + _i7.Future addFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #addFavorite, [walletId], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future removeFavorite(String? walletId) => (super.noSuchMethod( + _i7.Future removeFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #removeFavorite, [walletId], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future moveFavorite({ + _i7.Future moveFavorite({ required int? fromIndex, required int? toIndex, }) => @@ -192,48 +201,48 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { #toIndex: toIndex, }, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future checkForDuplicate(String? name) => (super.noSuchMethod( + _i7.Future checkForDuplicate(String? name) => (super.noSuchMethod( Invocation.method( #checkForDuplicate, [name], ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - _i6.Future getWalletId(String? walletName) => (super.noSuchMethod( + _i7.Future getWalletId(String? walletName) => (super.noSuchMethod( Invocation.method( #getWalletId, [walletName], ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future isMnemonicVerified({required String? walletId}) => + _i7.Future isMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #isMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - _i6.Future setMnemonicVerified({required String? walletId}) => + _i7.Future setMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #setMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future deleteWallet( + _i7.Future deleteWallet( String? name, bool? shouldNotifyListeners, ) => @@ -245,20 +254,20 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { shouldNotifyListeners, ], ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i7.Future.value(0), + ) as _i7.Future); @override - _i6.Future refreshWallets(bool? shouldNotifyListeners) => + _i7.Future refreshWallets(bool? shouldNotifyListeners) => (super.noSuchMethod( Invocation.method( #refreshWallets, [shouldNotifyListeners], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -266,7 +275,7 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -294,7 +303,7 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { /// A class which mocks [Manager]. /// /// See the documentation for Mockito's code generation for more information. -class MockManager extends _i1.Mock implements _i9.Manager { +class MockManager extends _i1.Mock implements _i10.Manager { @override bool get isActiveWallet => (super.noSuchMethod( Invocation.getter(#isActiveWallet), @@ -322,10 +331,10 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValue: false, ) as bool); @override - _i7.Coin get coin => (super.noSuchMethod( + _i8.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i7.Coin.bitcoin, - ) as _i7.Coin); + returnValue: _i8.Coin.bitcoin, + ) as _i8.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -358,90 +367,42 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValueForMissingStub: null, ); @override - _i6.Future<_i3.FeeObject> get fees => (super.noSuchMethod( + _i7.Future<_i3.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i6.Future<_i3.FeeObject>.value(_FakeFeeObject_1( + returnValue: _i7.Future<_i3.FeeObject>.value(_FakeFeeObject_1( this, Invocation.getter(#fees), )), - ) as _i6.Future<_i3.FeeObject>); + ) as _i7.Future<_i3.FeeObject>); @override - _i6.Future get maxFee => (super.noSuchMethod( + _i7.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i7.Future.value(0), + ) as _i7.Future); @override - _i6.Future get currentReceivingAddress => (super.noSuchMethod( + _i7.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i6.Future.value(''), - ) as _i6.Future); + returnValue: _i7.Future.value(''), + ) as _i7.Future); @override - _i6.Future<_i4.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i6.Future<_i4.Decimal>.value(_FakeDecimal_2( + _i4.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_2( this, - Invocation.getter(#availableBalance), - )), - ) as _i6.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i4.Decimal); + ) as _i4.Balance); @override - _i6.Future<_i4.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i6.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i6.Future<_i4.Decimal>); - @override - _i6.Future<_i4.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i6.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i6.Future<_i4.Decimal>); - @override - _i6.Future<_i4.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i6.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#totalBalance), - )), - ) as _i6.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i4.Decimal); - @override - _i6.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); - @override - _i6.Future<_i3.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i7.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i6.Future<_i3.TransactionData>.value(_FakeTransactionData_3( - this, - Invocation.getter(#transactionData), - )), - ) as _i6.Future<_i3.TransactionData>); + _i7.Future>.value(<_i11.Transaction>[]), + ) as _i7.Future>); @override - _i6.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: _i6.Future>.value(<_i3.UtxoObject>[]), - ) as _i6.Future>); + _i7.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i7.Future>.value(<_i11.UTXO>[]), + ) as _i7.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -461,29 +422,74 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValue: '', ) as String); @override - _i6.Future> get mnemonic => (super.noSuchMethod( + _i7.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i7.Future>.value([]), + ) as _i7.Future>); + @override + _i7.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i7.Future.value(), + ) as _i7.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i7.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i7.Future.value(''), + ) as _i7.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i6.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i7.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -493,9 +499,9 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValueForMissingStub: null, ); @override - _i6.Future> prepareSend({ + _i7.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i5.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -504,50 +510,32 @@ class MockManager extends _i1.Mock implements _i9.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i7.Future>.value({}), + ) as _i7.Future>); @override - _i6.Future confirmSend({required Map? txData}) => + _i7.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i6.Future.value(''), - ) as _i6.Future); + returnValue: _i7.Future.value(''), + ) as _i7.Future); @override - _i6.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i6.Future.value(''), - ) as _i6.Future); - @override - _i6.Future refresh() => (super.noSuchMethod( + _i7.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -557,34 +545,35 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValue: false, ) as bool); @override - _i6.Future testNetworkConnection() => (super.noSuchMethod( + _i7.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - _i6.Future initializeNew() => (super.noSuchMethod( + _i7.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future initializeExisting() => (super.noSuchMethod( + _i7.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future recoverFromMnemonic({ + _i7.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -595,25 +584,26 @@ class MockManager extends _i1.Mock implements _i9.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future exitCurrentWallet() => (super.noSuchMethod( + _i7.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future fullRescan( + _i7.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -625,42 +615,52 @@ class MockManager extends _i1.Mock implements _i9.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); - @override - _i6.Future estimateFeeFor( - int? satoshiAmount, + _i7.Future<_i5.Amount> estimateFeeFor( + _i5.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i7.Future<_i5.Amount>.value(_FakeAmount_3( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i7.Future<_i5.Amount>); @override - _i6.Future generateNewAddress() => (super.noSuchMethod( + _i7.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + _i7.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -668,7 +668,7 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -688,7 +688,7 @@ class MockManager extends _i1.Mock implements _i9.Manager { /// A class which mocks [NotesService]. /// /// See the documentation for Mockito's code generation for more information. -class MockNotesService extends _i1.Mock implements _i10.NotesService { +class MockNotesService extends _i1.Mock implements _i12.NotesService { @override String get walletId => (super.noSuchMethod( Invocation.getter(#walletId), @@ -700,34 +700,34 @@ class MockNotesService extends _i1.Mock implements _i10.NotesService { returnValue: {}, ) as Map); @override - _i6.Future> get notes => (super.noSuchMethod( + _i7.Future> get notes => (super.noSuchMethod( Invocation.getter(#notes), - returnValue: _i6.Future>.value({}), - ) as _i6.Future>); + returnValue: _i7.Future>.value({}), + ) as _i7.Future>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i6.Future> search(String? text) => (super.noSuchMethod( + _i7.Future> search(String? text) => (super.noSuchMethod( Invocation.method( #search, [text], ), - returnValue: _i6.Future>.value({}), - ) as _i6.Future>); + returnValue: _i7.Future>.value({}), + ) as _i7.Future>); @override - _i6.Future getNoteFor({required String? txid}) => (super.noSuchMethod( + _i7.Future getNoteFor({required String? txid}) => (super.noSuchMethod( Invocation.method( #getNoteFor, [], {#txid: txid}, ), - returnValue: _i6.Future.value(''), - ) as _i6.Future); + returnValue: _i7.Future.value(''), + ) as _i7.Future); @override - _i6.Future editOrAddNote({ + _i7.Future editOrAddNote({ required String? txid, required String? note, }) => @@ -740,21 +740,21 @@ class MockNotesService extends _i1.Mock implements _i10.NotesService { #note: note, }, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future deleteNote({required String? txid}) => (super.noSuchMethod( + _i7.Future deleteNote({required String? txid}) => (super.noSuchMethod( Invocation.method( #deleteNote, [], {#txid: txid}, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -762,7 +762,7 @@ class MockNotesService extends _i1.Mock implements _i10.NotesService { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -790,7 +790,7 @@ class MockNotesService extends _i1.Mock implements _i10.NotesService { /// A class which mocks [LocaleService]. /// /// See the documentation for Mockito's code generation for more information. -class MockLocaleService extends _i1.Mock implements _i11.LocaleService { +class MockLocaleService extends _i1.Mock implements _i13.LocaleService { @override String get locale => (super.noSuchMethod( Invocation.getter(#locale), @@ -802,17 +802,17 @@ class MockLocaleService extends _i1.Mock implements _i11.LocaleService { returnValue: false, ) as bool); @override - _i6.Future loadLocale({bool? notify = true}) => (super.noSuchMethod( + _i7.Future loadLocale({bool? notify = true}) => (super.noSuchMethod( Invocation.method( #loadLocale, [], {#notify: notify}, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -820,7 +820,7 @@ class MockLocaleService extends _i1.Mock implements _i11.LocaleService { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/screen_tests/main_view_tests/main_view_screen_testC_test.mocks.dart b/test/screen_tests/main_view_tests/main_view_screen_testC_test.mocks.dart index b3b16f4cb..7d1a3f0fc 100644 --- a/test/screen_tests/main_view_tests/main_view_screen_testC_test.mocks.dart +++ b/test/screen_tests/main_view_tests/main_view_screen_testC_test.mocks.dart @@ -3,18 +3,20 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i6; -import 'dart:ui' as _i8; +import 'dart:async' as _i7; +import 'dart:ui' as _i9; -import 'package:decimal/decimal.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/models/balance.dart' as _i4; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i11; import 'package:stackwallet/models/models.dart' as _i3; import 'package:stackwallet/services/coins/coin_service.dart' as _i2; -import 'package:stackwallet/services/coins/manager.dart' as _i9; -import 'package:stackwallet/services/locale_service.dart' as _i11; -import 'package:stackwallet/services/notes_service.dart' as _i10; -import 'package:stackwallet/services/wallets_service.dart' as _i5; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i7; +import 'package:stackwallet/services/coins/manager.dart' as _i10; +import 'package:stackwallet/services/locale_service.dart' as _i13; +import 'package:stackwallet/services/notes_service.dart' as _i12; +import 'package:stackwallet/services/wallets_service.dart' as _i6; +import 'package:stackwallet/utilities/amount/amount.dart' as _i5; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i8; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -48,8 +50,8 @@ class _FakeFeeObject_1 extends _i1.SmartFake implements _i3.FeeObject { ); } -class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { - _FakeDecimal_2( +class _FakeBalance_2 extends _i1.SmartFake implements _i4.Balance { + _FakeBalance_2( Object parent, Invocation parentInvocation, ) : super( @@ -58,9 +60,8 @@ class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { ); } -class _FakeTransactionData_3 extends _i1.SmartFake - implements _i3.TransactionData { - _FakeTransactionData_3( +class _FakeAmount_3 extends _i1.SmartFake implements _i5.Amount { + _FakeAmount_3( Object parent, Invocation parentInvocation, ) : super( @@ -72,21 +73,21 @@ class _FakeTransactionData_3 extends _i1.SmartFake /// A class which mocks [WalletsService]. /// /// See the documentation for Mockito's code generation for more information. -class MockWalletsService extends _i1.Mock implements _i5.WalletsService { +class MockWalletsService extends _i1.Mock implements _i6.WalletsService { @override - _i6.Future> get walletNames => + _i7.Future> get walletNames => (super.noSuchMethod( Invocation.getter(#walletNames), - returnValue: _i6.Future>.value( - {}), - ) as _i6.Future>); + returnValue: _i7.Future>.value( + {}), + ) as _i7.Future>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i6.Future renameWallet({ + _i7.Future renameWallet({ required String? from, required String? to, required bool? shouldNotifyListeners, @@ -101,13 +102,21 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - _i6.Future addExistingStackWallet({ + Map fetchWalletsData() => (super.noSuchMethod( + Invocation.method( + #fetchWalletsData, + [], + ), + returnValue: {}, + ) as Map); + @override + _i7.Future addExistingStackWallet({ required String? name, required String? walletId, - required _i7.Coin? coin, + required _i8.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -121,13 +130,13 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future addNewWallet({ + _i7.Future addNewWallet({ required String? name, - required _i7.Coin? coin, + required _i8.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -140,46 +149,46 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future> getFavoriteWalletIds() => (super.noSuchMethod( + _i7.Future> getFavoriteWalletIds() => (super.noSuchMethod( Invocation.method( #getFavoriteWalletIds, [], ), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i7.Future>.value([]), + ) as _i7.Future>); @override - _i6.Future saveFavoriteWalletIds(List? walletIds) => + _i7.Future saveFavoriteWalletIds(List? walletIds) => (super.noSuchMethod( Invocation.method( #saveFavoriteWalletIds, [walletIds], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future addFavorite(String? walletId) => (super.noSuchMethod( + _i7.Future addFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #addFavorite, [walletId], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future removeFavorite(String? walletId) => (super.noSuchMethod( + _i7.Future removeFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #removeFavorite, [walletId], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future moveFavorite({ + _i7.Future moveFavorite({ required int? fromIndex, required int? toIndex, }) => @@ -192,48 +201,48 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { #toIndex: toIndex, }, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future checkForDuplicate(String? name) => (super.noSuchMethod( + _i7.Future checkForDuplicate(String? name) => (super.noSuchMethod( Invocation.method( #checkForDuplicate, [name], ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - _i6.Future getWalletId(String? walletName) => (super.noSuchMethod( + _i7.Future getWalletId(String? walletName) => (super.noSuchMethod( Invocation.method( #getWalletId, [walletName], ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future isMnemonicVerified({required String? walletId}) => + _i7.Future isMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #isMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - _i6.Future setMnemonicVerified({required String? walletId}) => + _i7.Future setMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #setMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future deleteWallet( + _i7.Future deleteWallet( String? name, bool? shouldNotifyListeners, ) => @@ -245,20 +254,20 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { shouldNotifyListeners, ], ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i7.Future.value(0), + ) as _i7.Future); @override - _i6.Future refreshWallets(bool? shouldNotifyListeners) => + _i7.Future refreshWallets(bool? shouldNotifyListeners) => (super.noSuchMethod( Invocation.method( #refreshWallets, [shouldNotifyListeners], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -266,7 +275,7 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -294,7 +303,7 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { /// A class which mocks [Manager]. /// /// See the documentation for Mockito's code generation for more information. -class MockManager extends _i1.Mock implements _i9.Manager { +class MockManager extends _i1.Mock implements _i10.Manager { @override bool get isActiveWallet => (super.noSuchMethod( Invocation.getter(#isActiveWallet), @@ -322,10 +331,10 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValue: false, ) as bool); @override - _i7.Coin get coin => (super.noSuchMethod( + _i8.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i7.Coin.bitcoin, - ) as _i7.Coin); + returnValue: _i8.Coin.bitcoin, + ) as _i8.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -358,90 +367,42 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValueForMissingStub: null, ); @override - _i6.Future<_i3.FeeObject> get fees => (super.noSuchMethod( + _i7.Future<_i3.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i6.Future<_i3.FeeObject>.value(_FakeFeeObject_1( + returnValue: _i7.Future<_i3.FeeObject>.value(_FakeFeeObject_1( this, Invocation.getter(#fees), )), - ) as _i6.Future<_i3.FeeObject>); + ) as _i7.Future<_i3.FeeObject>); @override - _i6.Future get maxFee => (super.noSuchMethod( + _i7.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i7.Future.value(0), + ) as _i7.Future); @override - _i6.Future get currentReceivingAddress => (super.noSuchMethod( + _i7.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i6.Future.value(''), - ) as _i6.Future); + returnValue: _i7.Future.value(''), + ) as _i7.Future); @override - _i6.Future<_i4.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i6.Future<_i4.Decimal>.value(_FakeDecimal_2( + _i4.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_2( this, - Invocation.getter(#availableBalance), - )), - ) as _i6.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i4.Decimal); + ) as _i4.Balance); @override - _i6.Future<_i4.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i6.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i6.Future<_i4.Decimal>); - @override - _i6.Future<_i4.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i6.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i6.Future<_i4.Decimal>); - @override - _i6.Future<_i4.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i6.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#totalBalance), - )), - ) as _i6.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i4.Decimal); - @override - _i6.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); - @override - _i6.Future<_i3.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i7.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i6.Future<_i3.TransactionData>.value(_FakeTransactionData_3( - this, - Invocation.getter(#transactionData), - )), - ) as _i6.Future<_i3.TransactionData>); + _i7.Future>.value(<_i11.Transaction>[]), + ) as _i7.Future>); @override - _i6.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: _i6.Future>.value(<_i3.UtxoObject>[]), - ) as _i6.Future>); + _i7.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i7.Future>.value(<_i11.UTXO>[]), + ) as _i7.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -461,29 +422,74 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValue: '', ) as String); @override - _i6.Future> get mnemonic => (super.noSuchMethod( + _i7.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i7.Future>.value([]), + ) as _i7.Future>); + @override + _i7.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i7.Future.value(), + ) as _i7.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i7.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i7.Future.value(''), + ) as _i7.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i6.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i7.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -493,9 +499,9 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValueForMissingStub: null, ); @override - _i6.Future> prepareSend({ + _i7.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i5.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -504,50 +510,32 @@ class MockManager extends _i1.Mock implements _i9.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i7.Future>.value({}), + ) as _i7.Future>); @override - _i6.Future confirmSend({required Map? txData}) => + _i7.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i6.Future.value(''), - ) as _i6.Future); + returnValue: _i7.Future.value(''), + ) as _i7.Future); @override - _i6.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i6.Future.value(''), - ) as _i6.Future); - @override - _i6.Future refresh() => (super.noSuchMethod( + _i7.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -557,34 +545,35 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValue: false, ) as bool); @override - _i6.Future testNetworkConnection() => (super.noSuchMethod( + _i7.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - _i6.Future initializeNew() => (super.noSuchMethod( + _i7.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future initializeExisting() => (super.noSuchMethod( + _i7.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future recoverFromMnemonic({ + _i7.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -595,25 +584,26 @@ class MockManager extends _i1.Mock implements _i9.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future exitCurrentWallet() => (super.noSuchMethod( + _i7.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future fullRescan( + _i7.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -625,42 +615,52 @@ class MockManager extends _i1.Mock implements _i9.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); - @override - _i6.Future estimateFeeFor( - int? satoshiAmount, + _i7.Future<_i5.Amount> estimateFeeFor( + _i5.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i7.Future<_i5.Amount>.value(_FakeAmount_3( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i7.Future<_i5.Amount>); @override - _i6.Future generateNewAddress() => (super.noSuchMethod( + _i7.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + _i7.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -668,7 +668,7 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -688,7 +688,7 @@ class MockManager extends _i1.Mock implements _i9.Manager { /// A class which mocks [NotesService]. /// /// See the documentation for Mockito's code generation for more information. -class MockNotesService extends _i1.Mock implements _i10.NotesService { +class MockNotesService extends _i1.Mock implements _i12.NotesService { @override String get walletId => (super.noSuchMethod( Invocation.getter(#walletId), @@ -700,34 +700,34 @@ class MockNotesService extends _i1.Mock implements _i10.NotesService { returnValue: {}, ) as Map); @override - _i6.Future> get notes => (super.noSuchMethod( + _i7.Future> get notes => (super.noSuchMethod( Invocation.getter(#notes), - returnValue: _i6.Future>.value({}), - ) as _i6.Future>); + returnValue: _i7.Future>.value({}), + ) as _i7.Future>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i6.Future> search(String? text) => (super.noSuchMethod( + _i7.Future> search(String? text) => (super.noSuchMethod( Invocation.method( #search, [text], ), - returnValue: _i6.Future>.value({}), - ) as _i6.Future>); + returnValue: _i7.Future>.value({}), + ) as _i7.Future>); @override - _i6.Future getNoteFor({required String? txid}) => (super.noSuchMethod( + _i7.Future getNoteFor({required String? txid}) => (super.noSuchMethod( Invocation.method( #getNoteFor, [], {#txid: txid}, ), - returnValue: _i6.Future.value(''), - ) as _i6.Future); + returnValue: _i7.Future.value(''), + ) as _i7.Future); @override - _i6.Future editOrAddNote({ + _i7.Future editOrAddNote({ required String? txid, required String? note, }) => @@ -740,21 +740,21 @@ class MockNotesService extends _i1.Mock implements _i10.NotesService { #note: note, }, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future deleteNote({required String? txid}) => (super.noSuchMethod( + _i7.Future deleteNote({required String? txid}) => (super.noSuchMethod( Invocation.method( #deleteNote, [], {#txid: txid}, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -762,7 +762,7 @@ class MockNotesService extends _i1.Mock implements _i10.NotesService { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -790,7 +790,7 @@ class MockNotesService extends _i1.Mock implements _i10.NotesService { /// A class which mocks [LocaleService]. /// /// See the documentation for Mockito's code generation for more information. -class MockLocaleService extends _i1.Mock implements _i11.LocaleService { +class MockLocaleService extends _i1.Mock implements _i13.LocaleService { @override String get locale => (super.noSuchMethod( Invocation.getter(#locale), @@ -802,17 +802,17 @@ class MockLocaleService extends _i1.Mock implements _i11.LocaleService { returnValue: false, ) as bool); @override - _i6.Future loadLocale({bool? notify = true}) => (super.noSuchMethod( + _i7.Future loadLocale({bool? notify = true}) => (super.noSuchMethod( Invocation.method( #loadLocale, [], {#notify: notify}, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -820,7 +820,7 @@ class MockLocaleService extends _i1.Mock implements _i11.LocaleService { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/screen_tests/onboarding/backup_key_view_screen_test.mocks.dart b/test/screen_tests/onboarding/backup_key_view_screen_test.mocks.dart index 1e3c575cf..7a0270532 100644 --- a/test/screen_tests/onboarding/backup_key_view_screen_test.mocks.dart +++ b/test/screen_tests/onboarding/backup_key_view_screen_test.mocks.dart @@ -3,15 +3,17 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i7; -import 'dart:ui' as _i8; +import 'dart:async' as _i8; +import 'dart:ui' as _i10; -import 'package:decimal/decimal.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/models/balance.dart' as _i4; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i9; import 'package:stackwallet/models/models.dart' as _i3; import 'package:stackwallet/services/coins/coin_service.dart' as _i2; -import 'package:stackwallet/services/coins/manager.dart' as _i5; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i6; +import 'package:stackwallet/services/coins/manager.dart' as _i6; +import 'package:stackwallet/utilities/amount/amount.dart' as _i5; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i7; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -45,8 +47,8 @@ class _FakeFeeObject_1 extends _i1.SmartFake implements _i3.FeeObject { ); } -class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { - _FakeDecimal_2( +class _FakeBalance_2 extends _i1.SmartFake implements _i4.Balance { + _FakeBalance_2( Object parent, Invocation parentInvocation, ) : super( @@ -55,9 +57,8 @@ class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { ); } -class _FakeTransactionData_3 extends _i1.SmartFake - implements _i3.TransactionData { - _FakeTransactionData_3( +class _FakeAmount_3 extends _i1.SmartFake implements _i5.Amount { + _FakeAmount_3( Object parent, Invocation parentInvocation, ) : super( @@ -69,7 +70,7 @@ class _FakeTransactionData_3 extends _i1.SmartFake /// A class which mocks [Manager]. /// /// See the documentation for Mockito's code generation for more information. -class MockManager extends _i1.Mock implements _i5.Manager { +class MockManager extends _i1.Mock implements _i6.Manager { @override bool get isActiveWallet => (super.noSuchMethod( Invocation.getter(#isActiveWallet), @@ -97,10 +98,10 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: false, ) as bool); @override - _i6.Coin get coin => (super.noSuchMethod( + _i7.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i6.Coin.bitcoin, - ) as _i6.Coin); + returnValue: _i7.Coin.bitcoin, + ) as _i7.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -133,90 +134,42 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - _i7.Future<_i3.FeeObject> get fees => (super.noSuchMethod( + _i8.Future<_i3.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i7.Future<_i3.FeeObject>.value(_FakeFeeObject_1( + returnValue: _i8.Future<_i3.FeeObject>.value(_FakeFeeObject_1( this, Invocation.getter(#fees), )), - ) as _i7.Future<_i3.FeeObject>); + ) as _i8.Future<_i3.FeeObject>); @override - _i7.Future get maxFee => (super.noSuchMethod( + _i8.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future.value(0), + ) as _i8.Future); @override - _i7.Future get currentReceivingAddress => (super.noSuchMethod( + _i8.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future<_i4.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( + _i4.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_2( this, - Invocation.getter(#availableBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i4.Decimal); + ) as _i4.Balance); @override - _i7.Future<_i4.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i7.Future<_i4.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i7.Future<_i4.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#totalBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i4.Decimal); - @override - _i7.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); - @override - _i7.Future<_i3.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i8.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i7.Future<_i3.TransactionData>.value(_FakeTransactionData_3( - this, - Invocation.getter(#transactionData), - )), - ) as _i7.Future<_i3.TransactionData>); + _i8.Future>.value(<_i9.Transaction>[]), + ) as _i8.Future>); @override - _i7.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: _i7.Future>.value(<_i3.UtxoObject>[]), - ) as _i7.Future>); + _i8.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i8.Future>.value(<_i9.UTXO>[]), + ) as _i8.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -236,29 +189,74 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: '', ) as String); @override - _i7.Future> get mnemonic => (super.noSuchMethod( + _i8.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); + @override + _i8.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i8.Future.value(), + ) as _i8.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i8.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i8.Future.value(''), + ) as _i8.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i7.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i8.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -268,9 +266,9 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - _i7.Future> prepareSend({ + _i8.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i5.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -279,50 +277,32 @@ class MockManager extends _i1.Mock implements _i5.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i7.Future>.value({}), - ) as _i7.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future confirmSend({required Map? txData}) => + _i8.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); - @override - _i7.Future refresh() => (super.noSuchMethod( + _i8.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -332,34 +312,35 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: false, ) as bool); @override - _i7.Future testNetworkConnection() => (super.noSuchMethod( + _i8.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future initializeNew() => (super.noSuchMethod( + _i8.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future initializeExisting() => (super.noSuchMethod( + _i8.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future recoverFromMnemonic({ + _i8.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -370,25 +351,26 @@ class MockManager extends _i1.Mock implements _i5.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future exitCurrentWallet() => (super.noSuchMethod( + _i8.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future fullRescan( + _i8.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -400,42 +382,52 @@ class MockManager extends _i1.Mock implements _i5.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); - @override - _i7.Future estimateFeeFor( - int? satoshiAmount, + _i8.Future<_i5.Amount> estimateFeeFor( + _i5.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future<_i5.Amount>.value(_FakeAmount_3( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i8.Future<_i5.Amount>); @override - _i7.Future generateNewAddress() => (super.noSuchMethod( + _i8.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + _i8.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -443,7 +435,7 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/screen_tests/onboarding/backup_key_warning_view_screen_test.mocks.dart b/test/screen_tests/onboarding/backup_key_warning_view_screen_test.mocks.dart index 5bb48aadd..ff835bf64 100644 --- a/test/screen_tests/onboarding/backup_key_warning_view_screen_test.mocks.dart +++ b/test/screen_tests/onboarding/backup_key_warning_view_screen_test.mocks.dart @@ -3,16 +3,18 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i6; -import 'dart:ui' as _i8; +import 'dart:async' as _i7; +import 'dart:ui' as _i9; -import 'package:decimal/decimal.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/models/balance.dart' as _i4; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i11; import 'package:stackwallet/models/models.dart' as _i3; import 'package:stackwallet/services/coins/coin_service.dart' as _i2; -import 'package:stackwallet/services/coins/manager.dart' as _i9; -import 'package:stackwallet/services/wallets_service.dart' as _i5; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i7; +import 'package:stackwallet/services/coins/manager.dart' as _i10; +import 'package:stackwallet/services/wallets_service.dart' as _i6; +import 'package:stackwallet/utilities/amount/amount.dart' as _i5; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i8; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -46,8 +48,8 @@ class _FakeFeeObject_1 extends _i1.SmartFake implements _i3.FeeObject { ); } -class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { - _FakeDecimal_2( +class _FakeBalance_2 extends _i1.SmartFake implements _i4.Balance { + _FakeBalance_2( Object parent, Invocation parentInvocation, ) : super( @@ -56,9 +58,8 @@ class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { ); } -class _FakeTransactionData_3 extends _i1.SmartFake - implements _i3.TransactionData { - _FakeTransactionData_3( +class _FakeAmount_3 extends _i1.SmartFake implements _i5.Amount { + _FakeAmount_3( Object parent, Invocation parentInvocation, ) : super( @@ -70,21 +71,21 @@ class _FakeTransactionData_3 extends _i1.SmartFake /// A class which mocks [WalletsService]. /// /// See the documentation for Mockito's code generation for more information. -class MockWalletsService extends _i1.Mock implements _i5.WalletsService { +class MockWalletsService extends _i1.Mock implements _i6.WalletsService { @override - _i6.Future> get walletNames => + _i7.Future> get walletNames => (super.noSuchMethod( Invocation.getter(#walletNames), - returnValue: _i6.Future>.value( - {}), - ) as _i6.Future>); + returnValue: _i7.Future>.value( + {}), + ) as _i7.Future>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i6.Future renameWallet({ + _i7.Future renameWallet({ required String? from, required String? to, required bool? shouldNotifyListeners, @@ -99,13 +100,21 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - _i6.Future addExistingStackWallet({ + Map fetchWalletsData() => (super.noSuchMethod( + Invocation.method( + #fetchWalletsData, + [], + ), + returnValue: {}, + ) as Map); + @override + _i7.Future addExistingStackWallet({ required String? name, required String? walletId, - required _i7.Coin? coin, + required _i8.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -119,13 +128,13 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future addNewWallet({ + _i7.Future addNewWallet({ required String? name, - required _i7.Coin? coin, + required _i8.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -138,46 +147,46 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future> getFavoriteWalletIds() => (super.noSuchMethod( + _i7.Future> getFavoriteWalletIds() => (super.noSuchMethod( Invocation.method( #getFavoriteWalletIds, [], ), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i7.Future>.value([]), + ) as _i7.Future>); @override - _i6.Future saveFavoriteWalletIds(List? walletIds) => + _i7.Future saveFavoriteWalletIds(List? walletIds) => (super.noSuchMethod( Invocation.method( #saveFavoriteWalletIds, [walletIds], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future addFavorite(String? walletId) => (super.noSuchMethod( + _i7.Future addFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #addFavorite, [walletId], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future removeFavorite(String? walletId) => (super.noSuchMethod( + _i7.Future removeFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #removeFavorite, [walletId], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future moveFavorite({ + _i7.Future moveFavorite({ required int? fromIndex, required int? toIndex, }) => @@ -190,48 +199,48 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { #toIndex: toIndex, }, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future checkForDuplicate(String? name) => (super.noSuchMethod( + _i7.Future checkForDuplicate(String? name) => (super.noSuchMethod( Invocation.method( #checkForDuplicate, [name], ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - _i6.Future getWalletId(String? walletName) => (super.noSuchMethod( + _i7.Future getWalletId(String? walletName) => (super.noSuchMethod( Invocation.method( #getWalletId, [walletName], ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future isMnemonicVerified({required String? walletId}) => + _i7.Future isMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #isMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - _i6.Future setMnemonicVerified({required String? walletId}) => + _i7.Future setMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #setMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future deleteWallet( + _i7.Future deleteWallet( String? name, bool? shouldNotifyListeners, ) => @@ -243,20 +252,20 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { shouldNotifyListeners, ], ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i7.Future.value(0), + ) as _i7.Future); @override - _i6.Future refreshWallets(bool? shouldNotifyListeners) => + _i7.Future refreshWallets(bool? shouldNotifyListeners) => (super.noSuchMethod( Invocation.method( #refreshWallets, [shouldNotifyListeners], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -264,7 +273,7 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -292,7 +301,7 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { /// A class which mocks [Manager]. /// /// See the documentation for Mockito's code generation for more information. -class MockManager extends _i1.Mock implements _i9.Manager { +class MockManager extends _i1.Mock implements _i10.Manager { @override bool get isActiveWallet => (super.noSuchMethod( Invocation.getter(#isActiveWallet), @@ -320,10 +329,10 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValue: false, ) as bool); @override - _i7.Coin get coin => (super.noSuchMethod( + _i8.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i7.Coin.bitcoin, - ) as _i7.Coin); + returnValue: _i8.Coin.bitcoin, + ) as _i8.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -356,90 +365,42 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValueForMissingStub: null, ); @override - _i6.Future<_i3.FeeObject> get fees => (super.noSuchMethod( + _i7.Future<_i3.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i6.Future<_i3.FeeObject>.value(_FakeFeeObject_1( + returnValue: _i7.Future<_i3.FeeObject>.value(_FakeFeeObject_1( this, Invocation.getter(#fees), )), - ) as _i6.Future<_i3.FeeObject>); + ) as _i7.Future<_i3.FeeObject>); @override - _i6.Future get maxFee => (super.noSuchMethod( + _i7.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i7.Future.value(0), + ) as _i7.Future); @override - _i6.Future get currentReceivingAddress => (super.noSuchMethod( + _i7.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i6.Future.value(''), - ) as _i6.Future); + returnValue: _i7.Future.value(''), + ) as _i7.Future); @override - _i6.Future<_i4.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i6.Future<_i4.Decimal>.value(_FakeDecimal_2( + _i4.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_2( this, - Invocation.getter(#availableBalance), - )), - ) as _i6.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i4.Decimal); + ) as _i4.Balance); @override - _i6.Future<_i4.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i6.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i6.Future<_i4.Decimal>); - @override - _i6.Future<_i4.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i6.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i6.Future<_i4.Decimal>); - @override - _i6.Future<_i4.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i6.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#totalBalance), - )), - ) as _i6.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i4.Decimal); - @override - _i6.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); - @override - _i6.Future<_i3.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i7.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i6.Future<_i3.TransactionData>.value(_FakeTransactionData_3( - this, - Invocation.getter(#transactionData), - )), - ) as _i6.Future<_i3.TransactionData>); + _i7.Future>.value(<_i11.Transaction>[]), + ) as _i7.Future>); @override - _i6.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: _i6.Future>.value(<_i3.UtxoObject>[]), - ) as _i6.Future>); + _i7.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i7.Future>.value(<_i11.UTXO>[]), + ) as _i7.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -459,29 +420,74 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValue: '', ) as String); @override - _i6.Future> get mnemonic => (super.noSuchMethod( + _i7.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i7.Future>.value([]), + ) as _i7.Future>); + @override + _i7.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i7.Future.value(), + ) as _i7.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i7.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i7.Future.value(''), + ) as _i7.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i6.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i7.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -491,9 +497,9 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValueForMissingStub: null, ); @override - _i6.Future> prepareSend({ + _i7.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i5.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -502,50 +508,32 @@ class MockManager extends _i1.Mock implements _i9.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i7.Future>.value({}), + ) as _i7.Future>); @override - _i6.Future confirmSend({required Map? txData}) => + _i7.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i6.Future.value(''), - ) as _i6.Future); + returnValue: _i7.Future.value(''), + ) as _i7.Future); @override - _i6.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i6.Future.value(''), - ) as _i6.Future); - @override - _i6.Future refresh() => (super.noSuchMethod( + _i7.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -555,34 +543,35 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValue: false, ) as bool); @override - _i6.Future testNetworkConnection() => (super.noSuchMethod( + _i7.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - _i6.Future initializeNew() => (super.noSuchMethod( + _i7.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future initializeExisting() => (super.noSuchMethod( + _i7.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future recoverFromMnemonic({ + _i7.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -593,25 +582,26 @@ class MockManager extends _i1.Mock implements _i9.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future exitCurrentWallet() => (super.noSuchMethod( + _i7.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future fullRescan( + _i7.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -623,42 +613,52 @@ class MockManager extends _i1.Mock implements _i9.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); - @override - _i6.Future estimateFeeFor( - int? satoshiAmount, + _i7.Future<_i5.Amount> estimateFeeFor( + _i5.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i7.Future<_i5.Amount>.value(_FakeAmount_3( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i7.Future<_i5.Amount>); @override - _i6.Future generateNewAddress() => (super.noSuchMethod( + _i7.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + _i7.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -666,7 +666,7 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/screen_tests/onboarding/create_pin_view_screen_test.mocks.dart b/test/screen_tests/onboarding/create_pin_view_screen_test.mocks.dart index c891911ae..796f4fd13 100644 --- a/test/screen_tests/onboarding/create_pin_view_screen_test.mocks.dart +++ b/test/screen_tests/onboarding/create_pin_view_screen_test.mocks.dart @@ -3,18 +3,20 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i7; -import 'dart:ui' as _i9; +import 'dart:async' as _i8; +import 'dart:ui' as _i10; -import 'package:decimal/decimal.dart' as _i5; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/models/balance.dart' as _i5; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i14; import 'package:stackwallet/models/models.dart' as _i4; -import 'package:stackwallet/models/node_model.dart' as _i11; +import 'package:stackwallet/models/node_model.dart' as _i12; import 'package:stackwallet/services/coins/coin_service.dart' as _i3; -import 'package:stackwallet/services/coins/manager.dart' as _i12; -import 'package:stackwallet/services/node_service.dart' as _i10; -import 'package:stackwallet/services/wallets_service.dart' as _i6; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i8; +import 'package:stackwallet/services/coins/manager.dart' as _i13; +import 'package:stackwallet/services/node_service.dart' as _i11; +import 'package:stackwallet/services/wallets_service.dart' as _i7; +import 'package:stackwallet/utilities/amount/amount.dart' as _i6; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i9; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' as _i2; @@ -61,8 +63,8 @@ class _FakeFeeObject_2 extends _i1.SmartFake implements _i4.FeeObject { ); } -class _FakeDecimal_3 extends _i1.SmartFake implements _i5.Decimal { - _FakeDecimal_3( +class _FakeBalance_3 extends _i1.SmartFake implements _i5.Balance { + _FakeBalance_3( Object parent, Invocation parentInvocation, ) : super( @@ -71,9 +73,8 @@ class _FakeDecimal_3 extends _i1.SmartFake implements _i5.Decimal { ); } -class _FakeTransactionData_4 extends _i1.SmartFake - implements _i4.TransactionData { - _FakeTransactionData_4( +class _FakeAmount_4 extends _i1.SmartFake implements _i6.Amount { + _FakeAmount_4( Object parent, Invocation parentInvocation, ) : super( @@ -85,21 +86,21 @@ class _FakeTransactionData_4 extends _i1.SmartFake /// A class which mocks [WalletsService]. /// /// See the documentation for Mockito's code generation for more information. -class MockWalletsService extends _i1.Mock implements _i6.WalletsService { +class MockWalletsService extends _i1.Mock implements _i7.WalletsService { @override - _i7.Future> get walletNames => + _i8.Future> get walletNames => (super.noSuchMethod( Invocation.getter(#walletNames), - returnValue: _i7.Future>.value( - {}), - ) as _i7.Future>); + returnValue: _i8.Future>.value( + {}), + ) as _i8.Future>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i7.Future renameWallet({ + _i8.Future renameWallet({ required String? from, required String? to, required bool? shouldNotifyListeners, @@ -114,13 +115,21 @@ class MockWalletsService extends _i1.Mock implements _i6.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future addExistingStackWallet({ + Map fetchWalletsData() => (super.noSuchMethod( + Invocation.method( + #fetchWalletsData, + [], + ), + returnValue: {}, + ) as Map); + @override + _i8.Future addExistingStackWallet({ required String? name, required String? walletId, - required _i8.Coin? coin, + required _i9.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -134,13 +143,13 @@ class MockWalletsService extends _i1.Mock implements _i6.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future addNewWallet({ + _i8.Future addNewWallet({ required String? name, - required _i8.Coin? coin, + required _i9.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -153,46 +162,46 @@ class MockWalletsService extends _i1.Mock implements _i6.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future> getFavoriteWalletIds() => (super.noSuchMethod( + _i8.Future> getFavoriteWalletIds() => (super.noSuchMethod( Invocation.method( #getFavoriteWalletIds, [], ), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); @override - _i7.Future saveFavoriteWalletIds(List? walletIds) => + _i8.Future saveFavoriteWalletIds(List? walletIds) => (super.noSuchMethod( Invocation.method( #saveFavoriteWalletIds, [walletIds], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future addFavorite(String? walletId) => (super.noSuchMethod( + _i8.Future addFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #addFavorite, [walletId], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future removeFavorite(String? walletId) => (super.noSuchMethod( + _i8.Future removeFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #removeFavorite, [walletId], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future moveFavorite({ + _i8.Future moveFavorite({ required int? fromIndex, required int? toIndex, }) => @@ -205,48 +214,48 @@ class MockWalletsService extends _i1.Mock implements _i6.WalletsService { #toIndex: toIndex, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future checkForDuplicate(String? name) => (super.noSuchMethod( + _i8.Future checkForDuplicate(String? name) => (super.noSuchMethod( Invocation.method( #checkForDuplicate, [name], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future getWalletId(String? walletName) => (super.noSuchMethod( + _i8.Future getWalletId(String? walletName) => (super.noSuchMethod( Invocation.method( #getWalletId, [walletName], ), - returnValue: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future isMnemonicVerified({required String? walletId}) => + _i8.Future isMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #isMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future setMnemonicVerified({required String? walletId}) => + _i8.Future setMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #setMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future deleteWallet( + _i8.Future deleteWallet( String? name, bool? shouldNotifyListeners, ) => @@ -258,20 +267,20 @@ class MockWalletsService extends _i1.Mock implements _i6.WalletsService { shouldNotifyListeners, ], ), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future.value(0), + ) as _i8.Future); @override - _i7.Future refreshWallets(bool? shouldNotifyListeners) => + _i8.Future refreshWallets(bool? shouldNotifyListeners) => (super.noSuchMethod( Invocation.method( #refreshWallets, [shouldNotifyListeners], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -279,7 +288,7 @@ class MockWalletsService extends _i1.Mock implements _i6.WalletsService { returnValueForMissingStub: null, ); @override - void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -307,7 +316,7 @@ class MockWalletsService extends _i1.Mock implements _i6.WalletsService { /// A class which mocks [NodeService]. /// /// See the documentation for Mockito's code generation for more information. -class MockNodeService extends _i1.Mock implements _i10.NodeService { +class MockNodeService extends _i1.Mock implements _i11.NodeService { @override _i2.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod( Invocation.getter(#secureStorageInterface), @@ -317,33 +326,33 @@ class MockNodeService extends _i1.Mock implements _i10.NodeService { ), ) as _i2.SecureStorageInterface); @override - List<_i11.NodeModel> get primaryNodes => (super.noSuchMethod( + List<_i12.NodeModel> get primaryNodes => (super.noSuchMethod( Invocation.getter(#primaryNodes), - returnValue: <_i11.NodeModel>[], - ) as List<_i11.NodeModel>); + returnValue: <_i12.NodeModel>[], + ) as List<_i12.NodeModel>); @override - List<_i11.NodeModel> get nodes => (super.noSuchMethod( + List<_i12.NodeModel> get nodes => (super.noSuchMethod( Invocation.getter(#nodes), - returnValue: <_i11.NodeModel>[], - ) as List<_i11.NodeModel>); + returnValue: <_i12.NodeModel>[], + ) as List<_i12.NodeModel>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i7.Future updateDefaults() => (super.noSuchMethod( + _i8.Future updateDefaults() => (super.noSuchMethod( Invocation.method( #updateDefaults, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future setPrimaryNodeFor({ - required _i8.Coin? coin, - required _i11.NodeModel? node, + _i8.Future setPrimaryNodeFor({ + required _i9.Coin? coin, + required _i12.NodeModel? node, bool? shouldNotifyListeners = false, }) => (super.noSuchMethod( @@ -356,44 +365,44 @@ class MockNodeService extends _i1.Mock implements _i10.NodeService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i11.NodeModel? getPrimaryNodeFor({required _i8.Coin? coin}) => + _i12.NodeModel? getPrimaryNodeFor({required _i9.Coin? coin}) => (super.noSuchMethod(Invocation.method( #getPrimaryNodeFor, [], {#coin: coin}, - )) as _i11.NodeModel?); + )) as _i12.NodeModel?); @override - List<_i11.NodeModel> getNodesFor(_i8.Coin? coin) => (super.noSuchMethod( + List<_i12.NodeModel> getNodesFor(_i9.Coin? coin) => (super.noSuchMethod( Invocation.method( #getNodesFor, [coin], ), - returnValue: <_i11.NodeModel>[], - ) as List<_i11.NodeModel>); + returnValue: <_i12.NodeModel>[], + ) as List<_i12.NodeModel>); @override - _i11.NodeModel? getNodeById({required String? id}) => + _i12.NodeModel? getNodeById({required String? id}) => (super.noSuchMethod(Invocation.method( #getNodeById, [], {#id: id}, - )) as _i11.NodeModel?); + )) as _i12.NodeModel?); @override - List<_i11.NodeModel> failoverNodesFor({required _i8.Coin? coin}) => + List<_i12.NodeModel> failoverNodesFor({required _i9.Coin? coin}) => (super.noSuchMethod( Invocation.method( #failoverNodesFor, [], {#coin: coin}, ), - returnValue: <_i11.NodeModel>[], - ) as List<_i11.NodeModel>); + returnValue: <_i12.NodeModel>[], + ) as List<_i12.NodeModel>); @override - _i7.Future add( - _i11.NodeModel? node, + _i8.Future add( + _i12.NodeModel? node, String? password, bool? shouldNotifyListeners, ) => @@ -406,11 +415,11 @@ class MockNodeService extends _i1.Mock implements _i10.NodeService { shouldNotifyListeners, ], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future delete( + _i8.Future delete( String? id, bool? shouldNotifyListeners, ) => @@ -422,11 +431,11 @@ class MockNodeService extends _i1.Mock implements _i10.NodeService { shouldNotifyListeners, ], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future setEnabledState( + _i8.Future setEnabledState( String? id, bool? enabled, bool? shouldNotifyListeners, @@ -440,12 +449,12 @@ class MockNodeService extends _i1.Mock implements _i10.NodeService { shouldNotifyListeners, ], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future edit( - _i11.NodeModel? editedNode, + _i8.Future edit( + _i12.NodeModel? editedNode, String? password, bool? shouldNotifyListeners, ) => @@ -458,20 +467,20 @@ class MockNodeService extends _i1.Mock implements _i10.NodeService { shouldNotifyListeners, ], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future updateCommunityNodes() => (super.noSuchMethod( + _i8.Future updateCommunityNodes() => (super.noSuchMethod( Invocation.method( #updateCommunityNodes, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -479,7 +488,7 @@ class MockNodeService extends _i1.Mock implements _i10.NodeService { returnValueForMissingStub: null, ); @override - void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -507,7 +516,7 @@ class MockNodeService extends _i1.Mock implements _i10.NodeService { /// A class which mocks [Manager]. /// /// See the documentation for Mockito's code generation for more information. -class MockManager extends _i1.Mock implements _i12.Manager { +class MockManager extends _i1.Mock implements _i13.Manager { @override bool get isActiveWallet => (super.noSuchMethod( Invocation.getter(#isActiveWallet), @@ -535,10 +544,10 @@ class MockManager extends _i1.Mock implements _i12.Manager { returnValue: false, ) as bool); @override - _i8.Coin get coin => (super.noSuchMethod( + _i9.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i8.Coin.bitcoin, - ) as _i8.Coin); + returnValue: _i9.Coin.bitcoin, + ) as _i9.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -571,90 +580,42 @@ class MockManager extends _i1.Mock implements _i12.Manager { returnValueForMissingStub: null, ); @override - _i7.Future<_i4.FeeObject> get fees => (super.noSuchMethod( + _i8.Future<_i4.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i7.Future<_i4.FeeObject>.value(_FakeFeeObject_2( + returnValue: _i8.Future<_i4.FeeObject>.value(_FakeFeeObject_2( this, Invocation.getter(#fees), )), - ) as _i7.Future<_i4.FeeObject>); + ) as _i8.Future<_i4.FeeObject>); @override - _i7.Future get maxFee => (super.noSuchMethod( + _i8.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future.value(0), + ) as _i8.Future); @override - _i7.Future get currentReceivingAddress => (super.noSuchMethod( + _i8.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future<_i5.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i7.Future<_i5.Decimal>.value(_FakeDecimal_3( + _i5.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_3( this, - Invocation.getter(#availableBalance), - )), - ) as _i7.Future<_i5.Decimal>); - @override - _i5.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_3( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i5.Decimal); + ) as _i5.Balance); @override - _i7.Future<_i5.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i7.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i7.Future<_i5.Decimal>); - @override - _i7.Future<_i5.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i7.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i7.Future<_i5.Decimal>); - @override - _i7.Future<_i5.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i7.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#totalBalance), - )), - ) as _i7.Future<_i5.Decimal>); - @override - _i5.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_3( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i5.Decimal); - @override - _i7.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); - @override - _i7.Future<_i4.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i8.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i7.Future<_i4.TransactionData>.value(_FakeTransactionData_4( - this, - Invocation.getter(#transactionData), - )), - ) as _i7.Future<_i4.TransactionData>); + _i8.Future>.value(<_i14.Transaction>[]), + ) as _i8.Future>); @override - _i7.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: _i7.Future>.value(<_i4.UtxoObject>[]), - ) as _i7.Future>); + _i8.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i8.Future>.value(<_i14.UTXO>[]), + ) as _i8.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -674,29 +635,74 @@ class MockManager extends _i1.Mock implements _i12.Manager { returnValue: '', ) as String); @override - _i7.Future> get mnemonic => (super.noSuchMethod( + _i8.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); + @override + _i8.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i8.Future.value(), + ) as _i8.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i8.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i8.Future.value(''), + ) as _i8.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i7.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i8.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -706,9 +712,9 @@ class MockManager extends _i1.Mock implements _i12.Manager { returnValueForMissingStub: null, ); @override - _i7.Future> prepareSend({ + _i8.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i6.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -717,50 +723,32 @@ class MockManager extends _i1.Mock implements _i12.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i7.Future>.value({}), - ) as _i7.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future confirmSend({required Map? txData}) => + _i8.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); - @override - _i7.Future refresh() => (super.noSuchMethod( + _i8.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -770,34 +758,35 @@ class MockManager extends _i1.Mock implements _i12.Manager { returnValue: false, ) as bool); @override - _i7.Future testNetworkConnection() => (super.noSuchMethod( + _i8.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future initializeNew() => (super.noSuchMethod( + _i8.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future initializeExisting() => (super.noSuchMethod( + _i8.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future recoverFromMnemonic({ + _i8.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -808,25 +797,26 @@ class MockManager extends _i1.Mock implements _i12.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future exitCurrentWallet() => (super.noSuchMethod( + _i8.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future fullRescan( + _i8.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -838,42 +828,52 @@ class MockManager extends _i1.Mock implements _i12.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); - @override - _i7.Future estimateFeeFor( - int? satoshiAmount, + _i8.Future<_i6.Amount> estimateFeeFor( + _i6.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future<_i6.Amount>.value(_FakeAmount_4( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i8.Future<_i6.Amount>); @override - _i7.Future generateNewAddress() => (super.noSuchMethod( + _i8.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( + _i8.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -881,7 +881,7 @@ class MockManager extends _i1.Mock implements _i12.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/screen_tests/onboarding/name_your_wallet_view_screen_test.mocks.dart b/test/screen_tests/onboarding/name_your_wallet_view_screen_test.mocks.dart index 29cdf0642..65cd8fea1 100644 --- a/test/screen_tests/onboarding/name_your_wallet_view_screen_test.mocks.dart +++ b/test/screen_tests/onboarding/name_your_wallet_view_screen_test.mocks.dart @@ -56,6 +56,14 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { returnValue: _i3.Future.value(false), ) as _i3.Future); @override + Map fetchWalletsData() => (super.noSuchMethod( + Invocation.method( + #fetchWalletsData, + [], + ), + returnValue: {}, + ) as Map); + @override _i3.Future addExistingStackWallet({ required String? name, required String? walletId, diff --git a/test/screen_tests/onboarding/restore_wallet_view_screen_test.mocks.dart b/test/screen_tests/onboarding/restore_wallet_view_screen_test.mocks.dart index ee326b1d8..6ea1fd2c5 100644 --- a/test/screen_tests/onboarding/restore_wallet_view_screen_test.mocks.dart +++ b/test/screen_tests/onboarding/restore_wallet_view_screen_test.mocks.dart @@ -3,22 +3,24 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i8; -import 'dart:ui' as _i11; +import 'dart:async' as _i9; +import 'dart:ui' as _i12; import 'package:barcode_scan2/barcode_scan2.dart' as _i2; -import 'package:decimal/decimal.dart' as _i5; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/models/balance.dart' as _i5; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i14; import 'package:stackwallet/models/models.dart' as _i4; -import 'package:stackwallet/models/node_model.dart' as _i14; +import 'package:stackwallet/models/node_model.dart' as _i16; import 'package:stackwallet/services/coins/coin_service.dart' as _i3; -import 'package:stackwallet/services/coins/manager.dart' as _i12; -import 'package:stackwallet/services/node_service.dart' as _i13; -import 'package:stackwallet/services/wallets_service.dart' as _i9; -import 'package:stackwallet/utilities/barcode_scanner_interface.dart' as _i7; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i10; +import 'package:stackwallet/services/coins/manager.dart' as _i13; +import 'package:stackwallet/services/node_service.dart' as _i15; +import 'package:stackwallet/services/wallets_service.dart' as _i10; +import 'package:stackwallet/utilities/amount/amount.dart' as _i6; +import 'package:stackwallet/utilities/barcode_scanner_interface.dart' as _i8; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i11; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' - as _i6; + as _i7; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -62,8 +64,8 @@ class _FakeFeeObject_2 extends _i1.SmartFake implements _i4.FeeObject { ); } -class _FakeDecimal_3 extends _i1.SmartFake implements _i5.Decimal { - _FakeDecimal_3( +class _FakeBalance_3 extends _i1.SmartFake implements _i5.Balance { + _FakeBalance_3( Object parent, Invocation parentInvocation, ) : super( @@ -72,9 +74,8 @@ class _FakeDecimal_3 extends _i1.SmartFake implements _i5.Decimal { ); } -class _FakeTransactionData_4 extends _i1.SmartFake - implements _i4.TransactionData { - _FakeTransactionData_4( +class _FakeAmount_4 extends _i1.SmartFake implements _i6.Amount { + _FakeAmount_4( Object parent, Invocation parentInvocation, ) : super( @@ -84,7 +85,7 @@ class _FakeTransactionData_4 extends _i1.SmartFake } class _FakeSecureStorageInterface_5 extends _i1.SmartFake - implements _i6.SecureStorageInterface { + implements _i7.SecureStorageInterface { _FakeSecureStorageInterface_5( Object parent, Invocation parentInvocation, @@ -98,13 +99,13 @@ class _FakeSecureStorageInterface_5 extends _i1.SmartFake /// /// See the documentation for Mockito's code generation for more information. class MockBarcodeScannerWrapper extends _i1.Mock - implements _i7.BarcodeScannerWrapper { + implements _i8.BarcodeScannerWrapper { MockBarcodeScannerWrapper() { _i1.throwOnMissingStub(this); } @override - _i8.Future<_i2.ScanResult> scan( + _i9.Future<_i2.ScanResult> scan( {_i2.ScanOptions? options = const _i2.ScanOptions()}) => (super.noSuchMethod( Invocation.method( @@ -112,7 +113,7 @@ class MockBarcodeScannerWrapper extends _i1.Mock [], {#options: options}, ), - returnValue: _i8.Future<_i2.ScanResult>.value(_FakeScanResult_0( + returnValue: _i9.Future<_i2.ScanResult>.value(_FakeScanResult_0( this, Invocation.method( #scan, @@ -120,27 +121,27 @@ class MockBarcodeScannerWrapper extends _i1.Mock {#options: options}, ), )), - ) as _i8.Future<_i2.ScanResult>); + ) as _i9.Future<_i2.ScanResult>); } /// A class which mocks [WalletsService]. /// /// See the documentation for Mockito's code generation for more information. -class MockWalletsService extends _i1.Mock implements _i9.WalletsService { +class MockWalletsService extends _i1.Mock implements _i10.WalletsService { @override - _i8.Future> get walletNames => + _i9.Future> get walletNames => (super.noSuchMethod( Invocation.getter(#walletNames), - returnValue: _i8.Future>.value( - {}), - ) as _i8.Future>); + returnValue: _i9.Future>.value( + {}), + ) as _i9.Future>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i8.Future renameWallet({ + _i9.Future renameWallet({ required String? from, required String? to, required bool? shouldNotifyListeners, @@ -155,13 +156,21 @@ class MockWalletsService extends _i1.Mock implements _i9.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); @override - _i8.Future addExistingStackWallet({ + Map fetchWalletsData() => (super.noSuchMethod( + Invocation.method( + #fetchWalletsData, + [], + ), + returnValue: {}, + ) as Map); + @override + _i9.Future addExistingStackWallet({ required String? name, required String? walletId, - required _i10.Coin? coin, + required _i11.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -175,13 +184,13 @@ class MockWalletsService extends _i1.Mock implements _i9.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future addNewWallet({ + _i9.Future addNewWallet({ required String? name, - required _i10.Coin? coin, + required _i11.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -194,46 +203,46 @@ class MockWalletsService extends _i1.Mock implements _i9.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future> getFavoriteWalletIds() => (super.noSuchMethod( + _i9.Future> getFavoriteWalletIds() => (super.noSuchMethod( Invocation.method( #getFavoriteWalletIds, [], ), - returnValue: _i8.Future>.value([]), - ) as _i8.Future>); + returnValue: _i9.Future>.value([]), + ) as _i9.Future>); @override - _i8.Future saveFavoriteWalletIds(List? walletIds) => + _i9.Future saveFavoriteWalletIds(List? walletIds) => (super.noSuchMethod( Invocation.method( #saveFavoriteWalletIds, [walletIds], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future addFavorite(String? walletId) => (super.noSuchMethod( + _i9.Future addFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #addFavorite, [walletId], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future removeFavorite(String? walletId) => (super.noSuchMethod( + _i9.Future removeFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #removeFavorite, [walletId], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future moveFavorite({ + _i9.Future moveFavorite({ required int? fromIndex, required int? toIndex, }) => @@ -246,48 +255,48 @@ class MockWalletsService extends _i1.Mock implements _i9.WalletsService { #toIndex: toIndex, }, ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future checkForDuplicate(String? name) => (super.noSuchMethod( + _i9.Future checkForDuplicate(String? name) => (super.noSuchMethod( Invocation.method( #checkForDuplicate, [name], ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); @override - _i8.Future getWalletId(String? walletName) => (super.noSuchMethod( + _i9.Future getWalletId(String? walletName) => (super.noSuchMethod( Invocation.method( #getWalletId, [walletName], ), - returnValue: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future isMnemonicVerified({required String? walletId}) => + _i9.Future isMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #isMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); @override - _i8.Future setMnemonicVerified({required String? walletId}) => + _i9.Future setMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #setMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future deleteWallet( + _i9.Future deleteWallet( String? name, bool? shouldNotifyListeners, ) => @@ -299,20 +308,20 @@ class MockWalletsService extends _i1.Mock implements _i9.WalletsService { shouldNotifyListeners, ], ), - returnValue: _i8.Future.value(0), - ) as _i8.Future); + returnValue: _i9.Future.value(0), + ) as _i9.Future); @override - _i8.Future refreshWallets(bool? shouldNotifyListeners) => + _i9.Future refreshWallets(bool? shouldNotifyListeners) => (super.noSuchMethod( Invocation.method( #refreshWallets, [shouldNotifyListeners], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - void addListener(_i11.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i12.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -320,7 +329,7 @@ class MockWalletsService extends _i1.Mock implements _i9.WalletsService { returnValueForMissingStub: null, ); @override - void removeListener(_i11.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i12.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -348,7 +357,7 @@ class MockWalletsService extends _i1.Mock implements _i9.WalletsService { /// A class which mocks [Manager]. /// /// See the documentation for Mockito's code generation for more information. -class MockManager extends _i1.Mock implements _i12.Manager { +class MockManager extends _i1.Mock implements _i13.Manager { @override bool get isActiveWallet => (super.noSuchMethod( Invocation.getter(#isActiveWallet), @@ -376,10 +385,10 @@ class MockManager extends _i1.Mock implements _i12.Manager { returnValue: false, ) as bool); @override - _i10.Coin get coin => (super.noSuchMethod( + _i11.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i10.Coin.bitcoin, - ) as _i10.Coin); + returnValue: _i11.Coin.bitcoin, + ) as _i11.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -412,90 +421,42 @@ class MockManager extends _i1.Mock implements _i12.Manager { returnValueForMissingStub: null, ); @override - _i8.Future<_i4.FeeObject> get fees => (super.noSuchMethod( + _i9.Future<_i4.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i8.Future<_i4.FeeObject>.value(_FakeFeeObject_2( + returnValue: _i9.Future<_i4.FeeObject>.value(_FakeFeeObject_2( this, Invocation.getter(#fees), )), - ) as _i8.Future<_i4.FeeObject>); + ) as _i9.Future<_i4.FeeObject>); @override - _i8.Future get maxFee => (super.noSuchMethod( + _i9.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i8.Future.value(0), - ) as _i8.Future); + returnValue: _i9.Future.value(0), + ) as _i9.Future); @override - _i8.Future get currentReceivingAddress => (super.noSuchMethod( + _i9.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i8.Future.value(''), - ) as _i8.Future); + returnValue: _i9.Future.value(''), + ) as _i9.Future); @override - _i8.Future<_i5.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i8.Future<_i5.Decimal>.value(_FakeDecimal_3( + _i5.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_3( this, - Invocation.getter(#availableBalance), - )), - ) as _i8.Future<_i5.Decimal>); - @override - _i5.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_3( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i5.Decimal); + ) as _i5.Balance); @override - _i8.Future<_i5.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i8.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i8.Future<_i5.Decimal>); - @override - _i8.Future<_i5.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i8.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i8.Future<_i5.Decimal>); - @override - _i8.Future<_i5.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i8.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#totalBalance), - )), - ) as _i8.Future<_i5.Decimal>); - @override - _i5.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_3( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i5.Decimal); - @override - _i8.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i8.Future>.value([]), - ) as _i8.Future>); - @override - _i8.Future<_i4.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i9.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i8.Future<_i4.TransactionData>.value(_FakeTransactionData_4( - this, - Invocation.getter(#transactionData), - )), - ) as _i8.Future<_i4.TransactionData>); + _i9.Future>.value(<_i14.Transaction>[]), + ) as _i9.Future>); @override - _i8.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: _i8.Future>.value(<_i4.UtxoObject>[]), - ) as _i8.Future>); + _i9.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i9.Future>.value(<_i14.UTXO>[]), + ) as _i9.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -515,29 +476,74 @@ class MockManager extends _i1.Mock implements _i12.Manager { returnValue: '', ) as String); @override - _i8.Future> get mnemonic => (super.noSuchMethod( + _i9.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i8.Future>.value([]), - ) as _i8.Future>); + returnValue: _i9.Future>.value([]), + ) as _i9.Future>); + @override + _i9.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i9.Future.value(), + ) as _i9.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i9.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i9.Future.value(''), + ) as _i9.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i8.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i9.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -547,9 +553,9 @@ class MockManager extends _i1.Mock implements _i12.Manager { returnValueForMissingStub: null, ); @override - _i8.Future> prepareSend({ + _i9.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i6.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -558,50 +564,32 @@ class MockManager extends _i1.Mock implements _i12.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i8.Future>.value({}), - ) as _i8.Future>); + _i9.Future>.value({}), + ) as _i9.Future>); @override - _i8.Future confirmSend({required Map? txData}) => + _i9.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i8.Future.value(''), - ) as _i8.Future); + returnValue: _i9.Future.value(''), + ) as _i9.Future); @override - _i8.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i8.Future.value(''), - ) as _i8.Future); - @override - _i8.Future refresh() => (super.noSuchMethod( + _i9.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -611,34 +599,35 @@ class MockManager extends _i1.Mock implements _i12.Manager { returnValue: false, ) as bool); @override - _i8.Future testNetworkConnection() => (super.noSuchMethod( + _i9.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); @override - _i8.Future initializeNew() => (super.noSuchMethod( + _i9.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future initializeExisting() => (super.noSuchMethod( + _i9.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future recoverFromMnemonic({ + _i9.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -649,25 +638,26 @@ class MockManager extends _i1.Mock implements _i12.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future exitCurrentWallet() => (super.noSuchMethod( + _i9.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future fullRescan( + _i9.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -679,42 +669,52 @@ class MockManager extends _i1.Mock implements _i12.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); - @override - _i8.Future estimateFeeFor( - int? satoshiAmount, + _i9.Future<_i6.Amount> estimateFeeFor( + _i6.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i8.Future.value(0), - ) as _i8.Future); + returnValue: _i9.Future<_i6.Amount>.value(_FakeAmount_4( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i9.Future<_i6.Amount>); @override - _i8.Future generateNewAddress() => (super.noSuchMethod( + _i9.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); @override - void addListener(_i11.VoidCallback? listener) => super.noSuchMethod( + _i9.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override + void addListener(_i12.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -722,7 +722,7 @@ class MockManager extends _i1.Mock implements _i12.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i11.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i12.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -742,43 +742,43 @@ class MockManager extends _i1.Mock implements _i12.Manager { /// A class which mocks [NodeService]. /// /// See the documentation for Mockito's code generation for more information. -class MockNodeService extends _i1.Mock implements _i13.NodeService { +class MockNodeService extends _i1.Mock implements _i15.NodeService { @override - _i6.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod( + _i7.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod( Invocation.getter(#secureStorageInterface), returnValue: _FakeSecureStorageInterface_5( this, Invocation.getter(#secureStorageInterface), ), - ) as _i6.SecureStorageInterface); + ) as _i7.SecureStorageInterface); @override - List<_i14.NodeModel> get primaryNodes => (super.noSuchMethod( + List<_i16.NodeModel> get primaryNodes => (super.noSuchMethod( Invocation.getter(#primaryNodes), - returnValue: <_i14.NodeModel>[], - ) as List<_i14.NodeModel>); + returnValue: <_i16.NodeModel>[], + ) as List<_i16.NodeModel>); @override - List<_i14.NodeModel> get nodes => (super.noSuchMethod( + List<_i16.NodeModel> get nodes => (super.noSuchMethod( Invocation.getter(#nodes), - returnValue: <_i14.NodeModel>[], - ) as List<_i14.NodeModel>); + returnValue: <_i16.NodeModel>[], + ) as List<_i16.NodeModel>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i8.Future updateDefaults() => (super.noSuchMethod( + _i9.Future updateDefaults() => (super.noSuchMethod( Invocation.method( #updateDefaults, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future setPrimaryNodeFor({ - required _i10.Coin? coin, - required _i14.NodeModel? node, + _i9.Future setPrimaryNodeFor({ + required _i11.Coin? coin, + required _i16.NodeModel? node, bool? shouldNotifyListeners = false, }) => (super.noSuchMethod( @@ -791,44 +791,44 @@ class MockNodeService extends _i1.Mock implements _i13.NodeService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i14.NodeModel? getPrimaryNodeFor({required _i10.Coin? coin}) => + _i16.NodeModel? getPrimaryNodeFor({required _i11.Coin? coin}) => (super.noSuchMethod(Invocation.method( #getPrimaryNodeFor, [], {#coin: coin}, - )) as _i14.NodeModel?); + )) as _i16.NodeModel?); @override - List<_i14.NodeModel> getNodesFor(_i10.Coin? coin) => (super.noSuchMethod( + List<_i16.NodeModel> getNodesFor(_i11.Coin? coin) => (super.noSuchMethod( Invocation.method( #getNodesFor, [coin], ), - returnValue: <_i14.NodeModel>[], - ) as List<_i14.NodeModel>); + returnValue: <_i16.NodeModel>[], + ) as List<_i16.NodeModel>); @override - _i14.NodeModel? getNodeById({required String? id}) => + _i16.NodeModel? getNodeById({required String? id}) => (super.noSuchMethod(Invocation.method( #getNodeById, [], {#id: id}, - )) as _i14.NodeModel?); + )) as _i16.NodeModel?); @override - List<_i14.NodeModel> failoverNodesFor({required _i10.Coin? coin}) => + List<_i16.NodeModel> failoverNodesFor({required _i11.Coin? coin}) => (super.noSuchMethod( Invocation.method( #failoverNodesFor, [], {#coin: coin}, ), - returnValue: <_i14.NodeModel>[], - ) as List<_i14.NodeModel>); + returnValue: <_i16.NodeModel>[], + ) as List<_i16.NodeModel>); @override - _i8.Future add( - _i14.NodeModel? node, + _i9.Future add( + _i16.NodeModel? node, String? password, bool? shouldNotifyListeners, ) => @@ -841,11 +841,11 @@ class MockNodeService extends _i1.Mock implements _i13.NodeService { shouldNotifyListeners, ], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future delete( + _i9.Future delete( String? id, bool? shouldNotifyListeners, ) => @@ -857,11 +857,11 @@ class MockNodeService extends _i1.Mock implements _i13.NodeService { shouldNotifyListeners, ], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future setEnabledState( + _i9.Future setEnabledState( String? id, bool? enabled, bool? shouldNotifyListeners, @@ -875,12 +875,12 @@ class MockNodeService extends _i1.Mock implements _i13.NodeService { shouldNotifyListeners, ], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future edit( - _i14.NodeModel? editedNode, + _i9.Future edit( + _i16.NodeModel? editedNode, String? password, bool? shouldNotifyListeners, ) => @@ -893,20 +893,20 @@ class MockNodeService extends _i1.Mock implements _i13.NodeService { shouldNotifyListeners, ], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future updateCommunityNodes() => (super.noSuchMethod( + _i9.Future updateCommunityNodes() => (super.noSuchMethod( Invocation.method( #updateCommunityNodes, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - void addListener(_i11.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i12.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -914,7 +914,7 @@ class MockNodeService extends _i1.Mock implements _i13.NodeService { returnValueForMissingStub: null, ); @override - void removeListener(_i11.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i12.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/screen_tests/onboarding/verify_backup_key_view_screen_test.mocks.dart b/test/screen_tests/onboarding/verify_backup_key_view_screen_test.mocks.dart index 775326480..8d2715174 100644 --- a/test/screen_tests/onboarding/verify_backup_key_view_screen_test.mocks.dart +++ b/test/screen_tests/onboarding/verify_backup_key_view_screen_test.mocks.dart @@ -3,15 +3,17 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i7; -import 'dart:ui' as _i8; +import 'dart:async' as _i8; +import 'dart:ui' as _i10; -import 'package:decimal/decimal.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/models/balance.dart' as _i4; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i9; import 'package:stackwallet/models/models.dart' as _i3; import 'package:stackwallet/services/coins/coin_service.dart' as _i2; -import 'package:stackwallet/services/coins/manager.dart' as _i5; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i6; +import 'package:stackwallet/services/coins/manager.dart' as _i6; +import 'package:stackwallet/utilities/amount/amount.dart' as _i5; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i7; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -45,8 +47,8 @@ class _FakeFeeObject_1 extends _i1.SmartFake implements _i3.FeeObject { ); } -class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { - _FakeDecimal_2( +class _FakeBalance_2 extends _i1.SmartFake implements _i4.Balance { + _FakeBalance_2( Object parent, Invocation parentInvocation, ) : super( @@ -55,9 +57,8 @@ class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { ); } -class _FakeTransactionData_3 extends _i1.SmartFake - implements _i3.TransactionData { - _FakeTransactionData_3( +class _FakeAmount_3 extends _i1.SmartFake implements _i5.Amount { + _FakeAmount_3( Object parent, Invocation parentInvocation, ) : super( @@ -69,7 +70,7 @@ class _FakeTransactionData_3 extends _i1.SmartFake /// A class which mocks [Manager]. /// /// See the documentation for Mockito's code generation for more information. -class MockManager extends _i1.Mock implements _i5.Manager { +class MockManager extends _i1.Mock implements _i6.Manager { @override bool get isActiveWallet => (super.noSuchMethod( Invocation.getter(#isActiveWallet), @@ -97,10 +98,10 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: false, ) as bool); @override - _i6.Coin get coin => (super.noSuchMethod( + _i7.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i6.Coin.bitcoin, - ) as _i6.Coin); + returnValue: _i7.Coin.bitcoin, + ) as _i7.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -133,90 +134,42 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - _i7.Future<_i3.FeeObject> get fees => (super.noSuchMethod( + _i8.Future<_i3.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i7.Future<_i3.FeeObject>.value(_FakeFeeObject_1( + returnValue: _i8.Future<_i3.FeeObject>.value(_FakeFeeObject_1( this, Invocation.getter(#fees), )), - ) as _i7.Future<_i3.FeeObject>); + ) as _i8.Future<_i3.FeeObject>); @override - _i7.Future get maxFee => (super.noSuchMethod( + _i8.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future.value(0), + ) as _i8.Future); @override - _i7.Future get currentReceivingAddress => (super.noSuchMethod( + _i8.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future<_i4.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( + _i4.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_2( this, - Invocation.getter(#availableBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i4.Decimal); + ) as _i4.Balance); @override - _i7.Future<_i4.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i7.Future<_i4.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i7.Future<_i4.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#totalBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i4.Decimal); - @override - _i7.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); - @override - _i7.Future<_i3.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i8.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i7.Future<_i3.TransactionData>.value(_FakeTransactionData_3( - this, - Invocation.getter(#transactionData), - )), - ) as _i7.Future<_i3.TransactionData>); + _i8.Future>.value(<_i9.Transaction>[]), + ) as _i8.Future>); @override - _i7.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: _i7.Future>.value(<_i3.UtxoObject>[]), - ) as _i7.Future>); + _i8.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i8.Future>.value(<_i9.UTXO>[]), + ) as _i8.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -236,29 +189,74 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: '', ) as String); @override - _i7.Future> get mnemonic => (super.noSuchMethod( + _i8.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); + @override + _i8.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i8.Future.value(), + ) as _i8.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i8.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i8.Future.value(''), + ) as _i8.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i7.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i8.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -268,9 +266,9 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - _i7.Future> prepareSend({ + _i8.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i5.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -279,50 +277,32 @@ class MockManager extends _i1.Mock implements _i5.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i7.Future>.value({}), - ) as _i7.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future confirmSend({required Map? txData}) => + _i8.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); - @override - _i7.Future refresh() => (super.noSuchMethod( + _i8.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -332,34 +312,35 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: false, ) as bool); @override - _i7.Future testNetworkConnection() => (super.noSuchMethod( + _i8.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future initializeNew() => (super.noSuchMethod( + _i8.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future initializeExisting() => (super.noSuchMethod( + _i8.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future recoverFromMnemonic({ + _i8.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -370,25 +351,26 @@ class MockManager extends _i1.Mock implements _i5.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future exitCurrentWallet() => (super.noSuchMethod( + _i8.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future fullRescan( + _i8.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -400,42 +382,52 @@ class MockManager extends _i1.Mock implements _i5.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); - @override - _i7.Future estimateFeeFor( - int? satoshiAmount, + _i8.Future<_i5.Amount> estimateFeeFor( + _i5.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future<_i5.Amount>.value(_FakeAmount_3( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i8.Future<_i5.Amount>); @override - _i7.Future generateNewAddress() => (super.noSuchMethod( + _i8.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + _i8.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -443,7 +435,7 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/screen_tests/settings_view/settings_subviews/currency_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/currency_view_screen_test.mocks.dart index dc2ff6257..742d98a9c 100644 --- a/test/screen_tests/settings_view/settings_subviews/currency_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_subviews/currency_view_screen_test.mocks.dart @@ -3,15 +3,17 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i7; -import 'dart:ui' as _i8; +import 'dart:async' as _i8; +import 'dart:ui' as _i10; -import 'package:decimal/decimal.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/models/balance.dart' as _i4; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i9; import 'package:stackwallet/models/models.dart' as _i3; import 'package:stackwallet/services/coins/coin_service.dart' as _i2; -import 'package:stackwallet/services/coins/manager.dart' as _i5; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i6; +import 'package:stackwallet/services/coins/manager.dart' as _i6; +import 'package:stackwallet/utilities/amount/amount.dart' as _i5; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i7; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -45,8 +47,8 @@ class _FakeFeeObject_1 extends _i1.SmartFake implements _i3.FeeObject { ); } -class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { - _FakeDecimal_2( +class _FakeBalance_2 extends _i1.SmartFake implements _i4.Balance { + _FakeBalance_2( Object parent, Invocation parentInvocation, ) : super( @@ -55,9 +57,8 @@ class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { ); } -class _FakeTransactionData_3 extends _i1.SmartFake - implements _i3.TransactionData { - _FakeTransactionData_3( +class _FakeAmount_3 extends _i1.SmartFake implements _i5.Amount { + _FakeAmount_3( Object parent, Invocation parentInvocation, ) : super( @@ -69,7 +70,7 @@ class _FakeTransactionData_3 extends _i1.SmartFake /// A class which mocks [Manager]. /// /// See the documentation for Mockito's code generation for more information. -class MockManager extends _i1.Mock implements _i5.Manager { +class MockManager extends _i1.Mock implements _i6.Manager { @override bool get isActiveWallet => (super.noSuchMethod( Invocation.getter(#isActiveWallet), @@ -97,10 +98,10 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: false, ) as bool); @override - _i6.Coin get coin => (super.noSuchMethod( + _i7.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i6.Coin.bitcoin, - ) as _i6.Coin); + returnValue: _i7.Coin.bitcoin, + ) as _i7.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -133,90 +134,42 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - _i7.Future<_i3.FeeObject> get fees => (super.noSuchMethod( + _i8.Future<_i3.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i7.Future<_i3.FeeObject>.value(_FakeFeeObject_1( + returnValue: _i8.Future<_i3.FeeObject>.value(_FakeFeeObject_1( this, Invocation.getter(#fees), )), - ) as _i7.Future<_i3.FeeObject>); + ) as _i8.Future<_i3.FeeObject>); @override - _i7.Future get maxFee => (super.noSuchMethod( + _i8.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future.value(0), + ) as _i8.Future); @override - _i7.Future get currentReceivingAddress => (super.noSuchMethod( + _i8.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future<_i4.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( + _i4.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_2( this, - Invocation.getter(#availableBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i4.Decimal); + ) as _i4.Balance); @override - _i7.Future<_i4.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i7.Future<_i4.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i7.Future<_i4.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#totalBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i4.Decimal); - @override - _i7.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); - @override - _i7.Future<_i3.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i8.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i7.Future<_i3.TransactionData>.value(_FakeTransactionData_3( - this, - Invocation.getter(#transactionData), - )), - ) as _i7.Future<_i3.TransactionData>); + _i8.Future>.value(<_i9.Transaction>[]), + ) as _i8.Future>); @override - _i7.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: _i7.Future>.value(<_i3.UtxoObject>[]), - ) as _i7.Future>); + _i8.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i8.Future>.value(<_i9.UTXO>[]), + ) as _i8.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -236,29 +189,74 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: '', ) as String); @override - _i7.Future> get mnemonic => (super.noSuchMethod( + _i8.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); + @override + _i8.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i8.Future.value(), + ) as _i8.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i8.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i8.Future.value(''), + ) as _i8.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i7.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i8.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -268,9 +266,9 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - _i7.Future> prepareSend({ + _i8.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i5.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -279,50 +277,32 @@ class MockManager extends _i1.Mock implements _i5.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i7.Future>.value({}), - ) as _i7.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future confirmSend({required Map? txData}) => + _i8.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); - @override - _i7.Future refresh() => (super.noSuchMethod( + _i8.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -332,34 +312,35 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: false, ) as bool); @override - _i7.Future testNetworkConnection() => (super.noSuchMethod( + _i8.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future initializeNew() => (super.noSuchMethod( + _i8.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future initializeExisting() => (super.noSuchMethod( + _i8.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future recoverFromMnemonic({ + _i8.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -370,25 +351,26 @@ class MockManager extends _i1.Mock implements _i5.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future exitCurrentWallet() => (super.noSuchMethod( + _i8.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future fullRescan( + _i8.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -400,42 +382,52 @@ class MockManager extends _i1.Mock implements _i5.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); - @override - _i7.Future estimateFeeFor( - int? satoshiAmount, + _i8.Future<_i5.Amount> estimateFeeFor( + _i5.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future<_i5.Amount>.value(_FakeAmount_3( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i8.Future<_i5.Amount>); @override - _i7.Future generateNewAddress() => (super.noSuchMethod( + _i8.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + _i8.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -443,7 +435,7 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/add_custom_node_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/add_custom_node_view_screen_test.mocks.dart index 9cdd45a2a..0db6ce111 100644 --- a/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/add_custom_node_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/add_custom_node_view_screen_test.mocks.dart @@ -3,17 +3,19 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i8; -import 'dart:ui' as _i10; +import 'dart:async' as _i9; +import 'dart:ui' as _i11; -import 'package:decimal/decimal.dart' as _i5; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/models/balance.dart' as _i5; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i13; import 'package:stackwallet/models/models.dart' as _i4; -import 'package:stackwallet/models/node_model.dart' as _i7; +import 'package:stackwallet/models/node_model.dart' as _i8; import 'package:stackwallet/services/coins/coin_service.dart' as _i3; -import 'package:stackwallet/services/coins/manager.dart' as _i11; -import 'package:stackwallet/services/node_service.dart' as _i6; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i9; +import 'package:stackwallet/services/coins/manager.dart' as _i12; +import 'package:stackwallet/services/node_service.dart' as _i7; +import 'package:stackwallet/utilities/amount/amount.dart' as _i6; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i10; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' as _i2; @@ -60,8 +62,8 @@ class _FakeFeeObject_2 extends _i1.SmartFake implements _i4.FeeObject { ); } -class _FakeDecimal_3 extends _i1.SmartFake implements _i5.Decimal { - _FakeDecimal_3( +class _FakeBalance_3 extends _i1.SmartFake implements _i5.Balance { + _FakeBalance_3( Object parent, Invocation parentInvocation, ) : super( @@ -70,9 +72,8 @@ class _FakeDecimal_3 extends _i1.SmartFake implements _i5.Decimal { ); } -class _FakeTransactionData_4 extends _i1.SmartFake - implements _i4.TransactionData { - _FakeTransactionData_4( +class _FakeAmount_4 extends _i1.SmartFake implements _i6.Amount { + _FakeAmount_4( Object parent, Invocation parentInvocation, ) : super( @@ -84,7 +85,7 @@ class _FakeTransactionData_4 extends _i1.SmartFake /// A class which mocks [NodeService]. /// /// See the documentation for Mockito's code generation for more information. -class MockNodeService extends _i1.Mock implements _i6.NodeService { +class MockNodeService extends _i1.Mock implements _i7.NodeService { @override _i2.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod( Invocation.getter(#secureStorageInterface), @@ -94,33 +95,33 @@ class MockNodeService extends _i1.Mock implements _i6.NodeService { ), ) as _i2.SecureStorageInterface); @override - List<_i7.NodeModel> get primaryNodes => (super.noSuchMethod( + List<_i8.NodeModel> get primaryNodes => (super.noSuchMethod( Invocation.getter(#primaryNodes), - returnValue: <_i7.NodeModel>[], - ) as List<_i7.NodeModel>); + returnValue: <_i8.NodeModel>[], + ) as List<_i8.NodeModel>); @override - List<_i7.NodeModel> get nodes => (super.noSuchMethod( + List<_i8.NodeModel> get nodes => (super.noSuchMethod( Invocation.getter(#nodes), - returnValue: <_i7.NodeModel>[], - ) as List<_i7.NodeModel>); + returnValue: <_i8.NodeModel>[], + ) as List<_i8.NodeModel>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i8.Future updateDefaults() => (super.noSuchMethod( + _i9.Future updateDefaults() => (super.noSuchMethod( Invocation.method( #updateDefaults, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future setPrimaryNodeFor({ - required _i9.Coin? coin, - required _i7.NodeModel? node, + _i9.Future setPrimaryNodeFor({ + required _i10.Coin? coin, + required _i8.NodeModel? node, bool? shouldNotifyListeners = false, }) => (super.noSuchMethod( @@ -133,44 +134,44 @@ class MockNodeService extends _i1.Mock implements _i6.NodeService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i7.NodeModel? getPrimaryNodeFor({required _i9.Coin? coin}) => + _i8.NodeModel? getPrimaryNodeFor({required _i10.Coin? coin}) => (super.noSuchMethod(Invocation.method( #getPrimaryNodeFor, [], {#coin: coin}, - )) as _i7.NodeModel?); + )) as _i8.NodeModel?); @override - List<_i7.NodeModel> getNodesFor(_i9.Coin? coin) => (super.noSuchMethod( + List<_i8.NodeModel> getNodesFor(_i10.Coin? coin) => (super.noSuchMethod( Invocation.method( #getNodesFor, [coin], ), - returnValue: <_i7.NodeModel>[], - ) as List<_i7.NodeModel>); + returnValue: <_i8.NodeModel>[], + ) as List<_i8.NodeModel>); @override - _i7.NodeModel? getNodeById({required String? id}) => + _i8.NodeModel? getNodeById({required String? id}) => (super.noSuchMethod(Invocation.method( #getNodeById, [], {#id: id}, - )) as _i7.NodeModel?); + )) as _i8.NodeModel?); @override - List<_i7.NodeModel> failoverNodesFor({required _i9.Coin? coin}) => + List<_i8.NodeModel> failoverNodesFor({required _i10.Coin? coin}) => (super.noSuchMethod( Invocation.method( #failoverNodesFor, [], {#coin: coin}, ), - returnValue: <_i7.NodeModel>[], - ) as List<_i7.NodeModel>); + returnValue: <_i8.NodeModel>[], + ) as List<_i8.NodeModel>); @override - _i8.Future add( - _i7.NodeModel? node, + _i9.Future add( + _i8.NodeModel? node, String? password, bool? shouldNotifyListeners, ) => @@ -183,11 +184,11 @@ class MockNodeService extends _i1.Mock implements _i6.NodeService { shouldNotifyListeners, ], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future delete( + _i9.Future delete( String? id, bool? shouldNotifyListeners, ) => @@ -199,11 +200,11 @@ class MockNodeService extends _i1.Mock implements _i6.NodeService { shouldNotifyListeners, ], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future setEnabledState( + _i9.Future setEnabledState( String? id, bool? enabled, bool? shouldNotifyListeners, @@ -217,12 +218,12 @@ class MockNodeService extends _i1.Mock implements _i6.NodeService { shouldNotifyListeners, ], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future edit( - _i7.NodeModel? editedNode, + _i9.Future edit( + _i8.NodeModel? editedNode, String? password, bool? shouldNotifyListeners, ) => @@ -235,20 +236,20 @@ class MockNodeService extends _i1.Mock implements _i6.NodeService { shouldNotifyListeners, ], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future updateCommunityNodes() => (super.noSuchMethod( + _i9.Future updateCommunityNodes() => (super.noSuchMethod( Invocation.method( #updateCommunityNodes, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i11.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -256,7 +257,7 @@ class MockNodeService extends _i1.Mock implements _i6.NodeService { returnValueForMissingStub: null, ); @override - void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i11.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -284,7 +285,7 @@ class MockNodeService extends _i1.Mock implements _i6.NodeService { /// A class which mocks [Manager]. /// /// See the documentation for Mockito's code generation for more information. -class MockManager extends _i1.Mock implements _i11.Manager { +class MockManager extends _i1.Mock implements _i12.Manager { @override bool get isActiveWallet => (super.noSuchMethod( Invocation.getter(#isActiveWallet), @@ -312,10 +313,10 @@ class MockManager extends _i1.Mock implements _i11.Manager { returnValue: false, ) as bool); @override - _i9.Coin get coin => (super.noSuchMethod( + _i10.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i9.Coin.bitcoin, - ) as _i9.Coin); + returnValue: _i10.Coin.bitcoin, + ) as _i10.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -348,90 +349,42 @@ class MockManager extends _i1.Mock implements _i11.Manager { returnValueForMissingStub: null, ); @override - _i8.Future<_i4.FeeObject> get fees => (super.noSuchMethod( + _i9.Future<_i4.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i8.Future<_i4.FeeObject>.value(_FakeFeeObject_2( + returnValue: _i9.Future<_i4.FeeObject>.value(_FakeFeeObject_2( this, Invocation.getter(#fees), )), - ) as _i8.Future<_i4.FeeObject>); + ) as _i9.Future<_i4.FeeObject>); @override - _i8.Future get maxFee => (super.noSuchMethod( + _i9.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i8.Future.value(0), - ) as _i8.Future); + returnValue: _i9.Future.value(0), + ) as _i9.Future); @override - _i8.Future get currentReceivingAddress => (super.noSuchMethod( + _i9.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i8.Future.value(''), - ) as _i8.Future); + returnValue: _i9.Future.value(''), + ) as _i9.Future); @override - _i8.Future<_i5.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i8.Future<_i5.Decimal>.value(_FakeDecimal_3( + _i5.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_3( this, - Invocation.getter(#availableBalance), - )), - ) as _i8.Future<_i5.Decimal>); - @override - _i5.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_3( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i5.Decimal); + ) as _i5.Balance); @override - _i8.Future<_i5.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i8.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i8.Future<_i5.Decimal>); - @override - _i8.Future<_i5.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i8.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i8.Future<_i5.Decimal>); - @override - _i8.Future<_i5.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i8.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#totalBalance), - )), - ) as _i8.Future<_i5.Decimal>); - @override - _i5.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_3( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i5.Decimal); - @override - _i8.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i8.Future>.value([]), - ) as _i8.Future>); - @override - _i8.Future<_i4.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i9.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i8.Future<_i4.TransactionData>.value(_FakeTransactionData_4( - this, - Invocation.getter(#transactionData), - )), - ) as _i8.Future<_i4.TransactionData>); + _i9.Future>.value(<_i13.Transaction>[]), + ) as _i9.Future>); @override - _i8.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: _i8.Future>.value(<_i4.UtxoObject>[]), - ) as _i8.Future>); + _i9.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i9.Future>.value(<_i13.UTXO>[]), + ) as _i9.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -451,29 +404,74 @@ class MockManager extends _i1.Mock implements _i11.Manager { returnValue: '', ) as String); @override - _i8.Future> get mnemonic => (super.noSuchMethod( + _i9.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i8.Future>.value([]), - ) as _i8.Future>); + returnValue: _i9.Future>.value([]), + ) as _i9.Future>); + @override + _i9.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i9.Future.value(), + ) as _i9.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i9.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i9.Future.value(''), + ) as _i9.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i8.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i9.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -483,9 +481,9 @@ class MockManager extends _i1.Mock implements _i11.Manager { returnValueForMissingStub: null, ); @override - _i8.Future> prepareSend({ + _i9.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i6.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -494,50 +492,32 @@ class MockManager extends _i1.Mock implements _i11.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i8.Future>.value({}), - ) as _i8.Future>); + _i9.Future>.value({}), + ) as _i9.Future>); @override - _i8.Future confirmSend({required Map? txData}) => + _i9.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i8.Future.value(''), - ) as _i8.Future); + returnValue: _i9.Future.value(''), + ) as _i9.Future); @override - _i8.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i8.Future.value(''), - ) as _i8.Future); - @override - _i8.Future refresh() => (super.noSuchMethod( + _i9.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -547,34 +527,35 @@ class MockManager extends _i1.Mock implements _i11.Manager { returnValue: false, ) as bool); @override - _i8.Future testNetworkConnection() => (super.noSuchMethod( + _i9.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); @override - _i8.Future initializeNew() => (super.noSuchMethod( + _i9.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future initializeExisting() => (super.noSuchMethod( + _i9.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future recoverFromMnemonic({ + _i9.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -585,25 +566,26 @@ class MockManager extends _i1.Mock implements _i11.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future exitCurrentWallet() => (super.noSuchMethod( + _i9.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future fullRescan( + _i9.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -615,42 +597,52 @@ class MockManager extends _i1.Mock implements _i11.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); - @override - _i8.Future estimateFeeFor( - int? satoshiAmount, + _i9.Future<_i6.Amount> estimateFeeFor( + _i6.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i8.Future.value(0), - ) as _i8.Future); + returnValue: _i9.Future<_i6.Amount>.value(_FakeAmount_4( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i9.Future<_i6.Amount>); @override - _i8.Future generateNewAddress() => (super.noSuchMethod( + _i9.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); @override - void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( + _i9.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override + void addListener(_i11.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -658,7 +650,7 @@ class MockManager extends _i1.Mock implements _i11.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i11.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/node_details_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/node_details_view_screen_test.mocks.dart index 984287655..865ed2f88 100644 --- a/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/node_details_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_subviews/network_settings_subviews/node_details_view_screen_test.mocks.dart @@ -3,17 +3,19 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i8; -import 'dart:ui' as _i10; +import 'dart:async' as _i9; +import 'dart:ui' as _i11; -import 'package:decimal/decimal.dart' as _i5; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/models/balance.dart' as _i5; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i13; import 'package:stackwallet/models/models.dart' as _i4; -import 'package:stackwallet/models/node_model.dart' as _i7; +import 'package:stackwallet/models/node_model.dart' as _i8; import 'package:stackwallet/services/coins/coin_service.dart' as _i3; -import 'package:stackwallet/services/coins/manager.dart' as _i11; -import 'package:stackwallet/services/node_service.dart' as _i6; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i9; +import 'package:stackwallet/services/coins/manager.dart' as _i12; +import 'package:stackwallet/services/node_service.dart' as _i7; +import 'package:stackwallet/utilities/amount/amount.dart' as _i6; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i10; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' as _i2; @@ -60,8 +62,8 @@ class _FakeFeeObject_2 extends _i1.SmartFake implements _i4.FeeObject { ); } -class _FakeDecimal_3 extends _i1.SmartFake implements _i5.Decimal { - _FakeDecimal_3( +class _FakeBalance_3 extends _i1.SmartFake implements _i5.Balance { + _FakeBalance_3( Object parent, Invocation parentInvocation, ) : super( @@ -70,9 +72,8 @@ class _FakeDecimal_3 extends _i1.SmartFake implements _i5.Decimal { ); } -class _FakeTransactionData_4 extends _i1.SmartFake - implements _i4.TransactionData { - _FakeTransactionData_4( +class _FakeAmount_4 extends _i1.SmartFake implements _i6.Amount { + _FakeAmount_4( Object parent, Invocation parentInvocation, ) : super( @@ -84,7 +85,7 @@ class _FakeTransactionData_4 extends _i1.SmartFake /// A class which mocks [NodeService]. /// /// See the documentation for Mockito's code generation for more information. -class MockNodeService extends _i1.Mock implements _i6.NodeService { +class MockNodeService extends _i1.Mock implements _i7.NodeService { @override _i2.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod( Invocation.getter(#secureStorageInterface), @@ -94,33 +95,33 @@ class MockNodeService extends _i1.Mock implements _i6.NodeService { ), ) as _i2.SecureStorageInterface); @override - List<_i7.NodeModel> get primaryNodes => (super.noSuchMethod( + List<_i8.NodeModel> get primaryNodes => (super.noSuchMethod( Invocation.getter(#primaryNodes), - returnValue: <_i7.NodeModel>[], - ) as List<_i7.NodeModel>); + returnValue: <_i8.NodeModel>[], + ) as List<_i8.NodeModel>); @override - List<_i7.NodeModel> get nodes => (super.noSuchMethod( + List<_i8.NodeModel> get nodes => (super.noSuchMethod( Invocation.getter(#nodes), - returnValue: <_i7.NodeModel>[], - ) as List<_i7.NodeModel>); + returnValue: <_i8.NodeModel>[], + ) as List<_i8.NodeModel>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i8.Future updateDefaults() => (super.noSuchMethod( + _i9.Future updateDefaults() => (super.noSuchMethod( Invocation.method( #updateDefaults, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future setPrimaryNodeFor({ - required _i9.Coin? coin, - required _i7.NodeModel? node, + _i9.Future setPrimaryNodeFor({ + required _i10.Coin? coin, + required _i8.NodeModel? node, bool? shouldNotifyListeners = false, }) => (super.noSuchMethod( @@ -133,44 +134,44 @@ class MockNodeService extends _i1.Mock implements _i6.NodeService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i7.NodeModel? getPrimaryNodeFor({required _i9.Coin? coin}) => + _i8.NodeModel? getPrimaryNodeFor({required _i10.Coin? coin}) => (super.noSuchMethod(Invocation.method( #getPrimaryNodeFor, [], {#coin: coin}, - )) as _i7.NodeModel?); + )) as _i8.NodeModel?); @override - List<_i7.NodeModel> getNodesFor(_i9.Coin? coin) => (super.noSuchMethod( + List<_i8.NodeModel> getNodesFor(_i10.Coin? coin) => (super.noSuchMethod( Invocation.method( #getNodesFor, [coin], ), - returnValue: <_i7.NodeModel>[], - ) as List<_i7.NodeModel>); + returnValue: <_i8.NodeModel>[], + ) as List<_i8.NodeModel>); @override - _i7.NodeModel? getNodeById({required String? id}) => + _i8.NodeModel? getNodeById({required String? id}) => (super.noSuchMethod(Invocation.method( #getNodeById, [], {#id: id}, - )) as _i7.NodeModel?); + )) as _i8.NodeModel?); @override - List<_i7.NodeModel> failoverNodesFor({required _i9.Coin? coin}) => + List<_i8.NodeModel> failoverNodesFor({required _i10.Coin? coin}) => (super.noSuchMethod( Invocation.method( #failoverNodesFor, [], {#coin: coin}, ), - returnValue: <_i7.NodeModel>[], - ) as List<_i7.NodeModel>); + returnValue: <_i8.NodeModel>[], + ) as List<_i8.NodeModel>); @override - _i8.Future add( - _i7.NodeModel? node, + _i9.Future add( + _i8.NodeModel? node, String? password, bool? shouldNotifyListeners, ) => @@ -183,11 +184,11 @@ class MockNodeService extends _i1.Mock implements _i6.NodeService { shouldNotifyListeners, ], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future delete( + _i9.Future delete( String? id, bool? shouldNotifyListeners, ) => @@ -199,11 +200,11 @@ class MockNodeService extends _i1.Mock implements _i6.NodeService { shouldNotifyListeners, ], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future setEnabledState( + _i9.Future setEnabledState( String? id, bool? enabled, bool? shouldNotifyListeners, @@ -217,12 +218,12 @@ class MockNodeService extends _i1.Mock implements _i6.NodeService { shouldNotifyListeners, ], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future edit( - _i7.NodeModel? editedNode, + _i9.Future edit( + _i8.NodeModel? editedNode, String? password, bool? shouldNotifyListeners, ) => @@ -235,20 +236,20 @@ class MockNodeService extends _i1.Mock implements _i6.NodeService { shouldNotifyListeners, ], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future updateCommunityNodes() => (super.noSuchMethod( + _i9.Future updateCommunityNodes() => (super.noSuchMethod( Invocation.method( #updateCommunityNodes, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i11.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -256,7 +257,7 @@ class MockNodeService extends _i1.Mock implements _i6.NodeService { returnValueForMissingStub: null, ); @override - void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i11.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -284,7 +285,7 @@ class MockNodeService extends _i1.Mock implements _i6.NodeService { /// A class which mocks [Manager]. /// /// See the documentation for Mockito's code generation for more information. -class MockManager extends _i1.Mock implements _i11.Manager { +class MockManager extends _i1.Mock implements _i12.Manager { @override bool get isActiveWallet => (super.noSuchMethod( Invocation.getter(#isActiveWallet), @@ -312,10 +313,10 @@ class MockManager extends _i1.Mock implements _i11.Manager { returnValue: false, ) as bool); @override - _i9.Coin get coin => (super.noSuchMethod( + _i10.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i9.Coin.bitcoin, - ) as _i9.Coin); + returnValue: _i10.Coin.bitcoin, + ) as _i10.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -348,90 +349,42 @@ class MockManager extends _i1.Mock implements _i11.Manager { returnValueForMissingStub: null, ); @override - _i8.Future<_i4.FeeObject> get fees => (super.noSuchMethod( + _i9.Future<_i4.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i8.Future<_i4.FeeObject>.value(_FakeFeeObject_2( + returnValue: _i9.Future<_i4.FeeObject>.value(_FakeFeeObject_2( this, Invocation.getter(#fees), )), - ) as _i8.Future<_i4.FeeObject>); + ) as _i9.Future<_i4.FeeObject>); @override - _i8.Future get maxFee => (super.noSuchMethod( + _i9.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i8.Future.value(0), - ) as _i8.Future); + returnValue: _i9.Future.value(0), + ) as _i9.Future); @override - _i8.Future get currentReceivingAddress => (super.noSuchMethod( + _i9.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i8.Future.value(''), - ) as _i8.Future); + returnValue: _i9.Future.value(''), + ) as _i9.Future); @override - _i8.Future<_i5.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i8.Future<_i5.Decimal>.value(_FakeDecimal_3( + _i5.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_3( this, - Invocation.getter(#availableBalance), - )), - ) as _i8.Future<_i5.Decimal>); - @override - _i5.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_3( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i5.Decimal); + ) as _i5.Balance); @override - _i8.Future<_i5.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i8.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i8.Future<_i5.Decimal>); - @override - _i8.Future<_i5.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i8.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i8.Future<_i5.Decimal>); - @override - _i8.Future<_i5.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i8.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#totalBalance), - )), - ) as _i8.Future<_i5.Decimal>); - @override - _i5.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_3( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i5.Decimal); - @override - _i8.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i8.Future>.value([]), - ) as _i8.Future>); - @override - _i8.Future<_i4.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i9.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i8.Future<_i4.TransactionData>.value(_FakeTransactionData_4( - this, - Invocation.getter(#transactionData), - )), - ) as _i8.Future<_i4.TransactionData>); + _i9.Future>.value(<_i13.Transaction>[]), + ) as _i9.Future>); @override - _i8.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: _i8.Future>.value(<_i4.UtxoObject>[]), - ) as _i8.Future>); + _i9.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i9.Future>.value(<_i13.UTXO>[]), + ) as _i9.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -451,29 +404,74 @@ class MockManager extends _i1.Mock implements _i11.Manager { returnValue: '', ) as String); @override - _i8.Future> get mnemonic => (super.noSuchMethod( + _i9.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i8.Future>.value([]), - ) as _i8.Future>); + returnValue: _i9.Future>.value([]), + ) as _i9.Future>); + @override + _i9.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i9.Future.value(), + ) as _i9.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i9.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i9.Future.value(''), + ) as _i9.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i8.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i9.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -483,9 +481,9 @@ class MockManager extends _i1.Mock implements _i11.Manager { returnValueForMissingStub: null, ); @override - _i8.Future> prepareSend({ + _i9.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i6.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -494,50 +492,32 @@ class MockManager extends _i1.Mock implements _i11.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i8.Future>.value({}), - ) as _i8.Future>); + _i9.Future>.value({}), + ) as _i9.Future>); @override - _i8.Future confirmSend({required Map? txData}) => + _i9.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i8.Future.value(''), - ) as _i8.Future); + returnValue: _i9.Future.value(''), + ) as _i9.Future); @override - _i8.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i8.Future.value(''), - ) as _i8.Future); - @override - _i8.Future refresh() => (super.noSuchMethod( + _i9.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -547,34 +527,35 @@ class MockManager extends _i1.Mock implements _i11.Manager { returnValue: false, ) as bool); @override - _i8.Future testNetworkConnection() => (super.noSuchMethod( + _i9.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); @override - _i8.Future initializeNew() => (super.noSuchMethod( + _i9.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future initializeExisting() => (super.noSuchMethod( + _i9.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future recoverFromMnemonic({ + _i9.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -585,25 +566,26 @@ class MockManager extends _i1.Mock implements _i11.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future exitCurrentWallet() => (super.noSuchMethod( + _i9.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future fullRescan( + _i9.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -615,42 +597,52 @@ class MockManager extends _i1.Mock implements _i11.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); - @override - _i8.Future estimateFeeFor( - int? satoshiAmount, + _i9.Future<_i6.Amount> estimateFeeFor( + _i6.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i8.Future.value(0), - ) as _i8.Future); + returnValue: _i9.Future<_i6.Amount>.value(_FakeAmount_4( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i9.Future<_i6.Amount>); @override - _i8.Future generateNewAddress() => (super.noSuchMethod( + _i9.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); @override - void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( + _i9.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override + void addListener(_i11.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -658,7 +650,7 @@ class MockManager extends _i1.Mock implements _i11.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i11.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/screen_tests/settings_view/settings_subviews/wallet_backup_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/wallet_backup_view_screen_test.mocks.dart index 3241ca931..25a849858 100644 --- a/test/screen_tests/settings_view/settings_subviews/wallet_backup_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_subviews/wallet_backup_view_screen_test.mocks.dart @@ -3,15 +3,17 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i7; -import 'dart:ui' as _i8; +import 'dart:async' as _i8; +import 'dart:ui' as _i10; -import 'package:decimal/decimal.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/models/balance.dart' as _i4; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i9; import 'package:stackwallet/models/models.dart' as _i3; import 'package:stackwallet/services/coins/coin_service.dart' as _i2; -import 'package:stackwallet/services/coins/manager.dart' as _i5; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i6; +import 'package:stackwallet/services/coins/manager.dart' as _i6; +import 'package:stackwallet/utilities/amount/amount.dart' as _i5; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i7; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -45,8 +47,8 @@ class _FakeFeeObject_1 extends _i1.SmartFake implements _i3.FeeObject { ); } -class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { - _FakeDecimal_2( +class _FakeBalance_2 extends _i1.SmartFake implements _i4.Balance { + _FakeBalance_2( Object parent, Invocation parentInvocation, ) : super( @@ -55,9 +57,8 @@ class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { ); } -class _FakeTransactionData_3 extends _i1.SmartFake - implements _i3.TransactionData { - _FakeTransactionData_3( +class _FakeAmount_3 extends _i1.SmartFake implements _i5.Amount { + _FakeAmount_3( Object parent, Invocation parentInvocation, ) : super( @@ -69,7 +70,7 @@ class _FakeTransactionData_3 extends _i1.SmartFake /// A class which mocks [Manager]. /// /// See the documentation for Mockito's code generation for more information. -class MockManager extends _i1.Mock implements _i5.Manager { +class MockManager extends _i1.Mock implements _i6.Manager { @override bool get isActiveWallet => (super.noSuchMethod( Invocation.getter(#isActiveWallet), @@ -97,10 +98,10 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: false, ) as bool); @override - _i6.Coin get coin => (super.noSuchMethod( + _i7.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i6.Coin.bitcoin, - ) as _i6.Coin); + returnValue: _i7.Coin.bitcoin, + ) as _i7.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -133,90 +134,42 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - _i7.Future<_i3.FeeObject> get fees => (super.noSuchMethod( + _i8.Future<_i3.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i7.Future<_i3.FeeObject>.value(_FakeFeeObject_1( + returnValue: _i8.Future<_i3.FeeObject>.value(_FakeFeeObject_1( this, Invocation.getter(#fees), )), - ) as _i7.Future<_i3.FeeObject>); + ) as _i8.Future<_i3.FeeObject>); @override - _i7.Future get maxFee => (super.noSuchMethod( + _i8.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future.value(0), + ) as _i8.Future); @override - _i7.Future get currentReceivingAddress => (super.noSuchMethod( + _i8.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future<_i4.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( + _i4.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_2( this, - Invocation.getter(#availableBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i4.Decimal); + ) as _i4.Balance); @override - _i7.Future<_i4.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i7.Future<_i4.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i7.Future<_i4.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#totalBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i4.Decimal); - @override - _i7.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); - @override - _i7.Future<_i3.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i8.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i7.Future<_i3.TransactionData>.value(_FakeTransactionData_3( - this, - Invocation.getter(#transactionData), - )), - ) as _i7.Future<_i3.TransactionData>); + _i8.Future>.value(<_i9.Transaction>[]), + ) as _i8.Future>); @override - _i7.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: _i7.Future>.value(<_i3.UtxoObject>[]), - ) as _i7.Future>); + _i8.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i8.Future>.value(<_i9.UTXO>[]), + ) as _i8.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -236,29 +189,74 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: '', ) as String); @override - _i7.Future> get mnemonic => (super.noSuchMethod( + _i8.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); + @override + _i8.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i8.Future.value(), + ) as _i8.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i8.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i8.Future.value(''), + ) as _i8.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i7.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i8.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -268,9 +266,9 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - _i7.Future> prepareSend({ + _i8.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i5.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -279,50 +277,32 @@ class MockManager extends _i1.Mock implements _i5.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i7.Future>.value({}), - ) as _i7.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future confirmSend({required Map? txData}) => + _i8.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); - @override - _i7.Future refresh() => (super.noSuchMethod( + _i8.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -332,34 +312,35 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: false, ) as bool); @override - _i7.Future testNetworkConnection() => (super.noSuchMethod( + _i8.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future initializeNew() => (super.noSuchMethod( + _i8.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future initializeExisting() => (super.noSuchMethod( + _i8.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future recoverFromMnemonic({ + _i8.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -370,25 +351,26 @@ class MockManager extends _i1.Mock implements _i5.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future exitCurrentWallet() => (super.noSuchMethod( + _i8.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future fullRescan( + _i8.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -400,42 +382,52 @@ class MockManager extends _i1.Mock implements _i5.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); - @override - _i7.Future estimateFeeFor( - int? satoshiAmount, + _i8.Future<_i5.Amount> estimateFeeFor( + _i5.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future<_i5.Amount>.value(_FakeAmount_3( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i8.Future<_i5.Amount>); @override - _i7.Future generateNewAddress() => (super.noSuchMethod( + _i8.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + _i8.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -443,7 +435,7 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/screen_tests/settings_view/settings_subviews/wallet_settings_subviews/change_pin_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/wallet_settings_subviews/change_pin_view_screen_test.mocks.dart index 280d08e09..799feb0cb 100644 --- a/test/screen_tests/settings_view/settings_subviews/wallet_settings_subviews/change_pin_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_subviews/wallet_settings_subviews/change_pin_view_screen_test.mocks.dart @@ -56,6 +56,14 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { returnValue: _i3.Future.value(false), ) as _i3.Future); @override + Map fetchWalletsData() => (super.noSuchMethod( + Invocation.method( + #fetchWalletsData, + [], + ), + returnValue: {}, + ) as Map); + @override _i3.Future addExistingStackWallet({ required String? name, required String? walletId, diff --git a/test/screen_tests/settings_view/settings_subviews/wallet_settings_subviews/rename_wallet_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/wallet_settings_subviews/rename_wallet_view_screen_test.mocks.dart index 12e07fd2f..b11c9ad89 100644 --- a/test/screen_tests/settings_view/settings_subviews/wallet_settings_subviews/rename_wallet_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_subviews/wallet_settings_subviews/rename_wallet_view_screen_test.mocks.dart @@ -56,6 +56,14 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { returnValue: _i3.Future.value(false), ) as _i3.Future); @override + Map fetchWalletsData() => (super.noSuchMethod( + Invocation.method( + #fetchWalletsData, + [], + ), + returnValue: {}, + ) as Map); + @override _i3.Future addExistingStackWallet({ required String? name, required String? walletId, diff --git a/test/screen_tests/settings_view/settings_subviews/wallet_settings_subviews/rescan_warning_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/wallet_settings_subviews/rescan_warning_view_screen_test.mocks.dart index 67a0b598b..63bbf9990 100644 --- a/test/screen_tests/settings_view/settings_subviews/wallet_settings_subviews/rescan_warning_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_subviews/wallet_settings_subviews/rescan_warning_view_screen_test.mocks.dart @@ -3,15 +3,17 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i7; -import 'dart:ui' as _i8; +import 'dart:async' as _i8; +import 'dart:ui' as _i10; -import 'package:decimal/decimal.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/models/balance.dart' as _i4; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i9; import 'package:stackwallet/models/models.dart' as _i3; import 'package:stackwallet/services/coins/coin_service.dart' as _i2; -import 'package:stackwallet/services/coins/manager.dart' as _i5; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i6; +import 'package:stackwallet/services/coins/manager.dart' as _i6; +import 'package:stackwallet/utilities/amount/amount.dart' as _i5; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i7; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -45,8 +47,8 @@ class _FakeFeeObject_1 extends _i1.SmartFake implements _i3.FeeObject { ); } -class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { - _FakeDecimal_2( +class _FakeBalance_2 extends _i1.SmartFake implements _i4.Balance { + _FakeBalance_2( Object parent, Invocation parentInvocation, ) : super( @@ -55,9 +57,8 @@ class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { ); } -class _FakeTransactionData_3 extends _i1.SmartFake - implements _i3.TransactionData { - _FakeTransactionData_3( +class _FakeAmount_3 extends _i1.SmartFake implements _i5.Amount { + _FakeAmount_3( Object parent, Invocation parentInvocation, ) : super( @@ -69,7 +70,7 @@ class _FakeTransactionData_3 extends _i1.SmartFake /// A class which mocks [Manager]. /// /// See the documentation for Mockito's code generation for more information. -class MockManager extends _i1.Mock implements _i5.Manager { +class MockManager extends _i1.Mock implements _i6.Manager { @override bool get isActiveWallet => (super.noSuchMethod( Invocation.getter(#isActiveWallet), @@ -97,10 +98,10 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: false, ) as bool); @override - _i6.Coin get coin => (super.noSuchMethod( + _i7.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i6.Coin.bitcoin, - ) as _i6.Coin); + returnValue: _i7.Coin.bitcoin, + ) as _i7.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -133,90 +134,42 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - _i7.Future<_i3.FeeObject> get fees => (super.noSuchMethod( + _i8.Future<_i3.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i7.Future<_i3.FeeObject>.value(_FakeFeeObject_1( + returnValue: _i8.Future<_i3.FeeObject>.value(_FakeFeeObject_1( this, Invocation.getter(#fees), )), - ) as _i7.Future<_i3.FeeObject>); + ) as _i8.Future<_i3.FeeObject>); @override - _i7.Future get maxFee => (super.noSuchMethod( + _i8.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future.value(0), + ) as _i8.Future); @override - _i7.Future get currentReceivingAddress => (super.noSuchMethod( + _i8.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future<_i4.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( + _i4.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_2( this, - Invocation.getter(#availableBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i4.Decimal); + ) as _i4.Balance); @override - _i7.Future<_i4.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i7.Future<_i4.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i7.Future<_i4.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#totalBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i4.Decimal); - @override - _i7.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); - @override - _i7.Future<_i3.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i8.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i7.Future<_i3.TransactionData>.value(_FakeTransactionData_3( - this, - Invocation.getter(#transactionData), - )), - ) as _i7.Future<_i3.TransactionData>); + _i8.Future>.value(<_i9.Transaction>[]), + ) as _i8.Future>); @override - _i7.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: _i7.Future>.value(<_i3.UtxoObject>[]), - ) as _i7.Future>); + _i8.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i8.Future>.value(<_i9.UTXO>[]), + ) as _i8.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -236,29 +189,74 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: '', ) as String); @override - _i7.Future> get mnemonic => (super.noSuchMethod( + _i8.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); + @override + _i8.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i8.Future.value(), + ) as _i8.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i8.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i8.Future.value(''), + ) as _i8.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i7.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i8.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -268,9 +266,9 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - _i7.Future> prepareSend({ + _i8.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i5.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -279,50 +277,32 @@ class MockManager extends _i1.Mock implements _i5.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i7.Future>.value({}), - ) as _i7.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future confirmSend({required Map? txData}) => + _i8.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); - @override - _i7.Future refresh() => (super.noSuchMethod( + _i8.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -332,34 +312,35 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: false, ) as bool); @override - _i7.Future testNetworkConnection() => (super.noSuchMethod( + _i8.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future initializeNew() => (super.noSuchMethod( + _i8.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future initializeExisting() => (super.noSuchMethod( + _i8.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future recoverFromMnemonic({ + _i8.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -370,25 +351,26 @@ class MockManager extends _i1.Mock implements _i5.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future exitCurrentWallet() => (super.noSuchMethod( + _i8.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future fullRescan( + _i8.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -400,42 +382,52 @@ class MockManager extends _i1.Mock implements _i5.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); - @override - _i7.Future estimateFeeFor( - int? satoshiAmount, + _i8.Future<_i5.Amount> estimateFeeFor( + _i5.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future<_i5.Amount>.value(_FakeAmount_3( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i8.Future<_i5.Amount>); @override - _i7.Future generateNewAddress() => (super.noSuchMethod( + _i8.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + _i8.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -443,7 +435,7 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/screen_tests/settings_view/settings_subviews/wallet_settings_subviews/wallet_delete_mnemonic_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/wallet_settings_subviews/wallet_delete_mnemonic_view_screen_test.mocks.dart index a036896f1..49ddcebd3 100644 --- a/test/screen_tests/settings_view/settings_subviews/wallet_settings_subviews/wallet_delete_mnemonic_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_subviews/wallet_settings_subviews/wallet_delete_mnemonic_view_screen_test.mocks.dart @@ -3,16 +3,18 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i6; -import 'dart:ui' as _i8; +import 'dart:async' as _i7; +import 'dart:ui' as _i9; -import 'package:decimal/decimal.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/models/balance.dart' as _i4; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i11; import 'package:stackwallet/models/models.dart' as _i3; import 'package:stackwallet/services/coins/coin_service.dart' as _i2; -import 'package:stackwallet/services/coins/manager.dart' as _i9; -import 'package:stackwallet/services/wallets_service.dart' as _i5; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i7; +import 'package:stackwallet/services/coins/manager.dart' as _i10; +import 'package:stackwallet/services/wallets_service.dart' as _i6; +import 'package:stackwallet/utilities/amount/amount.dart' as _i5; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i8; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -46,8 +48,8 @@ class _FakeFeeObject_1 extends _i1.SmartFake implements _i3.FeeObject { ); } -class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { - _FakeDecimal_2( +class _FakeBalance_2 extends _i1.SmartFake implements _i4.Balance { + _FakeBalance_2( Object parent, Invocation parentInvocation, ) : super( @@ -56,9 +58,8 @@ class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { ); } -class _FakeTransactionData_3 extends _i1.SmartFake - implements _i3.TransactionData { - _FakeTransactionData_3( +class _FakeAmount_3 extends _i1.SmartFake implements _i5.Amount { + _FakeAmount_3( Object parent, Invocation parentInvocation, ) : super( @@ -70,21 +71,21 @@ class _FakeTransactionData_3 extends _i1.SmartFake /// A class which mocks [WalletsService]. /// /// See the documentation for Mockito's code generation for more information. -class MockWalletsService extends _i1.Mock implements _i5.WalletsService { +class MockWalletsService extends _i1.Mock implements _i6.WalletsService { @override - _i6.Future> get walletNames => + _i7.Future> get walletNames => (super.noSuchMethod( Invocation.getter(#walletNames), - returnValue: _i6.Future>.value( - {}), - ) as _i6.Future>); + returnValue: _i7.Future>.value( + {}), + ) as _i7.Future>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i6.Future renameWallet({ + _i7.Future renameWallet({ required String? from, required String? to, required bool? shouldNotifyListeners, @@ -99,13 +100,21 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - _i6.Future addExistingStackWallet({ + Map fetchWalletsData() => (super.noSuchMethod( + Invocation.method( + #fetchWalletsData, + [], + ), + returnValue: {}, + ) as Map); + @override + _i7.Future addExistingStackWallet({ required String? name, required String? walletId, - required _i7.Coin? coin, + required _i8.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -119,13 +128,13 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future addNewWallet({ + _i7.Future addNewWallet({ required String? name, - required _i7.Coin? coin, + required _i8.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -138,46 +147,46 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future> getFavoriteWalletIds() => (super.noSuchMethod( + _i7.Future> getFavoriteWalletIds() => (super.noSuchMethod( Invocation.method( #getFavoriteWalletIds, [], ), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i7.Future>.value([]), + ) as _i7.Future>); @override - _i6.Future saveFavoriteWalletIds(List? walletIds) => + _i7.Future saveFavoriteWalletIds(List? walletIds) => (super.noSuchMethod( Invocation.method( #saveFavoriteWalletIds, [walletIds], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future addFavorite(String? walletId) => (super.noSuchMethod( + _i7.Future addFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #addFavorite, [walletId], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future removeFavorite(String? walletId) => (super.noSuchMethod( + _i7.Future removeFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #removeFavorite, [walletId], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future moveFavorite({ + _i7.Future moveFavorite({ required int? fromIndex, required int? toIndex, }) => @@ -190,48 +199,48 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { #toIndex: toIndex, }, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future checkForDuplicate(String? name) => (super.noSuchMethod( + _i7.Future checkForDuplicate(String? name) => (super.noSuchMethod( Invocation.method( #checkForDuplicate, [name], ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - _i6.Future getWalletId(String? walletName) => (super.noSuchMethod( + _i7.Future getWalletId(String? walletName) => (super.noSuchMethod( Invocation.method( #getWalletId, [walletName], ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future isMnemonicVerified({required String? walletId}) => + _i7.Future isMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #isMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - _i6.Future setMnemonicVerified({required String? walletId}) => + _i7.Future setMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #setMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future deleteWallet( + _i7.Future deleteWallet( String? name, bool? shouldNotifyListeners, ) => @@ -243,20 +252,20 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { shouldNotifyListeners, ], ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i7.Future.value(0), + ) as _i7.Future); @override - _i6.Future refreshWallets(bool? shouldNotifyListeners) => + _i7.Future refreshWallets(bool? shouldNotifyListeners) => (super.noSuchMethod( Invocation.method( #refreshWallets, [shouldNotifyListeners], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -264,7 +273,7 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -292,7 +301,7 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { /// A class which mocks [Manager]. /// /// See the documentation for Mockito's code generation for more information. -class MockManager extends _i1.Mock implements _i9.Manager { +class MockManager extends _i1.Mock implements _i10.Manager { @override bool get isActiveWallet => (super.noSuchMethod( Invocation.getter(#isActiveWallet), @@ -320,10 +329,10 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValue: false, ) as bool); @override - _i7.Coin get coin => (super.noSuchMethod( + _i8.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i7.Coin.bitcoin, - ) as _i7.Coin); + returnValue: _i8.Coin.bitcoin, + ) as _i8.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -356,90 +365,42 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValueForMissingStub: null, ); @override - _i6.Future<_i3.FeeObject> get fees => (super.noSuchMethod( + _i7.Future<_i3.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i6.Future<_i3.FeeObject>.value(_FakeFeeObject_1( + returnValue: _i7.Future<_i3.FeeObject>.value(_FakeFeeObject_1( this, Invocation.getter(#fees), )), - ) as _i6.Future<_i3.FeeObject>); + ) as _i7.Future<_i3.FeeObject>); @override - _i6.Future get maxFee => (super.noSuchMethod( + _i7.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i7.Future.value(0), + ) as _i7.Future); @override - _i6.Future get currentReceivingAddress => (super.noSuchMethod( + _i7.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i6.Future.value(''), - ) as _i6.Future); + returnValue: _i7.Future.value(''), + ) as _i7.Future); @override - _i6.Future<_i4.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i6.Future<_i4.Decimal>.value(_FakeDecimal_2( + _i4.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_2( this, - Invocation.getter(#availableBalance), - )), - ) as _i6.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i4.Decimal); + ) as _i4.Balance); @override - _i6.Future<_i4.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i6.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i6.Future<_i4.Decimal>); - @override - _i6.Future<_i4.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i6.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i6.Future<_i4.Decimal>); - @override - _i6.Future<_i4.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i6.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#totalBalance), - )), - ) as _i6.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i4.Decimal); - @override - _i6.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); - @override - _i6.Future<_i3.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i7.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i6.Future<_i3.TransactionData>.value(_FakeTransactionData_3( - this, - Invocation.getter(#transactionData), - )), - ) as _i6.Future<_i3.TransactionData>); + _i7.Future>.value(<_i11.Transaction>[]), + ) as _i7.Future>); @override - _i6.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: _i6.Future>.value(<_i3.UtxoObject>[]), - ) as _i6.Future>); + _i7.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i7.Future>.value(<_i11.UTXO>[]), + ) as _i7.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -459,29 +420,74 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValue: '', ) as String); @override - _i6.Future> get mnemonic => (super.noSuchMethod( + _i7.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i7.Future>.value([]), + ) as _i7.Future>); + @override + _i7.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i7.Future.value(), + ) as _i7.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i7.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i7.Future.value(''), + ) as _i7.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i6.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i7.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -491,9 +497,9 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValueForMissingStub: null, ); @override - _i6.Future> prepareSend({ + _i7.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i5.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -502,50 +508,32 @@ class MockManager extends _i1.Mock implements _i9.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i7.Future>.value({}), + ) as _i7.Future>); @override - _i6.Future confirmSend({required Map? txData}) => + _i7.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i6.Future.value(''), - ) as _i6.Future); + returnValue: _i7.Future.value(''), + ) as _i7.Future); @override - _i6.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i6.Future.value(''), - ) as _i6.Future); - @override - _i6.Future refresh() => (super.noSuchMethod( + _i7.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -555,34 +543,35 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValue: false, ) as bool); @override - _i6.Future testNetworkConnection() => (super.noSuchMethod( + _i7.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - _i6.Future initializeNew() => (super.noSuchMethod( + _i7.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future initializeExisting() => (super.noSuchMethod( + _i7.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future recoverFromMnemonic({ + _i7.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -593,25 +582,26 @@ class MockManager extends _i1.Mock implements _i9.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future exitCurrentWallet() => (super.noSuchMethod( + _i7.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future fullRescan( + _i7.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -623,42 +613,52 @@ class MockManager extends _i1.Mock implements _i9.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); - @override - _i6.Future estimateFeeFor( - int? satoshiAmount, + _i7.Future<_i5.Amount> estimateFeeFor( + _i5.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i7.Future<_i5.Amount>.value(_FakeAmount_3( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i7.Future<_i5.Amount>); @override - _i6.Future generateNewAddress() => (super.noSuchMethod( + _i7.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + _i7.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -666,7 +666,7 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/screen_tests/settings_view/settings_subviews/wallet_settings_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_subviews/wallet_settings_view_screen_test.mocks.dart index 7eb0853dd..b63e29f34 100644 --- a/test/screen_tests/settings_view/settings_subviews/wallet_settings_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_subviews/wallet_settings_view_screen_test.mocks.dart @@ -3,21 +3,23 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i8; -import 'dart:ui' as _i14; +import 'dart:async' as _i9; +import 'dart:ui' as _i15; -import 'package:decimal/decimal.dart' as _i5; -import 'package:local_auth/auth_strings.dart' as _i11; -import 'package:local_auth/local_auth.dart' as _i10; +import 'package:local_auth/auth_strings.dart' as _i12; +import 'package:local_auth/local_auth.dart' as _i11; import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i6; -import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i7; +import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i7; +import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i8; +import 'package:stackwallet/models/balance.dart' as _i5; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i17; import 'package:stackwallet/models/models.dart' as _i4; import 'package:stackwallet/services/coins/coin_service.dart' as _i3; -import 'package:stackwallet/services/coins/manager.dart' as _i15; -import 'package:stackwallet/services/wallets_service.dart' as _i13; -import 'package:stackwallet/utilities/biometrics.dart' as _i12; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i9; +import 'package:stackwallet/services/coins/manager.dart' as _i16; +import 'package:stackwallet/services/wallets_service.dart' as _i14; +import 'package:stackwallet/utilities/amount/amount.dart' as _i6; +import 'package:stackwallet/utilities/biometrics.dart' as _i13; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i10; import 'package:stackwallet/utilities/prefs.dart' as _i2; // ignore_for_file: type=lint @@ -62,8 +64,8 @@ class _FakeFeeObject_2 extends _i1.SmartFake implements _i4.FeeObject { ); } -class _FakeDecimal_3 extends _i1.SmartFake implements _i5.Decimal { - _FakeDecimal_3( +class _FakeBalance_3 extends _i1.SmartFake implements _i5.Balance { + _FakeBalance_3( Object parent, Invocation parentInvocation, ) : super( @@ -72,9 +74,8 @@ class _FakeDecimal_3 extends _i1.SmartFake implements _i5.Decimal { ); } -class _FakeTransactionData_4 extends _i1.SmartFake - implements _i4.TransactionData { - _FakeTransactionData_4( +class _FakeAmount_4 extends _i1.SmartFake implements _i6.Amount { + _FakeAmount_4( Object parent, Invocation parentInvocation, ) : super( @@ -86,7 +87,7 @@ class _FakeTransactionData_4 extends _i1.SmartFake /// A class which mocks [CachedElectrumX]. /// /// See the documentation for Mockito's code generation for more information. -class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { +class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { MockCachedElectrumX() { _i1.throwOnMissingStub(this); } @@ -115,15 +116,15 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { ), ) as _i2.Prefs); @override - List<_i7.ElectrumXNode> get failovers => (super.noSuchMethod( + List<_i8.ElectrumXNode> get failovers => (super.noSuchMethod( Invocation.getter(#failovers), - returnValue: <_i7.ElectrumXNode>[], - ) as List<_i7.ElectrumXNode>); + returnValue: <_i8.ElectrumXNode>[], + ) as List<_i8.ElectrumXNode>); @override - _i8.Future> getAnonymitySet({ + _i9.Future> getAnonymitySet({ required String? groupId, String? blockhash = r'', - required _i9.Coin? coin, + required _i10.Coin? coin, }) => (super.noSuchMethod( Invocation.method( @@ -136,8 +137,8 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { }, ), returnValue: - _i8.Future>.value({}), - ) as _i8.Future>); + _i9.Future>.value({}), + ) as _i9.Future>); @override String base64ToHex(String? source) => (super.noSuchMethod( Invocation.method( @@ -155,9 +156,9 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { returnValue: '', ) as String); @override - _i8.Future> getTransaction({ + _i9.Future> getTransaction({ required String? txHash, - required _i9.Coin? coin, + required _i10.Coin? coin, bool? verbose = true, }) => (super.noSuchMethod( @@ -171,11 +172,11 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { }, ), returnValue: - _i8.Future>.value({}), - ) as _i8.Future>); + _i9.Future>.value({}), + ) as _i9.Future>); @override - _i8.Future> getUsedCoinSerials({ - required _i9.Coin? coin, + _i9.Future> getUsedCoinSerials({ + required _i10.Coin? coin, int? startNumber = 0, }) => (super.noSuchMethod( @@ -187,43 +188,43 @@ class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { #startNumber: startNumber, }, ), - returnValue: _i8.Future>.value([]), - ) as _i8.Future>); + returnValue: _i9.Future>.value([]), + ) as _i9.Future>); @override - _i8.Future clearSharedTransactionCache({required _i9.Coin? coin}) => + _i9.Future clearSharedTransactionCache({required _i10.Coin? coin}) => (super.noSuchMethod( Invocation.method( #clearSharedTransactionCache, [], {#coin: coin}, ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); } /// A class which mocks [LocalAuthentication]. /// /// See the documentation for Mockito's code generation for more information. class MockLocalAuthentication extends _i1.Mock - implements _i10.LocalAuthentication { + implements _i11.LocalAuthentication { MockLocalAuthentication() { _i1.throwOnMissingStub(this); } @override - _i8.Future get canCheckBiometrics => (super.noSuchMethod( + _i9.Future get canCheckBiometrics => (super.noSuchMethod( Invocation.getter(#canCheckBiometrics), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); @override - _i8.Future authenticateWithBiometrics({ + _i9.Future authenticateWithBiometrics({ required String? localizedReason, bool? useErrorDialogs = true, bool? stickyAuth = false, - _i11.AndroidAuthMessages? androidAuthStrings = - const _i11.AndroidAuthMessages(), - _i11.IOSAuthMessages? iOSAuthStrings = const _i11.IOSAuthMessages(), + _i12.AndroidAuthMessages? androidAuthStrings = + const _i12.AndroidAuthMessages(), + _i12.IOSAuthMessages? iOSAuthStrings = const _i12.IOSAuthMessages(), bool? sensitiveTransaction = true, }) => (super.noSuchMethod( @@ -239,16 +240,16 @@ class MockLocalAuthentication extends _i1.Mock #sensitiveTransaction: sensitiveTransaction, }, ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); @override - _i8.Future authenticate({ + _i9.Future authenticate({ required String? localizedReason, bool? useErrorDialogs = true, bool? stickyAuth = false, - _i11.AndroidAuthMessages? androidAuthStrings = - const _i11.AndroidAuthMessages(), - _i11.IOSAuthMessages? iOSAuthStrings = const _i11.IOSAuthMessages(), + _i12.AndroidAuthMessages? androidAuthStrings = + const _i12.AndroidAuthMessages(), + _i12.IOSAuthMessages? iOSAuthStrings = const _i12.IOSAuthMessages(), bool? sensitiveTransaction = true, bool? biometricOnly = false, }) => @@ -266,46 +267,46 @@ class MockLocalAuthentication extends _i1.Mock #biometricOnly: biometricOnly, }, ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); @override - _i8.Future stopAuthentication() => (super.noSuchMethod( + _i9.Future stopAuthentication() => (super.noSuchMethod( Invocation.method( #stopAuthentication, [], ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); @override - _i8.Future isDeviceSupported() => (super.noSuchMethod( + _i9.Future isDeviceSupported() => (super.noSuchMethod( Invocation.method( #isDeviceSupported, [], ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); @override - _i8.Future> getAvailableBiometrics() => + _i9.Future> getAvailableBiometrics() => (super.noSuchMethod( Invocation.method( #getAvailableBiometrics, [], ), returnValue: - _i8.Future>.value(<_i10.BiometricType>[]), - ) as _i8.Future>); + _i9.Future>.value(<_i11.BiometricType>[]), + ) as _i9.Future>); } /// A class which mocks [Biometrics]. /// /// See the documentation for Mockito's code generation for more information. -class MockBiometrics extends _i1.Mock implements _i12.Biometrics { +class MockBiometrics extends _i1.Mock implements _i13.Biometrics { MockBiometrics() { _i1.throwOnMissingStub(this); } @override - _i8.Future authenticate({ + _i9.Future authenticate({ required String? cancelButtonText, required String? localizedReason, required String? title, @@ -320,28 +321,28 @@ class MockBiometrics extends _i1.Mock implements _i12.Biometrics { #title: title, }, ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); } /// A class which mocks [WalletsService]. /// /// See the documentation for Mockito's code generation for more information. -class MockWalletsService extends _i1.Mock implements _i13.WalletsService { +class MockWalletsService extends _i1.Mock implements _i14.WalletsService { @override - _i8.Future> get walletNames => + _i9.Future> get walletNames => (super.noSuchMethod( Invocation.getter(#walletNames), - returnValue: _i8.Future>.value( - {}), - ) as _i8.Future>); + returnValue: _i9.Future>.value( + {}), + ) as _i9.Future>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i8.Future renameWallet({ + _i9.Future renameWallet({ required String? from, required String? to, required bool? shouldNotifyListeners, @@ -356,13 +357,21 @@ class MockWalletsService extends _i1.Mock implements _i13.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); @override - _i8.Future addExistingStackWallet({ + Map fetchWalletsData() => (super.noSuchMethod( + Invocation.method( + #fetchWalletsData, + [], + ), + returnValue: {}, + ) as Map); + @override + _i9.Future addExistingStackWallet({ required String? name, required String? walletId, - required _i9.Coin? coin, + required _i10.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -376,13 +385,13 @@ class MockWalletsService extends _i1.Mock implements _i13.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future addNewWallet({ + _i9.Future addNewWallet({ required String? name, - required _i9.Coin? coin, + required _i10.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -395,46 +404,46 @@ class MockWalletsService extends _i1.Mock implements _i13.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future> getFavoriteWalletIds() => (super.noSuchMethod( + _i9.Future> getFavoriteWalletIds() => (super.noSuchMethod( Invocation.method( #getFavoriteWalletIds, [], ), - returnValue: _i8.Future>.value([]), - ) as _i8.Future>); + returnValue: _i9.Future>.value([]), + ) as _i9.Future>); @override - _i8.Future saveFavoriteWalletIds(List? walletIds) => + _i9.Future saveFavoriteWalletIds(List? walletIds) => (super.noSuchMethod( Invocation.method( #saveFavoriteWalletIds, [walletIds], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future addFavorite(String? walletId) => (super.noSuchMethod( + _i9.Future addFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #addFavorite, [walletId], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future removeFavorite(String? walletId) => (super.noSuchMethod( + _i9.Future removeFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #removeFavorite, [walletId], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future moveFavorite({ + _i9.Future moveFavorite({ required int? fromIndex, required int? toIndex, }) => @@ -447,48 +456,48 @@ class MockWalletsService extends _i1.Mock implements _i13.WalletsService { #toIndex: toIndex, }, ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future checkForDuplicate(String? name) => (super.noSuchMethod( + _i9.Future checkForDuplicate(String? name) => (super.noSuchMethod( Invocation.method( #checkForDuplicate, [name], ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); @override - _i8.Future getWalletId(String? walletName) => (super.noSuchMethod( + _i9.Future getWalletId(String? walletName) => (super.noSuchMethod( Invocation.method( #getWalletId, [walletName], ), - returnValue: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future isMnemonicVerified({required String? walletId}) => + _i9.Future isMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #isMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); @override - _i8.Future setMnemonicVerified({required String? walletId}) => + _i9.Future setMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #setMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future deleteWallet( + _i9.Future deleteWallet( String? name, bool? shouldNotifyListeners, ) => @@ -500,20 +509,20 @@ class MockWalletsService extends _i1.Mock implements _i13.WalletsService { shouldNotifyListeners, ], ), - returnValue: _i8.Future.value(0), - ) as _i8.Future); + returnValue: _i9.Future.value(0), + ) as _i9.Future); @override - _i8.Future refreshWallets(bool? shouldNotifyListeners) => + _i9.Future refreshWallets(bool? shouldNotifyListeners) => (super.noSuchMethod( Invocation.method( #refreshWallets, [shouldNotifyListeners], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - void addListener(_i14.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i15.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -521,7 +530,7 @@ class MockWalletsService extends _i1.Mock implements _i13.WalletsService { returnValueForMissingStub: null, ); @override - void removeListener(_i14.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i15.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -549,7 +558,7 @@ class MockWalletsService extends _i1.Mock implements _i13.WalletsService { /// A class which mocks [Manager]. /// /// See the documentation for Mockito's code generation for more information. -class MockManager extends _i1.Mock implements _i15.Manager { +class MockManager extends _i1.Mock implements _i16.Manager { @override bool get isActiveWallet => (super.noSuchMethod( Invocation.getter(#isActiveWallet), @@ -577,10 +586,10 @@ class MockManager extends _i1.Mock implements _i15.Manager { returnValue: false, ) as bool); @override - _i9.Coin get coin => (super.noSuchMethod( + _i10.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i9.Coin.bitcoin, - ) as _i9.Coin); + returnValue: _i10.Coin.bitcoin, + ) as _i10.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -613,90 +622,42 @@ class MockManager extends _i1.Mock implements _i15.Manager { returnValueForMissingStub: null, ); @override - _i8.Future<_i4.FeeObject> get fees => (super.noSuchMethod( + _i9.Future<_i4.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i8.Future<_i4.FeeObject>.value(_FakeFeeObject_2( + returnValue: _i9.Future<_i4.FeeObject>.value(_FakeFeeObject_2( this, Invocation.getter(#fees), )), - ) as _i8.Future<_i4.FeeObject>); + ) as _i9.Future<_i4.FeeObject>); @override - _i8.Future get maxFee => (super.noSuchMethod( + _i9.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i8.Future.value(0), - ) as _i8.Future); + returnValue: _i9.Future.value(0), + ) as _i9.Future); @override - _i8.Future get currentReceivingAddress => (super.noSuchMethod( + _i9.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i8.Future.value(''), - ) as _i8.Future); + returnValue: _i9.Future.value(''), + ) as _i9.Future); @override - _i8.Future<_i5.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i8.Future<_i5.Decimal>.value(_FakeDecimal_3( + _i5.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_3( this, - Invocation.getter(#availableBalance), - )), - ) as _i8.Future<_i5.Decimal>); - @override - _i5.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_3( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i5.Decimal); + ) as _i5.Balance); @override - _i8.Future<_i5.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i8.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i8.Future<_i5.Decimal>); - @override - _i8.Future<_i5.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i8.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i8.Future<_i5.Decimal>); - @override - _i8.Future<_i5.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i8.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#totalBalance), - )), - ) as _i8.Future<_i5.Decimal>); - @override - _i5.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_3( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i5.Decimal); - @override - _i8.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i8.Future>.value([]), - ) as _i8.Future>); - @override - _i8.Future<_i4.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i9.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i8.Future<_i4.TransactionData>.value(_FakeTransactionData_4( - this, - Invocation.getter(#transactionData), - )), - ) as _i8.Future<_i4.TransactionData>); + _i9.Future>.value(<_i17.Transaction>[]), + ) as _i9.Future>); @override - _i8.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: _i8.Future>.value(<_i4.UtxoObject>[]), - ) as _i8.Future>); + _i9.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i9.Future>.value(<_i17.UTXO>[]), + ) as _i9.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -716,29 +677,74 @@ class MockManager extends _i1.Mock implements _i15.Manager { returnValue: '', ) as String); @override - _i8.Future> get mnemonic => (super.noSuchMethod( + _i9.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i8.Future>.value([]), - ) as _i8.Future>); + returnValue: _i9.Future>.value([]), + ) as _i9.Future>); + @override + _i9.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i9.Future.value(), + ) as _i9.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i9.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i9.Future.value(''), + ) as _i9.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i8.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i9.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -748,9 +754,9 @@ class MockManager extends _i1.Mock implements _i15.Manager { returnValueForMissingStub: null, ); @override - _i8.Future> prepareSend({ + _i9.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i6.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -759,50 +765,32 @@ class MockManager extends _i1.Mock implements _i15.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i8.Future>.value({}), - ) as _i8.Future>); + _i9.Future>.value({}), + ) as _i9.Future>); @override - _i8.Future confirmSend({required Map? txData}) => + _i9.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i8.Future.value(''), - ) as _i8.Future); + returnValue: _i9.Future.value(''), + ) as _i9.Future); @override - _i8.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i8.Future.value(''), - ) as _i8.Future); - @override - _i8.Future refresh() => (super.noSuchMethod( + _i9.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -812,34 +800,35 @@ class MockManager extends _i1.Mock implements _i15.Manager { returnValue: false, ) as bool); @override - _i8.Future testNetworkConnection() => (super.noSuchMethod( + _i9.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); @override - _i8.Future initializeNew() => (super.noSuchMethod( + _i9.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future initializeExisting() => (super.noSuchMethod( + _i9.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future recoverFromMnemonic({ + _i9.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -850,25 +839,26 @@ class MockManager extends _i1.Mock implements _i15.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future exitCurrentWallet() => (super.noSuchMethod( + _i9.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future fullRescan( + _i9.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -880,42 +870,52 @@ class MockManager extends _i1.Mock implements _i15.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); @override - _i8.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); - @override - _i8.Future estimateFeeFor( - int? satoshiAmount, + _i9.Future<_i6.Amount> estimateFeeFor( + _i6.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i8.Future.value(0), - ) as _i8.Future); + returnValue: _i9.Future<_i6.Amount>.value(_FakeAmount_4( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i9.Future<_i6.Amount>); @override - _i8.Future generateNewAddress() => (super.noSuchMethod( + _i9.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i9.Future.value(false), + ) as _i9.Future); @override - void addListener(_i14.VoidCallback? listener) => super.noSuchMethod( + _i9.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i9.Future.value(), + returnValueForMissingStub: _i9.Future.value(), + ) as _i9.Future); + @override + void addListener(_i15.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -923,7 +923,7 @@ class MockManager extends _i1.Mock implements _i15.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i14.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i15.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/screen_tests/settings_view/settings_view_screen_test.mocks.dart b/test/screen_tests/settings_view/settings_view_screen_test.mocks.dart index ddfdcc387..d918b1684 100644 --- a/test/screen_tests/settings_view/settings_view_screen_test.mocks.dart +++ b/test/screen_tests/settings_view/settings_view_screen_test.mocks.dart @@ -3,16 +3,18 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i6; -import 'dart:ui' as _i8; +import 'dart:async' as _i7; +import 'dart:ui' as _i9; -import 'package:decimal/decimal.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/models/balance.dart' as _i4; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i11; import 'package:stackwallet/models/models.dart' as _i3; import 'package:stackwallet/services/coins/coin_service.dart' as _i2; -import 'package:stackwallet/services/coins/manager.dart' as _i9; -import 'package:stackwallet/services/wallets_service.dart' as _i5; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i7; +import 'package:stackwallet/services/coins/manager.dart' as _i10; +import 'package:stackwallet/services/wallets_service.dart' as _i6; +import 'package:stackwallet/utilities/amount/amount.dart' as _i5; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i8; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -46,8 +48,8 @@ class _FakeFeeObject_1 extends _i1.SmartFake implements _i3.FeeObject { ); } -class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { - _FakeDecimal_2( +class _FakeBalance_2 extends _i1.SmartFake implements _i4.Balance { + _FakeBalance_2( Object parent, Invocation parentInvocation, ) : super( @@ -56,9 +58,8 @@ class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { ); } -class _FakeTransactionData_3 extends _i1.SmartFake - implements _i3.TransactionData { - _FakeTransactionData_3( +class _FakeAmount_3 extends _i1.SmartFake implements _i5.Amount { + _FakeAmount_3( Object parent, Invocation parentInvocation, ) : super( @@ -70,21 +71,21 @@ class _FakeTransactionData_3 extends _i1.SmartFake /// A class which mocks [WalletsService]. /// /// See the documentation for Mockito's code generation for more information. -class MockWalletsService extends _i1.Mock implements _i5.WalletsService { +class MockWalletsService extends _i1.Mock implements _i6.WalletsService { @override - _i6.Future> get walletNames => + _i7.Future> get walletNames => (super.noSuchMethod( Invocation.getter(#walletNames), - returnValue: _i6.Future>.value( - {}), - ) as _i6.Future>); + returnValue: _i7.Future>.value( + {}), + ) as _i7.Future>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i6.Future renameWallet({ + _i7.Future renameWallet({ required String? from, required String? to, required bool? shouldNotifyListeners, @@ -99,13 +100,21 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - _i6.Future addExistingStackWallet({ + Map fetchWalletsData() => (super.noSuchMethod( + Invocation.method( + #fetchWalletsData, + [], + ), + returnValue: {}, + ) as Map); + @override + _i7.Future addExistingStackWallet({ required String? name, required String? walletId, - required _i7.Coin? coin, + required _i8.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -119,13 +128,13 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future addNewWallet({ + _i7.Future addNewWallet({ required String? name, - required _i7.Coin? coin, + required _i8.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -138,46 +147,46 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future> getFavoriteWalletIds() => (super.noSuchMethod( + _i7.Future> getFavoriteWalletIds() => (super.noSuchMethod( Invocation.method( #getFavoriteWalletIds, [], ), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i7.Future>.value([]), + ) as _i7.Future>); @override - _i6.Future saveFavoriteWalletIds(List? walletIds) => + _i7.Future saveFavoriteWalletIds(List? walletIds) => (super.noSuchMethod( Invocation.method( #saveFavoriteWalletIds, [walletIds], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future addFavorite(String? walletId) => (super.noSuchMethod( + _i7.Future addFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #addFavorite, [walletId], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future removeFavorite(String? walletId) => (super.noSuchMethod( + _i7.Future removeFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #removeFavorite, [walletId], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future moveFavorite({ + _i7.Future moveFavorite({ required int? fromIndex, required int? toIndex, }) => @@ -190,48 +199,48 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { #toIndex: toIndex, }, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future checkForDuplicate(String? name) => (super.noSuchMethod( + _i7.Future checkForDuplicate(String? name) => (super.noSuchMethod( Invocation.method( #checkForDuplicate, [name], ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - _i6.Future getWalletId(String? walletName) => (super.noSuchMethod( + _i7.Future getWalletId(String? walletName) => (super.noSuchMethod( Invocation.method( #getWalletId, [walletName], ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future isMnemonicVerified({required String? walletId}) => + _i7.Future isMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #isMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - _i6.Future setMnemonicVerified({required String? walletId}) => + _i7.Future setMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #setMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future deleteWallet( + _i7.Future deleteWallet( String? name, bool? shouldNotifyListeners, ) => @@ -243,20 +252,20 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { shouldNotifyListeners, ], ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i7.Future.value(0), + ) as _i7.Future); @override - _i6.Future refreshWallets(bool? shouldNotifyListeners) => + _i7.Future refreshWallets(bool? shouldNotifyListeners) => (super.noSuchMethod( Invocation.method( #refreshWallets, [shouldNotifyListeners], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -264,7 +273,7 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -292,7 +301,7 @@ class MockWalletsService extends _i1.Mock implements _i5.WalletsService { /// A class which mocks [Manager]. /// /// See the documentation for Mockito's code generation for more information. -class MockManager extends _i1.Mock implements _i9.Manager { +class MockManager extends _i1.Mock implements _i10.Manager { @override bool get isActiveWallet => (super.noSuchMethod( Invocation.getter(#isActiveWallet), @@ -320,10 +329,10 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValue: false, ) as bool); @override - _i7.Coin get coin => (super.noSuchMethod( + _i8.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i7.Coin.bitcoin, - ) as _i7.Coin); + returnValue: _i8.Coin.bitcoin, + ) as _i8.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -356,90 +365,42 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValueForMissingStub: null, ); @override - _i6.Future<_i3.FeeObject> get fees => (super.noSuchMethod( + _i7.Future<_i3.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i6.Future<_i3.FeeObject>.value(_FakeFeeObject_1( + returnValue: _i7.Future<_i3.FeeObject>.value(_FakeFeeObject_1( this, Invocation.getter(#fees), )), - ) as _i6.Future<_i3.FeeObject>); + ) as _i7.Future<_i3.FeeObject>); @override - _i6.Future get maxFee => (super.noSuchMethod( + _i7.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i7.Future.value(0), + ) as _i7.Future); @override - _i6.Future get currentReceivingAddress => (super.noSuchMethod( + _i7.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i6.Future.value(''), - ) as _i6.Future); + returnValue: _i7.Future.value(''), + ) as _i7.Future); @override - _i6.Future<_i4.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i6.Future<_i4.Decimal>.value(_FakeDecimal_2( + _i4.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_2( this, - Invocation.getter(#availableBalance), - )), - ) as _i6.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i4.Decimal); + ) as _i4.Balance); @override - _i6.Future<_i4.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i6.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i6.Future<_i4.Decimal>); - @override - _i6.Future<_i4.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i6.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i6.Future<_i4.Decimal>); - @override - _i6.Future<_i4.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i6.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#totalBalance), - )), - ) as _i6.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i4.Decimal); - @override - _i6.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); - @override - _i6.Future<_i3.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i7.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i6.Future<_i3.TransactionData>.value(_FakeTransactionData_3( - this, - Invocation.getter(#transactionData), - )), - ) as _i6.Future<_i3.TransactionData>); + _i7.Future>.value(<_i11.Transaction>[]), + ) as _i7.Future>); @override - _i6.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: _i6.Future>.value(<_i3.UtxoObject>[]), - ) as _i6.Future>); + _i7.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i7.Future>.value(<_i11.UTXO>[]), + ) as _i7.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -459,29 +420,74 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValue: '', ) as String); @override - _i6.Future> get mnemonic => (super.noSuchMethod( + _i7.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i7.Future>.value([]), + ) as _i7.Future>); + @override + _i7.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i7.Future.value(), + ) as _i7.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i7.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i7.Future.value(''), + ) as _i7.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i6.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i7.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -491,9 +497,9 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValueForMissingStub: null, ); @override - _i6.Future> prepareSend({ + _i7.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i5.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -502,50 +508,32 @@ class MockManager extends _i1.Mock implements _i9.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i7.Future>.value({}), + ) as _i7.Future>); @override - _i6.Future confirmSend({required Map? txData}) => + _i7.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i6.Future.value(''), - ) as _i6.Future); + returnValue: _i7.Future.value(''), + ) as _i7.Future); @override - _i6.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i6.Future.value(''), - ) as _i6.Future); - @override - _i6.Future refresh() => (super.noSuchMethod( + _i7.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -555,34 +543,35 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValue: false, ) as bool); @override - _i6.Future testNetworkConnection() => (super.noSuchMethod( + _i7.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - _i6.Future initializeNew() => (super.noSuchMethod( + _i7.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future initializeExisting() => (super.noSuchMethod( + _i7.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future recoverFromMnemonic({ + _i7.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -593,25 +582,26 @@ class MockManager extends _i1.Mock implements _i9.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future exitCurrentWallet() => (super.noSuchMethod( + _i7.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future fullRescan( + _i7.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -623,42 +613,52 @@ class MockManager extends _i1.Mock implements _i9.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); @override - _i6.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); - @override - _i6.Future estimateFeeFor( - int? satoshiAmount, + _i7.Future<_i5.Amount> estimateFeeFor( + _i5.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i7.Future<_i5.Amount>.value(_FakeAmount_3( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i7.Future<_i5.Amount>); @override - _i6.Future generateNewAddress() => (super.noSuchMethod( + _i7.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i7.Future.value(false), + ) as _i7.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + _i7.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i7.Future.value(), + returnValueForMissingStub: _i7.Future.value(), + ) as _i7.Future); + @override + void addListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -666,7 +666,7 @@ class MockManager extends _i1.Mock implements _i9.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i9.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/screen_tests/transaction_subviews/transaction_details_view_screen_test.mocks.dart b/test/screen_tests/transaction_subviews/transaction_details_view_screen_test.mocks.dart index 4d7191c2c..73941fed6 100644 --- a/test/screen_tests/transaction_subviews/transaction_details_view_screen_test.mocks.dart +++ b/test/screen_tests/transaction_subviews/transaction_details_view_screen_test.mocks.dart @@ -7,7 +7,7 @@ import 'dart:async' as _i4; import 'dart:ui' as _i5; import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/models/contact.dart' as _i2; +import 'package:stackwallet/models/isar/models/contact_entry.dart' as _i2; import 'package:stackwallet/services/address_book_service.dart' as _i6; import 'package:stackwallet/services/locale_service.dart' as _i7; import 'package:stackwallet/services/notes_service.dart' as _i3; @@ -23,8 +23,8 @@ import 'package:stackwallet/services/notes_service.dart' as _i3; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class -class _FakeContact_0 extends _i1.SmartFake implements _i2.Contact { - _FakeContact_0( +class _FakeContactEntry_0 extends _i1.SmartFake implements _i2.ContactEntry { + _FakeContactEntry_0( Object parent, Invocation parentInvocation, ) : super( @@ -141,46 +141,43 @@ class MockNotesService extends _i1.Mock implements _i3.NotesService { class MockAddressBookService extends _i1.Mock implements _i6.AddressBookService { @override - List<_i2.Contact> get contacts => (super.noSuchMethod( + List<_i2.ContactEntry> get contacts => (super.noSuchMethod( Invocation.getter(#contacts), - returnValue: <_i2.Contact>[], - ) as List<_i2.Contact>); - @override - _i4.Future> get addressBookEntries => (super.noSuchMethod( - Invocation.getter(#addressBookEntries), - returnValue: _i4.Future>.value(<_i2.Contact>[]), - ) as _i4.Future>); + returnValue: <_i2.ContactEntry>[], + ) as List<_i2.ContactEntry>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i2.Contact getContactById(String? id) => (super.noSuchMethod( + _i2.ContactEntry getContactById(String? id) => (super.noSuchMethod( Invocation.method( #getContactById, [id], ), - returnValue: _FakeContact_0( + returnValue: _FakeContactEntry_0( this, Invocation.method( #getContactById, [id], ), ), - ) as _i2.Contact); + ) as _i2.ContactEntry); @override - _i4.Future> search(String? text) => (super.noSuchMethod( + _i4.Future> search(String? text) => + (super.noSuchMethod( Invocation.method( #search, [text], ), - returnValue: _i4.Future>.value(<_i2.Contact>[]), - ) as _i4.Future>); + returnValue: + _i4.Future>.value(<_i2.ContactEntry>[]), + ) as _i4.Future>); @override bool matches( String? term, - _i2.Contact? contact, + _i2.ContactEntry? contact, ) => (super.noSuchMethod( Invocation.method( @@ -193,7 +190,7 @@ class MockAddressBookService extends _i1.Mock returnValue: false, ) as bool); @override - _i4.Future addContact(_i2.Contact? contact) => (super.noSuchMethod( + _i4.Future addContact(_i2.ContactEntry? contact) => (super.noSuchMethod( Invocation.method( #addContact, [contact], @@ -201,7 +198,7 @@ class MockAddressBookService extends _i1.Mock returnValue: _i4.Future.value(false), ) as _i4.Future); @override - _i4.Future editContact(_i2.Contact? editedContact) => + _i4.Future editContact(_i2.ContactEntry? editedContact) => (super.noSuchMethod( Invocation.method( #editContact, diff --git a/test/screen_tests/transaction_subviews/transaction_search_results_view_screen_test.mocks.dart b/test/screen_tests/transaction_subviews/transaction_search_results_view_screen_test.mocks.dart index fda23c162..52c9776df 100644 --- a/test/screen_tests/transaction_subviews/transaction_search_results_view_screen_test.mocks.dart +++ b/test/screen_tests/transaction_subviews/transaction_search_results_view_screen_test.mocks.dart @@ -3,17 +3,19 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i7; -import 'dart:ui' as _i8; +import 'dart:async' as _i8; +import 'dart:ui' as _i10; -import 'package:decimal/decimal.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/models/balance.dart' as _i4; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i9; import 'package:stackwallet/models/models.dart' as _i3; import 'package:stackwallet/services/coins/coin_service.dart' as _i2; -import 'package:stackwallet/services/coins/manager.dart' as _i5; -import 'package:stackwallet/services/locale_service.dart' as _i10; -import 'package:stackwallet/services/notes_service.dart' as _i9; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i6; +import 'package:stackwallet/services/coins/manager.dart' as _i6; +import 'package:stackwallet/services/locale_service.dart' as _i12; +import 'package:stackwallet/services/notes_service.dart' as _i11; +import 'package:stackwallet/utilities/amount/amount.dart' as _i5; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i7; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -47,8 +49,8 @@ class _FakeFeeObject_1 extends _i1.SmartFake implements _i3.FeeObject { ); } -class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { - _FakeDecimal_2( +class _FakeBalance_2 extends _i1.SmartFake implements _i4.Balance { + _FakeBalance_2( Object parent, Invocation parentInvocation, ) : super( @@ -57,9 +59,8 @@ class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { ); } -class _FakeTransactionData_3 extends _i1.SmartFake - implements _i3.TransactionData { - _FakeTransactionData_3( +class _FakeAmount_3 extends _i1.SmartFake implements _i5.Amount { + _FakeAmount_3( Object parent, Invocation parentInvocation, ) : super( @@ -71,7 +72,7 @@ class _FakeTransactionData_3 extends _i1.SmartFake /// A class which mocks [Manager]. /// /// See the documentation for Mockito's code generation for more information. -class MockManager extends _i1.Mock implements _i5.Manager { +class MockManager extends _i1.Mock implements _i6.Manager { @override bool get isActiveWallet => (super.noSuchMethod( Invocation.getter(#isActiveWallet), @@ -99,10 +100,10 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: false, ) as bool); @override - _i6.Coin get coin => (super.noSuchMethod( + _i7.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i6.Coin.bitcoin, - ) as _i6.Coin); + returnValue: _i7.Coin.bitcoin, + ) as _i7.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -135,90 +136,42 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - _i7.Future<_i3.FeeObject> get fees => (super.noSuchMethod( + _i8.Future<_i3.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i7.Future<_i3.FeeObject>.value(_FakeFeeObject_1( + returnValue: _i8.Future<_i3.FeeObject>.value(_FakeFeeObject_1( this, Invocation.getter(#fees), )), - ) as _i7.Future<_i3.FeeObject>); + ) as _i8.Future<_i3.FeeObject>); @override - _i7.Future get maxFee => (super.noSuchMethod( + _i8.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future.value(0), + ) as _i8.Future); @override - _i7.Future get currentReceivingAddress => (super.noSuchMethod( + _i8.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future<_i4.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( + _i4.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_2( this, - Invocation.getter(#availableBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i4.Decimal); + ) as _i4.Balance); @override - _i7.Future<_i4.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i7.Future<_i4.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i7.Future<_i4.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#totalBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i4.Decimal); - @override - _i7.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); - @override - _i7.Future<_i3.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i8.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i7.Future<_i3.TransactionData>.value(_FakeTransactionData_3( - this, - Invocation.getter(#transactionData), - )), - ) as _i7.Future<_i3.TransactionData>); + _i8.Future>.value(<_i9.Transaction>[]), + ) as _i8.Future>); @override - _i7.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: _i7.Future>.value(<_i3.UtxoObject>[]), - ) as _i7.Future>); + _i8.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i8.Future>.value(<_i9.UTXO>[]), + ) as _i8.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -238,29 +191,74 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: '', ) as String); @override - _i7.Future> get mnemonic => (super.noSuchMethod( + _i8.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); + @override + _i8.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i8.Future.value(), + ) as _i8.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i8.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i8.Future.value(''), + ) as _i8.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i7.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i8.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -270,9 +268,9 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - _i7.Future> prepareSend({ + _i8.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i5.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -281,50 +279,32 @@ class MockManager extends _i1.Mock implements _i5.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i7.Future>.value({}), - ) as _i7.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future confirmSend({required Map? txData}) => + _i8.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); - @override - _i7.Future refresh() => (super.noSuchMethod( + _i8.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -334,34 +314,35 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: false, ) as bool); @override - _i7.Future testNetworkConnection() => (super.noSuchMethod( + _i8.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future initializeNew() => (super.noSuchMethod( + _i8.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future initializeExisting() => (super.noSuchMethod( + _i8.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future recoverFromMnemonic({ + _i8.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -372,25 +353,26 @@ class MockManager extends _i1.Mock implements _i5.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future exitCurrentWallet() => (super.noSuchMethod( + _i8.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future fullRescan( + _i8.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -402,42 +384,52 @@ class MockManager extends _i1.Mock implements _i5.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); - @override - _i7.Future estimateFeeFor( - int? satoshiAmount, + _i8.Future<_i5.Amount> estimateFeeFor( + _i5.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future<_i5.Amount>.value(_FakeAmount_3( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i8.Future<_i5.Amount>); @override - _i7.Future generateNewAddress() => (super.noSuchMethod( + _i8.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + _i8.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -445,7 +437,7 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -465,7 +457,7 @@ class MockManager extends _i1.Mock implements _i5.Manager { /// A class which mocks [NotesService]. /// /// See the documentation for Mockito's code generation for more information. -class MockNotesService extends _i1.Mock implements _i9.NotesService { +class MockNotesService extends _i1.Mock implements _i11.NotesService { @override String get walletId => (super.noSuchMethod( Invocation.getter(#walletId), @@ -477,34 +469,34 @@ class MockNotesService extends _i1.Mock implements _i9.NotesService { returnValue: {}, ) as Map); @override - _i7.Future> get notes => (super.noSuchMethod( + _i8.Future> get notes => (super.noSuchMethod( Invocation.getter(#notes), - returnValue: _i7.Future>.value({}), - ) as _i7.Future>); + returnValue: _i8.Future>.value({}), + ) as _i8.Future>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i7.Future> search(String? text) => (super.noSuchMethod( + _i8.Future> search(String? text) => (super.noSuchMethod( Invocation.method( #search, [text], ), - returnValue: _i7.Future>.value({}), - ) as _i7.Future>); + returnValue: _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future getNoteFor({required String? txid}) => (super.noSuchMethod( + _i8.Future getNoteFor({required String? txid}) => (super.noSuchMethod( Invocation.method( #getNoteFor, [], {#txid: txid}, ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future editOrAddNote({ + _i8.Future editOrAddNote({ required String? txid, required String? note, }) => @@ -517,21 +509,21 @@ class MockNotesService extends _i1.Mock implements _i9.NotesService { #note: note, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future deleteNote({required String? txid}) => (super.noSuchMethod( + _i8.Future deleteNote({required String? txid}) => (super.noSuchMethod( Invocation.method( #deleteNote, [], {#txid: txid}, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -539,7 +531,7 @@ class MockNotesService extends _i1.Mock implements _i9.NotesService { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -567,7 +559,7 @@ class MockNotesService extends _i1.Mock implements _i9.NotesService { /// A class which mocks [LocaleService]. /// /// See the documentation for Mockito's code generation for more information. -class MockLocaleService extends _i1.Mock implements _i10.LocaleService { +class MockLocaleService extends _i1.Mock implements _i12.LocaleService { @override String get locale => (super.noSuchMethod( Invocation.getter(#locale), @@ -579,17 +571,17 @@ class MockLocaleService extends _i1.Mock implements _i10.LocaleService { returnValue: false, ) as bool); @override - _i7.Future loadLocale({bool? notify = true}) => (super.noSuchMethod( + _i8.Future loadLocale({bool? notify = true}) => (super.noSuchMethod( Invocation.method( #loadLocale, [], {#notify: notify}, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -597,7 +589,7 @@ class MockLocaleService extends _i1.Mock implements _i10.LocaleService { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/screen_tests/transaction_subviews/transaction_search_view_screen_test.mocks.dart b/test/screen_tests/transaction_subviews/transaction_search_view_screen_test.mocks.dart index 86d4ca961..02b5eb2b7 100644 --- a/test/screen_tests/transaction_subviews/transaction_search_view_screen_test.mocks.dart +++ b/test/screen_tests/transaction_subviews/transaction_search_view_screen_test.mocks.dart @@ -7,7 +7,7 @@ import 'dart:async' as _i4; import 'dart:ui' as _i5; import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/models/contact.dart' as _i2; +import 'package:stackwallet/models/isar/models/contact_entry.dart' as _i2; import 'package:stackwallet/services/address_book_service.dart' as _i3; import 'package:stackwallet/services/notes_service.dart' as _i6; @@ -22,8 +22,8 @@ import 'package:stackwallet/services/notes_service.dart' as _i6; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class -class _FakeContact_0 extends _i1.SmartFake implements _i2.Contact { - _FakeContact_0( +class _FakeContactEntry_0 extends _i1.SmartFake implements _i2.ContactEntry { + _FakeContactEntry_0( Object parent, Invocation parentInvocation, ) : super( @@ -38,46 +38,43 @@ class _FakeContact_0 extends _i1.SmartFake implements _i2.Contact { class MockAddressBookService extends _i1.Mock implements _i3.AddressBookService { @override - List<_i2.Contact> get contacts => (super.noSuchMethod( + List<_i2.ContactEntry> get contacts => (super.noSuchMethod( Invocation.getter(#contacts), - returnValue: <_i2.Contact>[], - ) as List<_i2.Contact>); - @override - _i4.Future> get addressBookEntries => (super.noSuchMethod( - Invocation.getter(#addressBookEntries), - returnValue: _i4.Future>.value(<_i2.Contact>[]), - ) as _i4.Future>); + returnValue: <_i2.ContactEntry>[], + ) as List<_i2.ContactEntry>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i2.Contact getContactById(String? id) => (super.noSuchMethod( + _i2.ContactEntry getContactById(String? id) => (super.noSuchMethod( Invocation.method( #getContactById, [id], ), - returnValue: _FakeContact_0( + returnValue: _FakeContactEntry_0( this, Invocation.method( #getContactById, [id], ), ), - ) as _i2.Contact); + ) as _i2.ContactEntry); @override - _i4.Future> search(String? text) => (super.noSuchMethod( + _i4.Future> search(String? text) => + (super.noSuchMethod( Invocation.method( #search, [text], ), - returnValue: _i4.Future>.value(<_i2.Contact>[]), - ) as _i4.Future>); + returnValue: + _i4.Future>.value(<_i2.ContactEntry>[]), + ) as _i4.Future>); @override bool matches( String? term, - _i2.Contact? contact, + _i2.ContactEntry? contact, ) => (super.noSuchMethod( Invocation.method( @@ -90,7 +87,7 @@ class MockAddressBookService extends _i1.Mock returnValue: false, ) as bool); @override - _i4.Future addContact(_i2.Contact? contact) => (super.noSuchMethod( + _i4.Future addContact(_i2.ContactEntry? contact) => (super.noSuchMethod( Invocation.method( #addContact, [contact], @@ -98,7 +95,7 @@ class MockAddressBookService extends _i1.Mock returnValue: _i4.Future.value(false), ) as _i4.Future); @override - _i4.Future editContact(_i2.Contact? editedContact) => + _i4.Future editContact(_i2.ContactEntry? editedContact) => (super.noSuchMethod( Invocation.method( #editContact, diff --git a/test/screen_tests/wallet_view/confirm_send_view_screen_test.mocks.dart b/test/screen_tests/wallet_view/confirm_send_view_screen_test.mocks.dart index 108b77901..245da71d8 100644 --- a/test/screen_tests/wallet_view/confirm_send_view_screen_test.mocks.dart +++ b/test/screen_tests/wallet_view/confirm_send_view_screen_test.mocks.dart @@ -3,16 +3,18 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i7; -import 'dart:ui' as _i8; +import 'dart:async' as _i8; +import 'dart:ui' as _i10; -import 'package:decimal/decimal.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/models/balance.dart' as _i4; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i9; import 'package:stackwallet/models/models.dart' as _i3; import 'package:stackwallet/services/coins/coin_service.dart' as _i2; -import 'package:stackwallet/services/coins/manager.dart' as _i5; -import 'package:stackwallet/services/notes_service.dart' as _i9; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i6; +import 'package:stackwallet/services/coins/manager.dart' as _i6; +import 'package:stackwallet/services/notes_service.dart' as _i11; +import 'package:stackwallet/utilities/amount/amount.dart' as _i5; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i7; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -46,8 +48,8 @@ class _FakeFeeObject_1 extends _i1.SmartFake implements _i3.FeeObject { ); } -class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { - _FakeDecimal_2( +class _FakeBalance_2 extends _i1.SmartFake implements _i4.Balance { + _FakeBalance_2( Object parent, Invocation parentInvocation, ) : super( @@ -56,9 +58,8 @@ class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { ); } -class _FakeTransactionData_3 extends _i1.SmartFake - implements _i3.TransactionData { - _FakeTransactionData_3( +class _FakeAmount_3 extends _i1.SmartFake implements _i5.Amount { + _FakeAmount_3( Object parent, Invocation parentInvocation, ) : super( @@ -70,7 +71,7 @@ class _FakeTransactionData_3 extends _i1.SmartFake /// A class which mocks [Manager]. /// /// See the documentation for Mockito's code generation for more information. -class MockManager extends _i1.Mock implements _i5.Manager { +class MockManager extends _i1.Mock implements _i6.Manager { @override bool get isActiveWallet => (super.noSuchMethod( Invocation.getter(#isActiveWallet), @@ -98,10 +99,10 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: false, ) as bool); @override - _i6.Coin get coin => (super.noSuchMethod( + _i7.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i6.Coin.bitcoin, - ) as _i6.Coin); + returnValue: _i7.Coin.bitcoin, + ) as _i7.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -134,90 +135,42 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - _i7.Future<_i3.FeeObject> get fees => (super.noSuchMethod( + _i8.Future<_i3.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i7.Future<_i3.FeeObject>.value(_FakeFeeObject_1( + returnValue: _i8.Future<_i3.FeeObject>.value(_FakeFeeObject_1( this, Invocation.getter(#fees), )), - ) as _i7.Future<_i3.FeeObject>); + ) as _i8.Future<_i3.FeeObject>); @override - _i7.Future get maxFee => (super.noSuchMethod( + _i8.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future.value(0), + ) as _i8.Future); @override - _i7.Future get currentReceivingAddress => (super.noSuchMethod( + _i8.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future<_i4.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( + _i4.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_2( this, - Invocation.getter(#availableBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i4.Decimal); + ) as _i4.Balance); @override - _i7.Future<_i4.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i7.Future<_i4.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i7.Future<_i4.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#totalBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i4.Decimal); - @override - _i7.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); - @override - _i7.Future<_i3.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i8.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i7.Future<_i3.TransactionData>.value(_FakeTransactionData_3( - this, - Invocation.getter(#transactionData), - )), - ) as _i7.Future<_i3.TransactionData>); + _i8.Future>.value(<_i9.Transaction>[]), + ) as _i8.Future>); @override - _i7.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: _i7.Future>.value(<_i3.UtxoObject>[]), - ) as _i7.Future>); + _i8.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i8.Future>.value(<_i9.UTXO>[]), + ) as _i8.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -237,29 +190,74 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: '', ) as String); @override - _i7.Future> get mnemonic => (super.noSuchMethod( + _i8.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); + @override + _i8.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i8.Future.value(), + ) as _i8.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i8.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i8.Future.value(''), + ) as _i8.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i7.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i8.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -269,9 +267,9 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - _i7.Future> prepareSend({ + _i8.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i5.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -280,50 +278,32 @@ class MockManager extends _i1.Mock implements _i5.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i7.Future>.value({}), - ) as _i7.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future confirmSend({required Map? txData}) => + _i8.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); - @override - _i7.Future refresh() => (super.noSuchMethod( + _i8.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -333,34 +313,35 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: false, ) as bool); @override - _i7.Future testNetworkConnection() => (super.noSuchMethod( + _i8.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future initializeNew() => (super.noSuchMethod( + _i8.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future initializeExisting() => (super.noSuchMethod( + _i8.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future recoverFromMnemonic({ + _i8.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -371,25 +352,26 @@ class MockManager extends _i1.Mock implements _i5.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future exitCurrentWallet() => (super.noSuchMethod( + _i8.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future fullRescan( + _i8.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -401,42 +383,52 @@ class MockManager extends _i1.Mock implements _i5.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); - @override - _i7.Future estimateFeeFor( - int? satoshiAmount, + _i8.Future<_i5.Amount> estimateFeeFor( + _i5.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future<_i5.Amount>.value(_FakeAmount_3( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i8.Future<_i5.Amount>); @override - _i7.Future generateNewAddress() => (super.noSuchMethod( + _i8.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + _i8.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -444,7 +436,7 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -464,7 +456,7 @@ class MockManager extends _i1.Mock implements _i5.Manager { /// A class which mocks [NotesService]. /// /// See the documentation for Mockito's code generation for more information. -class MockNotesService extends _i1.Mock implements _i9.NotesService { +class MockNotesService extends _i1.Mock implements _i11.NotesService { @override String get walletId => (super.noSuchMethod( Invocation.getter(#walletId), @@ -476,34 +468,34 @@ class MockNotesService extends _i1.Mock implements _i9.NotesService { returnValue: {}, ) as Map); @override - _i7.Future> get notes => (super.noSuchMethod( + _i8.Future> get notes => (super.noSuchMethod( Invocation.getter(#notes), - returnValue: _i7.Future>.value({}), - ) as _i7.Future>); + returnValue: _i8.Future>.value({}), + ) as _i8.Future>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i7.Future> search(String? text) => (super.noSuchMethod( + _i8.Future> search(String? text) => (super.noSuchMethod( Invocation.method( #search, [text], ), - returnValue: _i7.Future>.value({}), - ) as _i7.Future>); + returnValue: _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future getNoteFor({required String? txid}) => (super.noSuchMethod( + _i8.Future getNoteFor({required String? txid}) => (super.noSuchMethod( Invocation.method( #getNoteFor, [], {#txid: txid}, ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future editOrAddNote({ + _i8.Future editOrAddNote({ required String? txid, required String? note, }) => @@ -516,21 +508,21 @@ class MockNotesService extends _i1.Mock implements _i9.NotesService { #note: note, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future deleteNote({required String? txid}) => (super.noSuchMethod( + _i8.Future deleteNote({required String? txid}) => (super.noSuchMethod( Invocation.method( #deleteNote, [], {#txid: txid}, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -538,7 +530,7 @@ class MockNotesService extends _i1.Mock implements _i9.NotesService { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/screen_tests/wallet_view/receive_view_screen_test.mocks.dart b/test/screen_tests/wallet_view/receive_view_screen_test.mocks.dart index 760c1d754..57de0b1b7 100644 --- a/test/screen_tests/wallet_view/receive_view_screen_test.mocks.dart +++ b/test/screen_tests/wallet_view/receive_view_screen_test.mocks.dart @@ -3,15 +3,17 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i7; -import 'dart:ui' as _i8; +import 'dart:async' as _i8; +import 'dart:ui' as _i10; -import 'package:decimal/decimal.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/models/balance.dart' as _i4; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i9; import 'package:stackwallet/models/models.dart' as _i3; import 'package:stackwallet/services/coins/coin_service.dart' as _i2; -import 'package:stackwallet/services/coins/manager.dart' as _i5; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i6; +import 'package:stackwallet/services/coins/manager.dart' as _i6; +import 'package:stackwallet/utilities/amount/amount.dart' as _i5; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i7; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -45,8 +47,8 @@ class _FakeFeeObject_1 extends _i1.SmartFake implements _i3.FeeObject { ); } -class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { - _FakeDecimal_2( +class _FakeBalance_2 extends _i1.SmartFake implements _i4.Balance { + _FakeBalance_2( Object parent, Invocation parentInvocation, ) : super( @@ -55,9 +57,8 @@ class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { ); } -class _FakeTransactionData_3 extends _i1.SmartFake - implements _i3.TransactionData { - _FakeTransactionData_3( +class _FakeAmount_3 extends _i1.SmartFake implements _i5.Amount { + _FakeAmount_3( Object parent, Invocation parentInvocation, ) : super( @@ -69,7 +70,7 @@ class _FakeTransactionData_3 extends _i1.SmartFake /// A class which mocks [Manager]. /// /// See the documentation for Mockito's code generation for more information. -class MockManager extends _i1.Mock implements _i5.Manager { +class MockManager extends _i1.Mock implements _i6.Manager { @override bool get isActiveWallet => (super.noSuchMethod( Invocation.getter(#isActiveWallet), @@ -97,10 +98,10 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: false, ) as bool); @override - _i6.Coin get coin => (super.noSuchMethod( + _i7.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i6.Coin.bitcoin, - ) as _i6.Coin); + returnValue: _i7.Coin.bitcoin, + ) as _i7.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -133,90 +134,42 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - _i7.Future<_i3.FeeObject> get fees => (super.noSuchMethod( + _i8.Future<_i3.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i7.Future<_i3.FeeObject>.value(_FakeFeeObject_1( + returnValue: _i8.Future<_i3.FeeObject>.value(_FakeFeeObject_1( this, Invocation.getter(#fees), )), - ) as _i7.Future<_i3.FeeObject>); + ) as _i8.Future<_i3.FeeObject>); @override - _i7.Future get maxFee => (super.noSuchMethod( + _i8.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future.value(0), + ) as _i8.Future); @override - _i7.Future get currentReceivingAddress => (super.noSuchMethod( + _i8.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future<_i4.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( + _i4.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_2( this, - Invocation.getter(#availableBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i4.Decimal); + ) as _i4.Balance); @override - _i7.Future<_i4.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i7.Future<_i4.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i7.Future<_i4.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#totalBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i4.Decimal); - @override - _i7.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); - @override - _i7.Future<_i3.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i8.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i7.Future<_i3.TransactionData>.value(_FakeTransactionData_3( - this, - Invocation.getter(#transactionData), - )), - ) as _i7.Future<_i3.TransactionData>); + _i8.Future>.value(<_i9.Transaction>[]), + ) as _i8.Future>); @override - _i7.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: _i7.Future>.value(<_i3.UtxoObject>[]), - ) as _i7.Future>); + _i8.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i8.Future>.value(<_i9.UTXO>[]), + ) as _i8.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -236,29 +189,74 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: '', ) as String); @override - _i7.Future> get mnemonic => (super.noSuchMethod( + _i8.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); + @override + _i8.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i8.Future.value(), + ) as _i8.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i8.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i8.Future.value(''), + ) as _i8.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i7.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i8.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -268,9 +266,9 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - _i7.Future> prepareSend({ + _i8.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i5.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -279,50 +277,32 @@ class MockManager extends _i1.Mock implements _i5.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i7.Future>.value({}), - ) as _i7.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future confirmSend({required Map? txData}) => + _i8.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); - @override - _i7.Future refresh() => (super.noSuchMethod( + _i8.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -332,34 +312,35 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: false, ) as bool); @override - _i7.Future testNetworkConnection() => (super.noSuchMethod( + _i8.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future initializeNew() => (super.noSuchMethod( + _i8.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future initializeExisting() => (super.noSuchMethod( + _i8.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future recoverFromMnemonic({ + _i8.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -370,25 +351,26 @@ class MockManager extends _i1.Mock implements _i5.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future exitCurrentWallet() => (super.noSuchMethod( + _i8.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future fullRescan( + _i8.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -400,42 +382,52 @@ class MockManager extends _i1.Mock implements _i5.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); - @override - _i7.Future estimateFeeFor( - int? satoshiAmount, + _i8.Future<_i5.Amount> estimateFeeFor( + _i5.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future<_i5.Amount>.value(_FakeAmount_3( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i8.Future<_i5.Amount>); @override - _i7.Future generateNewAddress() => (super.noSuchMethod( + _i8.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + _i8.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -443,7 +435,7 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/screen_tests/wallet_view/send_view_screen_test.mocks.dart b/test/screen_tests/wallet_view/send_view_screen_test.mocks.dart index dbcc2eef1..48dc8f554 100644 --- a/test/screen_tests/wallet_view/send_view_screen_test.mocks.dart +++ b/test/screen_tests/wallet_view/send_view_screen_test.mocks.dart @@ -3,18 +3,20 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i7; -import 'dart:ui' as _i10; +import 'dart:async' as _i8; +import 'dart:ui' as _i12; import 'package:barcode_scan2/barcode_scan2.dart' as _i2; -import 'package:decimal/decimal.dart' as _i5; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/models/balance.dart' as _i5; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i11; import 'package:stackwallet/models/models.dart' as _i4; import 'package:stackwallet/services/coins/coin_service.dart' as _i3; -import 'package:stackwallet/services/coins/manager.dart' as _i8; -import 'package:stackwallet/services/notes_service.dart' as _i11; -import 'package:stackwallet/utilities/barcode_scanner_interface.dart' as _i6; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i9; +import 'package:stackwallet/services/coins/manager.dart' as _i9; +import 'package:stackwallet/services/notes_service.dart' as _i13; +import 'package:stackwallet/utilities/amount/amount.dart' as _i6; +import 'package:stackwallet/utilities/barcode_scanner_interface.dart' as _i7; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i10; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -58,8 +60,8 @@ class _FakeFeeObject_2 extends _i1.SmartFake implements _i4.FeeObject { ); } -class _FakeDecimal_3 extends _i1.SmartFake implements _i5.Decimal { - _FakeDecimal_3( +class _FakeBalance_3 extends _i1.SmartFake implements _i5.Balance { + _FakeBalance_3( Object parent, Invocation parentInvocation, ) : super( @@ -68,9 +70,8 @@ class _FakeDecimal_3 extends _i1.SmartFake implements _i5.Decimal { ); } -class _FakeTransactionData_4 extends _i1.SmartFake - implements _i4.TransactionData { - _FakeTransactionData_4( +class _FakeAmount_4 extends _i1.SmartFake implements _i6.Amount { + _FakeAmount_4( Object parent, Invocation parentInvocation, ) : super( @@ -83,13 +84,13 @@ class _FakeTransactionData_4 extends _i1.SmartFake /// /// See the documentation for Mockito's code generation for more information. class MockBarcodeScannerWrapper extends _i1.Mock - implements _i6.BarcodeScannerWrapper { + implements _i7.BarcodeScannerWrapper { MockBarcodeScannerWrapper() { _i1.throwOnMissingStub(this); } @override - _i7.Future<_i2.ScanResult> scan( + _i8.Future<_i2.ScanResult> scan( {_i2.ScanOptions? options = const _i2.ScanOptions()}) => (super.noSuchMethod( Invocation.method( @@ -97,7 +98,7 @@ class MockBarcodeScannerWrapper extends _i1.Mock [], {#options: options}, ), - returnValue: _i7.Future<_i2.ScanResult>.value(_FakeScanResult_0( + returnValue: _i8.Future<_i2.ScanResult>.value(_FakeScanResult_0( this, Invocation.method( #scan, @@ -105,13 +106,13 @@ class MockBarcodeScannerWrapper extends _i1.Mock {#options: options}, ), )), - ) as _i7.Future<_i2.ScanResult>); + ) as _i8.Future<_i2.ScanResult>); } /// A class which mocks [Manager]. /// /// See the documentation for Mockito's code generation for more information. -class MockManager extends _i1.Mock implements _i8.Manager { +class MockManager extends _i1.Mock implements _i9.Manager { @override bool get isActiveWallet => (super.noSuchMethod( Invocation.getter(#isActiveWallet), @@ -139,10 +140,10 @@ class MockManager extends _i1.Mock implements _i8.Manager { returnValue: false, ) as bool); @override - _i9.Coin get coin => (super.noSuchMethod( + _i10.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i9.Coin.bitcoin, - ) as _i9.Coin); + returnValue: _i10.Coin.bitcoin, + ) as _i10.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -175,90 +176,42 @@ class MockManager extends _i1.Mock implements _i8.Manager { returnValueForMissingStub: null, ); @override - _i7.Future<_i4.FeeObject> get fees => (super.noSuchMethod( + _i8.Future<_i4.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i7.Future<_i4.FeeObject>.value(_FakeFeeObject_2( + returnValue: _i8.Future<_i4.FeeObject>.value(_FakeFeeObject_2( this, Invocation.getter(#fees), )), - ) as _i7.Future<_i4.FeeObject>); + ) as _i8.Future<_i4.FeeObject>); @override - _i7.Future get maxFee => (super.noSuchMethod( + _i8.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future.value(0), + ) as _i8.Future); @override - _i7.Future get currentReceivingAddress => (super.noSuchMethod( + _i8.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future<_i5.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i7.Future<_i5.Decimal>.value(_FakeDecimal_3( + _i5.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_3( this, - Invocation.getter(#availableBalance), - )), - ) as _i7.Future<_i5.Decimal>); - @override - _i5.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_3( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i5.Decimal); + ) as _i5.Balance); @override - _i7.Future<_i5.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i7.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i7.Future<_i5.Decimal>); - @override - _i7.Future<_i5.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i7.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i7.Future<_i5.Decimal>); - @override - _i7.Future<_i5.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i7.Future<_i5.Decimal>.value(_FakeDecimal_3( - this, - Invocation.getter(#totalBalance), - )), - ) as _i7.Future<_i5.Decimal>); - @override - _i5.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_3( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i5.Decimal); - @override - _i7.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); - @override - _i7.Future<_i4.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i8.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i7.Future<_i4.TransactionData>.value(_FakeTransactionData_4( - this, - Invocation.getter(#transactionData), - )), - ) as _i7.Future<_i4.TransactionData>); + _i8.Future>.value(<_i11.Transaction>[]), + ) as _i8.Future>); @override - _i7.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: _i7.Future>.value(<_i4.UtxoObject>[]), - ) as _i7.Future>); + _i8.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i8.Future>.value(<_i11.UTXO>[]), + ) as _i8.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -278,29 +231,74 @@ class MockManager extends _i1.Mock implements _i8.Manager { returnValue: '', ) as String); @override - _i7.Future> get mnemonic => (super.noSuchMethod( + _i8.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); + @override + _i8.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i8.Future.value(), + ) as _i8.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i8.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i8.Future.value(''), + ) as _i8.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i7.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i8.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -310,9 +308,9 @@ class MockManager extends _i1.Mock implements _i8.Manager { returnValueForMissingStub: null, ); @override - _i7.Future> prepareSend({ + _i8.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i6.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -321,50 +319,32 @@ class MockManager extends _i1.Mock implements _i8.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i7.Future>.value({}), - ) as _i7.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future confirmSend({required Map? txData}) => + _i8.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); - @override - _i7.Future refresh() => (super.noSuchMethod( + _i8.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -374,34 +354,35 @@ class MockManager extends _i1.Mock implements _i8.Manager { returnValue: false, ) as bool); @override - _i7.Future testNetworkConnection() => (super.noSuchMethod( + _i8.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future initializeNew() => (super.noSuchMethod( + _i8.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future initializeExisting() => (super.noSuchMethod( + _i8.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future recoverFromMnemonic({ + _i8.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -412,25 +393,26 @@ class MockManager extends _i1.Mock implements _i8.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future exitCurrentWallet() => (super.noSuchMethod( + _i8.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future fullRescan( + _i8.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -442,42 +424,52 @@ class MockManager extends _i1.Mock implements _i8.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); - @override - _i7.Future estimateFeeFor( - int? satoshiAmount, + _i8.Future<_i6.Amount> estimateFeeFor( + _i6.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future<_i6.Amount>.value(_FakeAmount_4( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i8.Future<_i6.Amount>); @override - _i7.Future generateNewAddress() => (super.noSuchMethod( + _i8.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( + _i8.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + void addListener(_i12.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -485,7 +477,7 @@ class MockManager extends _i1.Mock implements _i8.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i12.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -505,7 +497,7 @@ class MockManager extends _i1.Mock implements _i8.Manager { /// A class which mocks [NotesService]. /// /// See the documentation for Mockito's code generation for more information. -class MockNotesService extends _i1.Mock implements _i11.NotesService { +class MockNotesService extends _i1.Mock implements _i13.NotesService { @override String get walletId => (super.noSuchMethod( Invocation.getter(#walletId), @@ -517,34 +509,34 @@ class MockNotesService extends _i1.Mock implements _i11.NotesService { returnValue: {}, ) as Map); @override - _i7.Future> get notes => (super.noSuchMethod( + _i8.Future> get notes => (super.noSuchMethod( Invocation.getter(#notes), - returnValue: _i7.Future>.value({}), - ) as _i7.Future>); + returnValue: _i8.Future>.value({}), + ) as _i8.Future>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i7.Future> search(String? text) => (super.noSuchMethod( + _i8.Future> search(String? text) => (super.noSuchMethod( Invocation.method( #search, [text], ), - returnValue: _i7.Future>.value({}), - ) as _i7.Future>); + returnValue: _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future getNoteFor({required String? txid}) => (super.noSuchMethod( + _i8.Future getNoteFor({required String? txid}) => (super.noSuchMethod( Invocation.method( #getNoteFor, [], {#txid: txid}, ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future editOrAddNote({ + _i8.Future editOrAddNote({ required String? txid, required String? note, }) => @@ -557,21 +549,21 @@ class MockNotesService extends _i1.Mock implements _i11.NotesService { #note: note, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future deleteNote({required String? txid}) => (super.noSuchMethod( + _i8.Future deleteNote({required String? txid}) => (super.noSuchMethod( Invocation.method( #deleteNote, [], {#txid: txid}, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i12.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -579,7 +571,7 @@ class MockNotesService extends _i1.Mock implements _i11.NotesService { returnValueForMissingStub: null, ); @override - void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i12.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/screen_tests/wallet_view/wallet_view_screen_test.mocks.dart b/test/screen_tests/wallet_view/wallet_view_screen_test.mocks.dart index a75e0d23c..190da405c 100644 --- a/test/screen_tests/wallet_view/wallet_view_screen_test.mocks.dart +++ b/test/screen_tests/wallet_view/wallet_view_screen_test.mocks.dart @@ -3,17 +3,19 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i7; -import 'dart:ui' as _i8; +import 'dart:async' as _i8; +import 'dart:ui' as _i10; -import 'package:decimal/decimal.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/models/balance.dart' as _i4; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i9; import 'package:stackwallet/models/models.dart' as _i3; import 'package:stackwallet/services/coins/coin_service.dart' as _i2; -import 'package:stackwallet/services/coins/manager.dart' as _i5; -import 'package:stackwallet/services/locale_service.dart' as _i10; -import 'package:stackwallet/services/notes_service.dart' as _i9; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i6; +import 'package:stackwallet/services/coins/manager.dart' as _i6; +import 'package:stackwallet/services/locale_service.dart' as _i12; +import 'package:stackwallet/services/notes_service.dart' as _i11; +import 'package:stackwallet/utilities/amount/amount.dart' as _i5; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i7; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -47,8 +49,8 @@ class _FakeFeeObject_1 extends _i1.SmartFake implements _i3.FeeObject { ); } -class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { - _FakeDecimal_2( +class _FakeBalance_2 extends _i1.SmartFake implements _i4.Balance { + _FakeBalance_2( Object parent, Invocation parentInvocation, ) : super( @@ -57,9 +59,8 @@ class _FakeDecimal_2 extends _i1.SmartFake implements _i4.Decimal { ); } -class _FakeTransactionData_3 extends _i1.SmartFake - implements _i3.TransactionData { - _FakeTransactionData_3( +class _FakeAmount_3 extends _i1.SmartFake implements _i5.Amount { + _FakeAmount_3( Object parent, Invocation parentInvocation, ) : super( @@ -71,7 +72,7 @@ class _FakeTransactionData_3 extends _i1.SmartFake /// A class which mocks [Manager]. /// /// See the documentation for Mockito's code generation for more information. -class MockManager extends _i1.Mock implements _i5.Manager { +class MockManager extends _i1.Mock implements _i6.Manager { @override bool get isActiveWallet => (super.noSuchMethod( Invocation.getter(#isActiveWallet), @@ -99,10 +100,10 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: false, ) as bool); @override - _i6.Coin get coin => (super.noSuchMethod( + _i7.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i6.Coin.bitcoin, - ) as _i6.Coin); + returnValue: _i7.Coin.bitcoin, + ) as _i7.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -135,90 +136,42 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - _i7.Future<_i3.FeeObject> get fees => (super.noSuchMethod( + _i8.Future<_i3.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i7.Future<_i3.FeeObject>.value(_FakeFeeObject_1( + returnValue: _i8.Future<_i3.FeeObject>.value(_FakeFeeObject_1( this, Invocation.getter(#fees), )), - ) as _i7.Future<_i3.FeeObject>); + ) as _i8.Future<_i3.FeeObject>); @override - _i7.Future get maxFee => (super.noSuchMethod( + _i8.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future.value(0), + ) as _i8.Future); @override - _i7.Future get currentReceivingAddress => (super.noSuchMethod( + _i8.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future<_i4.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( + _i4.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_2( this, - Invocation.getter(#availableBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i4.Decimal); + ) as _i4.Balance); @override - _i7.Future<_i4.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i7.Future<_i4.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i7.Future<_i4.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i7.Future<_i4.Decimal>.value(_FakeDecimal_2( - this, - Invocation.getter(#totalBalance), - )), - ) as _i7.Future<_i4.Decimal>); - @override - _i4.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_2( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i4.Decimal); - @override - _i7.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); - @override - _i7.Future<_i3.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i8.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i7.Future<_i3.TransactionData>.value(_FakeTransactionData_3( - this, - Invocation.getter(#transactionData), - )), - ) as _i7.Future<_i3.TransactionData>); + _i8.Future>.value(<_i9.Transaction>[]), + ) as _i8.Future>); @override - _i7.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: _i7.Future>.value(<_i3.UtxoObject>[]), - ) as _i7.Future>); + _i8.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i8.Future>.value(<_i9.UTXO>[]), + ) as _i8.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -238,29 +191,74 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: '', ) as String); @override - _i7.Future> get mnemonic => (super.noSuchMethod( + _i8.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i7.Future>.value([]), - ) as _i7.Future>); + returnValue: _i8.Future>.value([]), + ) as _i8.Future>); + @override + _i8.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i8.Future.value(), + ) as _i8.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i8.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i8.Future.value(''), + ) as _i8.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i7.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i8.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -270,9 +268,9 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - _i7.Future> prepareSend({ + _i8.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i5.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -281,50 +279,32 @@ class MockManager extends _i1.Mock implements _i5.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i7.Future>.value({}), - ) as _i7.Future>); + _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future confirmSend({required Map? txData}) => + _i8.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); - @override - _i7.Future refresh() => (super.noSuchMethod( + _i8.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -334,34 +314,35 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValue: false, ) as bool); @override - _i7.Future testNetworkConnection() => (super.noSuchMethod( + _i8.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - _i7.Future initializeNew() => (super.noSuchMethod( + _i8.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future initializeExisting() => (super.noSuchMethod( + _i8.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future recoverFromMnemonic({ + _i8.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -372,25 +353,26 @@ class MockManager extends _i1.Mock implements _i5.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future exitCurrentWallet() => (super.noSuchMethod( + _i8.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future fullRescan( + _i8.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -402,42 +384,52 @@ class MockManager extends _i1.Mock implements _i5.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); - @override - _i7.Future estimateFeeFor( - int? satoshiAmount, + _i8.Future<_i5.Amount> estimateFeeFor( + _i5.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i7.Future.value(0), - ) as _i7.Future); + returnValue: _i8.Future<_i5.Amount>.value(_FakeAmount_3( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i8.Future<_i5.Amount>); @override - _i7.Future generateNewAddress() => (super.noSuchMethod( + _i8.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i7.Future.value(false), - ) as _i7.Future); + returnValue: _i8.Future.value(false), + ) as _i8.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + _i8.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override + void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -445,7 +437,7 @@ class MockManager extends _i1.Mock implements _i5.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -465,7 +457,7 @@ class MockManager extends _i1.Mock implements _i5.Manager { /// A class which mocks [NotesService]. /// /// See the documentation for Mockito's code generation for more information. -class MockNotesService extends _i1.Mock implements _i9.NotesService { +class MockNotesService extends _i1.Mock implements _i11.NotesService { @override String get walletId => (super.noSuchMethod( Invocation.getter(#walletId), @@ -477,34 +469,34 @@ class MockNotesService extends _i1.Mock implements _i9.NotesService { returnValue: {}, ) as Map); @override - _i7.Future> get notes => (super.noSuchMethod( + _i8.Future> get notes => (super.noSuchMethod( Invocation.getter(#notes), - returnValue: _i7.Future>.value({}), - ) as _i7.Future>); + returnValue: _i8.Future>.value({}), + ) as _i8.Future>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i7.Future> search(String? text) => (super.noSuchMethod( + _i8.Future> search(String? text) => (super.noSuchMethod( Invocation.method( #search, [text], ), - returnValue: _i7.Future>.value({}), - ) as _i7.Future>); + returnValue: _i8.Future>.value({}), + ) as _i8.Future>); @override - _i7.Future getNoteFor({required String? txid}) => (super.noSuchMethod( + _i8.Future getNoteFor({required String? txid}) => (super.noSuchMethod( Invocation.method( #getNoteFor, [], {#txid: txid}, ), - returnValue: _i7.Future.value(''), - ) as _i7.Future); + returnValue: _i8.Future.value(''), + ) as _i8.Future); @override - _i7.Future editOrAddNote({ + _i8.Future editOrAddNote({ required String? txid, required String? note, }) => @@ -517,21 +509,21 @@ class MockNotesService extends _i1.Mock implements _i9.NotesService { #note: note, }, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - _i7.Future deleteNote({required String? txid}) => (super.noSuchMethod( + _i8.Future deleteNote({required String? txid}) => (super.noSuchMethod( Invocation.method( #deleteNote, [], {#txid: txid}, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -539,7 +531,7 @@ class MockNotesService extends _i1.Mock implements _i9.NotesService { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -567,7 +559,7 @@ class MockNotesService extends _i1.Mock implements _i9.NotesService { /// A class which mocks [LocaleService]. /// /// See the documentation for Mockito's code generation for more information. -class MockLocaleService extends _i1.Mock implements _i10.LocaleService { +class MockLocaleService extends _i1.Mock implements _i12.LocaleService { @override String get locale => (super.noSuchMethod( Invocation.getter(#locale), @@ -579,17 +571,17 @@ class MockLocaleService extends _i1.Mock implements _i10.LocaleService { returnValue: false, ) as bool); @override - _i7.Future loadLocale({bool? notify = true}) => (super.noSuchMethod( + _i8.Future loadLocale({bool? notify = true}) => (super.noSuchMethod( Invocation.method( #loadLocale, [], {#notify: notify}, ), - returnValue: _i7.Future.value(), - returnValueForMissingStub: _i7.Future.value(), - ) as _i7.Future); + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); @override - void addListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -597,7 +589,7 @@ class MockLocaleService extends _i1.Mock implements _i10.LocaleService { returnValueForMissingStub: null, ); @override - void removeListener(_i8.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i10.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/services/change_now/change_now_test.dart b/test/services/change_now/change_now_test.dart index b7b504e98..f79cbcd4d 100644 --- a/test/services/change_now/change_now_test.dart +++ b/test/services/change_now/change_now_test.dart @@ -5,13 +5,12 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:http/http.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; -import 'package:stackwallet/models/exchange/change_now/estimated_exchange_amount.dart'; +import 'package:stackwallet/exceptions/exchange/exchange_exception.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction.dart'; import 'package:stackwallet/models/exchange/change_now/exchange_transaction_status.dart'; import 'package:stackwallet/models/exchange/response_objects/estimate.dart'; -import 'package:stackwallet/models/exchange/response_objects/pair.dart'; +import 'package:stackwallet/models/isar/exchange_cache/pair.dart'; import 'package:stackwallet/services/exchange/change_now/change_now_api.dart'; -import 'package:stackwallet/services/exchange/exchange_response.dart'; import 'change_now_sample_data.dart'; import 'change_now_test.mocks.dart'; @@ -122,7 +121,8 @@ void main() { final result = await ChangeNowAPI.instance.getAvailableCurrencies(); - expect(result.exception!.type, ExchangeExceptionType.generic); + expect( + result.exception!.type, ExchangeExceptionType.serializeResponseError); expect(result.value == null, true); }); }); @@ -264,7 +264,8 @@ void main() { apiKey: "testAPIKEY", ); - expect(result.exception!.type, ExchangeExceptionType.generic); + expect( + result.exception!.type, ExchangeExceptionType.serializeResponseError); expect(result.value == null, true); }); }); @@ -471,7 +472,8 @@ void main() { apiKey: "testAPIKEY", ); - expect(result.exception!.type, ExchangeExceptionType.generic); + expect( + result.exception!.type, ExchangeExceptionType.serializeResponseError); expect(result.value == null, true); }); }); @@ -722,7 +724,8 @@ void main() { apiKey: "testAPIKEY", ); - expect(result.exception!.type, ExchangeExceptionType.generic); + expect( + result.exception!.type, ExchangeExceptionType.serializeResponseError); expect(result.value == null, true); }); }); @@ -780,7 +783,8 @@ void main() { final result = await ChangeNowAPI.instance.getAvailableFloatingRatePairs(); - expect(result.exception!.type, ExchangeExceptionType.generic); + expect( + result.exception!.type, ExchangeExceptionType.serializeResponseError); expect(result.value == null, true); }); }); diff --git a/test/services/coins/bitcoin/bitcoin_wallet_test.dart b/test/services/coins/bitcoin/bitcoin_wallet_test.dart index e33ffafdf..1df1937ce 100644 --- a/test/services/coins/bitcoin/bitcoin_wallet_test.dart +++ b/test/services/coins/bitcoin/bitcoin_wallet_test.dart @@ -1,4 +1,3 @@ -import 'package:bitcoindart/bitcoindart.dart'; import 'package:decimal/decimal.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:hive/hive.dart'; @@ -8,30 +7,34 @@ import 'package:mockito/mockito.dart'; import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; import 'package:stackwallet/models/paymint/fee_object_model.dart'; -import 'package:stackwallet/models/paymint/transactions_model.dart'; -import 'package:stackwallet/models/paymint/utxo_model.dart'; import 'package:stackwallet/services/coins/bitcoin/bitcoin_wallet.dart'; -import 'package:stackwallet/services/price.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; -import 'package:tuple/tuple.dart'; -import 'bitcoin_history_sample_data.dart'; -import 'bitcoin_transaction_data_samples.dart'; -import 'bitcoin_utxo_sample_data.dart'; import 'bitcoin_wallet_test.mocks.dart'; import 'bitcoin_wallet_test_parameters.dart'; -@GenerateMocks( - [ElectrumX, CachedElectrumX, PriceAPI, TransactionNotificationTracker]) -void main() { +@GenerateMocks([ + ElectrumX, + CachedElectrumX, + TransactionNotificationTracker, +]) +void main() async { group("bitcoin constants", () { test("bitcoin minimum confirmations", () async { expect(MINIMUM_CONFIRMATIONS, 1); }); test("bitcoin dust limit", () async { - expect(DUST_LIMIT, 294); + expect( + DUST_LIMIT, + Amount( + rawValue: BigInt.from(294), + fractionDigits: 8, + ), + ); }); test("bitcoin mainnet genesis block hash", () async { expect(GENESIS_HASH_MAINNET, @@ -43,77 +46,20 @@ void main() { }); }); - test("bitcoin DerivePathType enum", () { - expect(DerivePathType.values.length, 3); - expect(DerivePathType.values.toString(), - "[DerivePathType.bip44, DerivePathType.bip49, DerivePathType.bip84]"); - }); - - group("bip32 node/root", () { - test("getBip32Root", () { - final root = getBip32Root(TEST_MNEMONIC, bitcoin); - expect(root.toWIF(), ROOT_WIF); - }); - - // test("getBip32NodeFromRoot", () { - // final root = getBip32Root(TEST_MNEMONIC, bitcoin); - // // two mainnet - // final node44 = getBip32NodeFromRoot(0, 0, root, DerivePathType.bip44); - // expect(node44.toWIF(), NODE_WIF_44); - // final node49 = getBip32NodeFromRoot(0, 0, root, DerivePathType.bip49); - // expect(node49.toWIF(), NODE_WIF_49); - // // and one on testnet - // final node84 = getBip32NodeFromRoot( - // 0, 0, getBip32Root(TEST_MNEMONIC, testnet), DerivePathType.bip84); - // expect(node84.toWIF(), NODE_WIF_84); - // // a bad derive path - // bool didThrow = false; - // try { - // getBip32NodeFromRoot(0, 0, root, null); - // } catch (_) { - // didThrow = true; - // } - // expect(didThrow, true); - // // finally an invalid network - // didThrow = false; - // final invalidNetwork = NetworkType( - // messagePrefix: '\x18hello world\n', - // bech32: 'gg', - // bip32: Bip32Type(public: 0x055521e, private: 0x055555), - // pubKeyHash: 0x55, - // scriptHash: 0x55, - // wif: 0x00); - // try { - // getBip32NodeFromRoot(0, 0, getBip32Root(TEST_MNEMONIC, invalidNetwork), - // DerivePathType.bip44); - // } catch (_) { - // didThrow = true; - // } - // expect(didThrow, true); - // }); - - test("basic getBip32Node", () { - final node = - getBip32Node(0, 0, TEST_MNEMONIC, testnet, DerivePathType.bip84); - expect(node.toWIF(), NODE_WIF_84); - }); - }); - group("validate testnet bitcoin addresses", () { MockElectrumX? client; MockCachedElectrumX? cachedClient; - MockPriceAPI? priceAPI; late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; - + // BitcoinWallet? testnetWallet; - setUp(() { + setUp(() async { client = MockElectrumX(); cachedClient = MockCachedElectrumX(); - priceAPI = MockPriceAPI(); secureStore = FakeSecureStorage(); tracker = MockTransactionNotificationTracker(); + // testnetWallet = BitcoinWallet( walletId: "validateAddressTestNet", @@ -122,8 +68,8 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, + // ); }); @@ -131,20 +77,18 @@ void main() { expect( testnetWallet?.validateAddress("mhqpGtwhcR6gFuuRjLTpHo41919QfuGy8Y"), true); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("valid testnet bitcoin p2sh-p2wpkh address", () { expect( testnetWallet?.validateAddress("2Mugf9hpSYdQPPLNtWiU2utCi6cM9v5Pnro"), true); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("valid testnet bitcoin p2wpkh address", () { @@ -152,30 +96,27 @@ void main() { testnetWallet ?.validateAddress("tb1qzzlm6mnc8k54mx6akehl8p9ray8r439va5ndyq"), true); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("invalid testnet bitcoin legacy/p2pkh address", () { expect( testnetWallet?.validateAddress("16YB85zQHjro7fqjR2hMcwdQWCX8jNVtr5"), false); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("invalid testnet bitcoin p2sh-p2wpkh address", () { expect( testnetWallet?.validateAddress("3Ns8HuQmkyyKnVixk2yQtG7pN3GcJ6xctk"), false); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("invalid testnet bitcoin p2wpkh address", () { @@ -183,28 +124,26 @@ void main() { testnetWallet ?.validateAddress("bc1qc5ymmsay89r6gr4fy2kklvrkuvzyln4shdvjhf"), false); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); }); group("validate mainnet bitcoin addresses", () { MockElectrumX? client; MockCachedElectrumX? cachedClient; - MockPriceAPI? priceAPI; late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; - + // BitcoinWallet? mainnetWallet; - setUp(() { + setUp(() async { client = MockElectrumX(); cachedClient = MockCachedElectrumX(); - priceAPI = MockPriceAPI(); secureStore = FakeSecureStorage(); tracker = MockTransactionNotificationTracker(); + // mainnetWallet = BitcoinWallet( walletId: "validateAddressMainNet", @@ -213,8 +152,8 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, + // ); }); @@ -223,11 +162,10 @@ void main() { mainnetWallet?.addressType( address: "16YB85zQHjro7fqjR2hMcwdQWCX8jNVtr5"), DerivePathType.bip44); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("valid mainnet p2sh-p2wpkh address type", () { @@ -235,11 +173,10 @@ void main() { mainnetWallet?.addressType( address: "3Ns8HuQmkyyKnVixk2yQtG7pN3GcJ6xctk"), DerivePathType.bip49); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("valid mainnet bech32 p2wpkh address type", () { @@ -247,11 +184,10 @@ void main() { mainnetWallet?.addressType( address: "bc1qc5ymmsay89r6gr4fy2kklvrkuvzyln4shdvjhf"), DerivePathType.bip84); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("invalid base58 address type", () { @@ -259,11 +195,10 @@ void main() { () => mainnetWallet?.addressType( address: "mhqpGtwhcR6gFuuRjLTpHo41919QfuGy8Y"), throwsArgumentError); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("invalid bech32 address type", () { @@ -271,11 +206,10 @@ void main() { () => mainnetWallet?.addressType( address: "tb1qzzlm6mnc8k54mx6akehl8p9ray8r439va5ndyq"), throwsArgumentError); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("address has no matching script", () { @@ -283,33 +217,30 @@ void main() { () => mainnetWallet?.addressType( address: "mpMk94ETazqonHutyC1v6ajshgtP8oiFKU"), throwsArgumentError); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("valid mainnet bitcoin legacy/p2pkh address", () { expect( mainnetWallet?.validateAddress("16YB85zQHjro7fqjR2hMcwdQWCX8jNVtr5"), true); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("valid mainnet bitcoin p2sh-p2wpkh address", () { expect( mainnetWallet?.validateAddress("3Ns8HuQmkyyKnVixk2yQtG7pN3GcJ6xctk"), true); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("valid mainnet bitcoin p2wpkh address", () { @@ -317,33 +248,30 @@ void main() { mainnetWallet ?.validateAddress("bc1qc5ymmsay89r6gr4fy2kklvrkuvzyln4shdvjhf"), true); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("invalid mainnet bitcoin legacy/p2pkh address", () { expect( mainnetWallet?.validateAddress("mhqpGtwhcR6gFuuRjLTpHo41919QfuGy8Y"), false); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("invalid mainnet bitcoin p2sh-p2wpkh address", () { expect( mainnetWallet?.validateAddress("2Mugf9hpSYdQPPLNtWiU2utCi6cM9v5Pnro"), false); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("invalid mainnet bitcoin p2wpkh address", () { @@ -351,27 +279,26 @@ void main() { mainnetWallet ?.validateAddress("tb1qzzlm6mnc8k54mx6akehl8p9ray8r439va5ndyq"), false); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); }); group("testNetworkConnection", () { MockElectrumX? client; MockCachedElectrumX? cachedClient; - MockPriceAPI? priceAPI; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; - + // BitcoinWallet? btc; - setUp(() { + setUp(() async { client = MockElectrumX(); cachedClient = MockCachedElectrumX(); - priceAPI = MockPriceAPI(); + // secureStore = FakeSecureStorage(); tracker = MockTransactionNotificationTracker(); @@ -382,8 +309,8 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, + // ); }); @@ -391,52 +318,49 @@ void main() { when(client?.ping()).thenAnswer((_) async => false); final bool? result = await btc?.testNetworkConnection(); expect(result, false); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verify(client?.ping()).called(1); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("attempted connection fails due to exception", () async { when(client?.ping()).thenThrow(Exception); final bool? result = await btc?.testNetworkConnection(); expect(result, false); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verify(client?.ping()).called(1); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("attempted connection test success", () async { when(client?.ping()).thenAnswer((_) async => true); final bool? result = await btc?.testNetworkConnection(); expect(result, true); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verify(client?.ping()).called(1); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); }); group("basic getters, setters, and functions", () { - final testWalletId = "BTCtestWalletID"; - final testWalletName = "BTCWallet"; + const testWalletId = "BTCtestWalletID"; + const testWalletName = "BTCWallet"; MockElectrumX? client; MockCachedElectrumX? cachedClient; - MockPriceAPI? priceAPI; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; - + // BitcoinWallet? btc; setUp(() async { client = MockElectrumX(); cachedClient = MockCachedElectrumX(); - priceAPI = MockPriceAPI(); + // secureStore = FakeSecureStorage(); tracker = MockTransactionNotificationTracker(); @@ -447,17 +371,16 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, + // ); }); test("get networkType main", () async { expect(Coin.bitcoin, Coin.bitcoin); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("get networkType test", () async { @@ -468,48 +391,43 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, + // ); expect(Coin.bitcoinTestNet, Coin.bitcoinTestNet); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("get cryptoCurrency", () async { expect(Coin.bitcoin, Coin.bitcoin); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("get coinName", () async { expect(Coin.bitcoin, Coin.bitcoin); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("get coinTicker", () async { expect(Coin.bitcoin, Coin.bitcoin); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("get and set walletName", () async { expect(Coin.bitcoin, Coin.bitcoin); btc?.walletName = "new name"; expect(btc?.walletName, "new name"); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("estimateTxFee", () async { @@ -521,23 +439,22 @@ void main() { expect(btc?.estimateTxFee(vSize: 356, feeRatePerKB: 1699), 712); expect(btc?.estimateTxFee(vSize: 356, feeRatePerKB: 2000), 712); expect(btc?.estimateTxFee(vSize: 356, feeRatePerKB: 12345), 4628); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("get fees succeeds", () async { when(client?.ping()).thenAnswer((_) async => true); when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_TESTNET, "hash_function": "sha256", - "services": [] + "services": [] }); when(client?.estimateFee(blocks: 1)) .thenAnswer((realInvocation) async => Decimal.zero); @@ -555,23 +472,22 @@ void main() { verify(client?.estimateFee(blocks: 1)).called(1); verify(client?.estimateFee(blocks: 5)).called(1); verify(client?.estimateFee(blocks: 20)).called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("get fees fails", () async { when(client?.ping()).thenAnswer((_) async => true); when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_TESTNET, "hash_function": "sha256", - "services": [] + "services": [] }); when(client?.estimateFee(blocks: 1)) .thenAnswer((realInvocation) async => Decimal.zero); @@ -592,23 +508,22 @@ void main() { verify(client?.estimateFee(blocks: 1)).called(1); verify(client?.estimateFee(blocks: 5)).called(1); verify(client?.estimateFee(blocks: 20)).called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); // test("get maxFee", () async { // when(client?.ping()).thenAnswer((_) async => true); // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_TESTNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // when(client?.estimateFee(blocks: 20)) // .thenAnswer((realInvocation) async => Decimal.zero); @@ -627,19 +542,19 @@ void main() { // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); // verifyNoMoreInteractions(tracker); - // verifyNoMoreInteractions(priceAPI); + // // }); }); group("Bitcoin service class functions that depend on shared storage", () { - final testWalletId = "BTCtestWalletID"; - final testWalletName = "BTCWallet"; + const testWalletId = "BTCtestWalletID"; + const testWalletName = "BTCWallet"; bool hiveAdaptersRegistered = false; MockElectrumX? client; MockCachedElectrumX? cachedClient; - MockPriceAPI? priceAPI; + // late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; @@ -650,25 +565,13 @@ void main() { if (!hiveAdaptersRegistered) { hiveAdaptersRegistered = true; - // Registering Transaction Model Adapters - Hive.registerAdapter(TransactionDataAdapter()); - Hive.registerAdapter(TransactionChunkAdapter()); - Hive.registerAdapter(TransactionAdapter()); - Hive.registerAdapter(InputAdapter()); - Hive.registerAdapter(OutputAdapter()); - - // Registering Utxo Model Adapters - Hive.registerAdapter(UtxoDataAdapter()); - Hive.registerAdapter(UtxoObjectAdapter()); - Hive.registerAdapter(StatusAdapter()); - - final wallets = await Hive.openBox('wallets'); + final wallets = await Hive.openBox('wallets'); await wallets.put('currentWalletName', testWalletName); } client = MockElectrumX(); cachedClient = MockCachedElectrumX(); - priceAPI = MockPriceAPI(); + // secureStore = FakeSecureStorage(); tracker = MockTransactionNotificationTracker(); @@ -679,8 +582,8 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, + // ); }); @@ -691,84 +594,84 @@ void main() { // verify(client?.ping()).called(1); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // test("initializeWallet no network exception", () async { // when(client?.ping()).thenThrow(Exception("Network connection failed")); - // final wallets = await Hive.openBox(testWalletId); + // final wallets = await Hive.openBox (testWalletId); // expect(await btc?.initializeExisting(), false); // expect(secureStore?.interactions, 0); // verify(client?.ping()).called(1); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); test("initializeWallet mainnet throws bad network", () async { when(client?.ping()).thenAnswer((_) async => true); when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_TESTNET, "hash_function": "sha256", - "services": [] + "services": [] }); // await btc?.initializeNew(); - final wallets = await Hive.openBox(testWalletId); + await Hive.openBox(testWalletId); - expectLater(() => btc?.initializeExisting(), throwsA(isA())) + await expectLater( + () => btc?.initializeExisting(), throwsA(isA())) .then((_) { - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); // verify(client?.ping()).called(1); // verify(client?.getServerFeatures()).called(1); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); }); test("initializeWallet throws mnemonic overwrite exception", () async { when(client?.ping()).thenAnswer((_) async => true); when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_MAINNET, "hash_function": "sha256", - "services": [] + "services": [] }); - await secureStore?.write( + await secureStore.write( key: "${testWalletId}_mnemonic", value: "some mnemonic"); - final wallets = await Hive.openBox(testWalletId); - expectLater(() => btc?.initializeExisting(), throwsA(isA())) + await Hive.openBox(testWalletId); + await expectLater( + () => btc?.initializeExisting(), throwsA(isA())) .then((_) { - expect(secureStore?.interactions, 1); + expect(secureStore.interactions, 1); // verify(client?.ping()).called(1); // verify(client?.getServerFeatures()).called(1); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); }); // test("initializeWallet testnet throws bad network", () async { // when(client?.ping()).thenAnswer((_) async => true); // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // // btc = BitcoinWallet( @@ -778,7 +681,7 @@ void main() { // client: client!, // cachedClient: cachedClient!, // tracker: tracker!, - // priceAPI: priceAPI, + // // secureStore: secureStore, // ); // @@ -789,7 +692,7 @@ void main() { // verify(client?.getServerFeatures()).called(1); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // }); @@ -798,14 +701,14 @@ void main() { // // .thenAnswer((realInvocation) async => Decimal.fromInt(10)); // when(client?.ping()).thenAnswer((_) async => true); // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // expect(await btc?.initializeWallet(), true); // @@ -819,7 +722,7 @@ void main() { // expect(didThrow, true); // // // set node - // final wallet = await Hive.openBox(testWalletId); + // final wallet = await Hive.openBox (testWalletId); // await wallet.put("nodes", { // "default": { // "id": "some nodeID", @@ -839,7 +742,7 @@ void main() { // verify(client?.getServerFeatures()).called(1); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("initializeWallet new main net wallet", () async { @@ -847,18 +750,18 @@ void main() { // // .thenAnswer((realInvocation) async => Decimal.fromInt(10)); // when(client?.ping()).thenAnswer((_) async => true); // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // expect(await btc?.initializeWallet(), true); // - // final wallet = await Hive.openBox(testWalletId); + // final wallet = await Hive.openBox (testWalletId); // // expect(await wallet.get("addressBookEntries"), {}); // expect(await wallet.get('notes'), null); @@ -922,7 +825,7 @@ void main() { // verify(client?.getServerFeatures()).called(1); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("initializeWallet existing main net wallet", () async { @@ -932,20 +835,20 @@ void main() { // when(client?.getBatchHistory(args: anyNamed("args"))) // .thenAnswer((_) async => {}); // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // // init new wallet // expect(await btc?.initializeWallet(), true); // // // fetch data to compare later - // final newWallet = await Hive.openBox(testWalletId); + // final newWallet = await Hive.openBox (testWalletId); // // final addressBookEntries = await newWallet.get("addressBookEntries"); // final notes = await newWallet.get('notes'); @@ -996,7 +899,7 @@ void main() { // client: client!, // cachedClient: cachedClient!, // tracker: tracker!, - // priceAPI: priceAPI, + // // secureStore: secureStore, // ); // @@ -1004,7 +907,7 @@ void main() { // expect(await btc?.initializeWallet(), true); // // // compare data to ensure state matches state of previously closed wallet - // final wallet = await Hive.openBox(testWalletId); + // final wallet = await Hive.openBox (testWalletId); // // expect(await wallet.get("addressBookEntries"), addressBookEntries); // expect(await wallet.get('notes'), notes); @@ -1063,18 +966,18 @@ void main() { // verify(client?.getServerFeatures()).called(1); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // // test("get fiatPrice", () async { // // // when(priceAPI.getBitcoinPrice(baseCurrency: "USD")) // // // .thenAnswer((realInvocation) async => Decimal.fromInt(10)); - // // await Hive.openBox(testWalletId); + // // await Hive.openBox (testWalletId); // // expect(await btc.basePrice, Decimal.fromInt(10)); // // verify(priceAPI.getBitcoinPrice(baseCurrency: "USD")).called(1); // // verifyNoMoreInteractions(client); // // verifyNoMoreInteractions(cachedClient); - // // verifyNoMoreInteractions(priceAPI); + // // // // }); // // test("get current receiving addresses", () async { @@ -1085,19 +988,19 @@ void main() { // client: client!, // cachedClient: cachedClient!, // tracker: tracker!, - // priceAPI: priceAPI, + // // secureStore: secureStore, // ); // when(client?.ping()).thenAnswer((_) async => true); // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_TESTNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // await btc?.initializeWallet(); // expect( @@ -1116,7 +1019,7 @@ void main() { // verify(client?.getServerFeatures()).called(1); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("get allOwnAddresses", () async { @@ -1127,19 +1030,19 @@ void main() { // client: client!, // cachedClient: cachedClient!, // tracker: tracker!, - // priceAPI: priceAPI, + // // secureStore: secureStore, // ); // when(client?.ping()).thenAnswer((_) async => true); // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_TESTNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // await btc?.initializeWallet(); // final addresses = await btc?.allOwnAddresses; @@ -1154,7 +1057,7 @@ void main() { // verify(client?.getServerFeatures()).called(1); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // test("get utxos and balances", () async { @@ -1165,19 +1068,19 @@ void main() { // client: client!, // cachedClient: cachedClient!, // tracker: tracker!, - // priceAPI: priceAPI, + // // secureStore: secureStore, // ); // when(client?.ping()).thenAnswer((_) async => true); // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_TESTNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // // when(client?.getBatchUTXOs(args: anyNamed("args"))) @@ -1266,7 +1169,7 @@ void main() { // // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // test("get utxos - multiple batches", () async { @@ -1277,19 +1180,19 @@ void main() { // client: client!, // cachedClient: cachedClient!, // tracker: tracker!, - // priceAPI: priceAPI, + // // secureStore: secureStore, // ); // when(client?.ping()).thenAnswer((_) async => true); // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_TESTNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // // when(client?.getBatchUTXOs(args: anyNamed("args"))) @@ -1331,7 +1234,7 @@ void main() { // // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("get utxos fails", () async { @@ -1342,33 +1245,34 @@ void main() { // client: client!, // cachedClient: cachedClient!, // tracker: tracker!, - // priceAPI: priceAPI, // secureStore: secureStore, + // // // ); + // // when(client?.ping()).thenAnswer((_) async => true); // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_TESTNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // + // await Hive.openBox(testWalletId); + // await Hive.openBox(DB.boxNamePrefs); + // // when(client?.getBatchUTXOs(args: anyNamed("args"))) // .thenThrow(Exception("some exception")); // - // await btc?.initializeWallet(); - // final utxoData = await btc?.utxoData; - // expect(utxoData, isA()); - // expect(utxoData.toString(), - // r"{totalUserCurrency: $0.00, satoshiBalance: 0, bitcoinBalance: 0, unspentOutputArray: []}"); + // await btc?.initializeNew(); + // await btc?.initializeExisting(); // - // final outputs = await btc?.unspentOutputs; - // expect(outputs, isA>()); - // expect(outputs?.length, 0); + // final outputs = await btc!.utxos; + // expect(outputs, isA>()); + // expect(outputs.length, 0); // // verify(client?.ping()).called(1); // verify(client?.getServerFeatures()).called(1); @@ -1376,7 +1280,6 @@ void main() { // // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); // }); // // test("chain height fetch, update, and get", () async { @@ -1387,19 +1290,19 @@ void main() { // client: client!, // cachedClient: cachedClient!, // tracker: tracker!, - // priceAPI: priceAPI, + // // secureStore: secureStore, // ); // when(client?.ping()).thenAnswer((_) async => true); // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_TESTNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // await btc?.initializeWallet(); // @@ -1428,7 +1331,7 @@ void main() { // verify(client?.getBlockHeadTip()).called(2); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("fetch and update useBiometrics", () async { @@ -1444,7 +1347,7 @@ void main() { // expect(secureStore?.interactions, 0); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("getTxCount succeeds", () async { @@ -1477,7 +1380,7 @@ void main() { // expect(secureStore?.interactions, 0); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("getTxCount fails", () async { @@ -1502,20 +1405,20 @@ void main() { // expect(secureStore?.interactions, 0); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("_checkCurrentReceivingAddressesForTransactions succeeds", () async { // when(client?.ping()).thenAnswer((_) async => true); // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // when(client?.getHistory(scripthash: anyNamed("scripthash"))) // .thenAnswer((realInvocation) async => [ @@ -1551,24 +1454,24 @@ void main() { // expect(secureStore?.deletes, 0); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // test("_checkCurrentReceivingAddressesForTransactions fails", () async { // when(client?.ping()).thenAnswer((_) async => true); // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // when(client?.getHistory(scripthash: anyNamed("scripthash"))) // .thenThrow(Exception("some exception")); - // final wallet = await Hive.openBox(testWalletId); + // final wallet = await Hive.openBox (testWalletId); // // await btc?.initializeNew(); // await btc?.initializeExisting(); @@ -1591,20 +1494,20 @@ void main() { // expect(secureStore?.deletes, 0); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // test("_checkCurrentChangeAddressesForTransactions succeeds", () async { // when(client?.ping()).thenAnswer((_) async => true); // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // when(client?.getHistory(scripthash: anyNamed("scripthash"))) // .thenAnswer((realInvocation) async => [ @@ -1640,20 +1543,20 @@ void main() { // expect(secureStore?.deletes, 0); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("_checkCurrentChangeAddressesForTransactions fails", () async { // when(client?.ping()).thenAnswer((_) async => true); // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // when(client?.getHistory(scripthash: anyNamed("scripthash"))) // .thenThrow(Exception("some exception")); @@ -1678,7 +1581,7 @@ void main() { // expect(secureStore?.deletes, 0); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("getAllTxsToWatch", () async { @@ -1706,7 +1609,7 @@ void main() { // expect(secureStore?.interactions, 0); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("refreshIfThereIsNewData true A", () async { @@ -1726,10 +1629,10 @@ void main() { // client: client!, // cachedClient: cachedClient!, // tracker: tracker!, - // priceAPI: priceAPI, + // // secureStore: secureStore, // ); - // final wallet = await Hive.openBox(testWalletId); + // final wallet = await Hive.openBox (testWalletId); // await wallet.put('receivingAddressesP2PKH', []); // await wallet.put('receivingAddressesP2SH', [ // "2Mv83bPh2HzPRXptuQg9ejbKpSp87Zi52zT", @@ -1763,7 +1666,7 @@ void main() { // expect(secureStore.interactions, 0); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("refreshIfThereIsNewData true B", () async { @@ -1864,10 +1767,10 @@ void main() { // client: client!, // cachedClient: cachedClient!, // tracker: tracker!, - // priceAPI: priceAPI, + // // secureStore: secureStore, // ); - // final wallet = await Hive.openBox(testWalletId); + // final wallet = await Hive.openBox (testWalletId); // await wallet.put('receivingAddressesP2PKH', []); // await wallet.put('receivingAddressesP2SH', [ // "2Mv83bPh2HzPRXptuQg9ejbKpSp87Zi52zT", @@ -1904,7 +1807,7 @@ void main() { // expect(secureStore?.interactions, 0); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("refreshIfThereIsNewData false A", () async { @@ -2005,10 +1908,10 @@ void main() { // client: client!, // cachedClient: cachedClient!, // tracker: tracker!, - // priceAPI: priceAPI, + // // secureStore: secureStore, // ); - // final wallet = await Hive.openBox(testWalletId); + // final wallet = await Hive.openBox (testWalletId); // await wallet.put('receivingAddressesP2PKH', []); // await wallet.put('receivingAddressesP2SH', [ // "2Mv83bPh2HzPRXptuQg9ejbKpSp87Zi52zT", @@ -2045,7 +1948,7 @@ void main() { // expect(secureStore?.interactions, 0); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // test("refreshIfThereIsNewData false B", () async { @@ -2064,10 +1967,10 @@ void main() { // client: client!, // cachedClient: cachedClient!, // tracker: tracker!, - // priceAPI: priceAPI, + // // secureStore: secureStore, // ); - // final wallet = await Hive.openBox(testWalletId); + // final wallet = await Hive.openBox (testWalletId); // await wallet.put('receivingAddressesP2PKH', []); // await wallet.put('receivingAddressesP2SH', [ // "2Mv83bPh2HzPRXptuQg9ejbKpSp87Zi52zT", @@ -2101,21 +2004,21 @@ void main() { // expect(secureStore?.interactions, 0); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); test( "recoverFromMnemonic using empty seed on mainnet fails due to bad genesis hash match", () async { when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_TESTNET, "hash_function": "sha256", - "services": [] + "services": [] }); bool hasThrown = false; @@ -2132,10 +2035,9 @@ void main() { verify(client?.getServerFeatures()).called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test( @@ -2148,18 +2050,18 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, + // ); when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_MAINNET, "hash_function": "sha256", - "services": [] + "services": [] }); bool hasThrown = false; @@ -2176,27 +2078,26 @@ void main() { verify(client?.getServerFeatures()).called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test( "recoverFromMnemonic using empty seed on mainnet fails due to attempted overwrite of mnemonic", () async { when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_MAINNET, "hash_function": "sha256", - "services": [] + "services": [] }); - await secureStore?.write( + await secureStore.write( key: "${testWalletId}_mnemonic", value: "some mnemonic words"); bool hasThrown = false; @@ -2213,644 +2114,638 @@ void main() { verify(client?.getServerFeatures()).called(1); - expect(secureStore?.interactions, 2); + expect(secureStore.interactions, 2); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); - test("recoverFromMnemonic using empty seed on mainnet succeeds", () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs4)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs5)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - // await DB.instance.init(); - final wallet = await Hive.openBox(testWalletId); - bool hasThrown = false; - try { - await btc?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - } catch (_) { - hasThrown = true; - } - expect(hasThrown, false); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); - - expect(secureStore?.interactions, 20); - expect(secureStore?.writes, 7); - expect(secureStore?.reads, 13); - expect(secureStore?.deletes, 0); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); - - test("get mnemonic list", () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs4)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs5)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - - final wallet = await Hive.openBox(testWalletId); - - await btc?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - expect(await btc?.mnemonic, TEST_MNEMONIC.split(" ")); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); - - test("recoverFromMnemonic using non empty seed on mainnet succeeds", - () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs4)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs5)) - .thenAnswer((_) async => historyBatchResponse); - - List dynamicArgValues = []; - - when(client?.getBatchHistory(args: anyNamed("args"))) - .thenAnswer((realInvocation) async { - if (realInvocation.namedArguments.values.first.length == 1) { - dynamicArgValues.add(realInvocation.namedArguments.values.first); - } - - return historyBatchResponse; - }); - - await Hive.openBox(testWalletId); - - bool hasThrown = false; - try { - await btc?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - } catch (_) { - hasThrown = true; - } - expect(hasThrown, false); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); - - for (final arg in dynamicArgValues) { - final map = Map>.from(arg as Map); - - verify(client?.getBatchHistory(args: map)).called(1); - expect(activeScriptHashes.contains(map.values.first.first as String), - true); - } - - expect(secureStore?.interactions, 14); - expect(secureStore?.writes, 7); - expect(secureStore?.reads, 7); - expect(secureStore?.deletes, 0); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); - - test("fullRescan succeeds", () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs4)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs5)) - .thenAnswer((_) async => historyBatchResponse); - when(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoin)) - .thenAnswer((realInvocation) async {}); - - when(client?.getBatchHistory(args: { - "0": [ - "bf5a6c56814e80eed11e1e459801515f8c2b83da812568aa9dc26e6356f6965b" - ] - })).thenAnswer((_) async => {"0": []}); - when(client?.getBatchHistory(args: { - "0": [ - "26f92666caebb9a17b14f5b573b385348cdc80065472b8961091f3226d2f650f" - ] - })).thenAnswer((_) async => {"0": []}); - when(client?.getBatchHistory(args: { - "0": [ - "06593b2d896751e8dda288bb6587b6bb6a1dee71d82a85457f5654f781e37b12" - ] - })).thenAnswer((_) async => {"0": []}); - when(client?.getBatchHistory(args: { - "0": [ - "11663d093cb17dfbed4a96d148b22d3e094b31d23c639c2814beb79f2ab0ca75" - ] - })).thenAnswer((_) async => {"0": []}); - when(client?.getBatchHistory(args: { - "0": [ - "2f18558e5d3015cb6578aee1c3e4b645725fa4e1d26ce22cb31c9949f3b4957c" - ] - })).thenAnswer((_) async => {"0": []}); - when(client?.getBatchHistory(args: { - "0": [ - "a328ae88ebce63c0010709ae900c199df2b585cdebce53a6291886dfdcc28c63" - ] - })).thenAnswer((_) async => {"0": []}); - - final wallet = await Hive.openBox(testWalletId); - - // restore so we have something to rescan - await btc?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - // fetch valid wallet data - final preReceivingAddressesP2PKH = - await wallet.get('receivingAddressesP2PKH'); - final preReceivingAddressesP2SH = - await wallet.get('receivingAddressesP2SH'); - final preReceivingAddressesP2WPKH = - await wallet.get('receivingAddressesP2WPKH'); - final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); - final preChangeAddressesP2SH = await wallet.get('changeAddressesP2SH'); - final preChangeAddressesP2WPKH = - await wallet.get('changeAddressesP2WPKH'); - final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); - final preReceivingIndexP2SH = await wallet.get('receivingIndexP2SH'); - final preReceivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); - final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); - final preChangeIndexP2SH = await wallet.get('changeIndexP2SH'); - final preChangeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); - final preUtxoData = await wallet.get('latest_utxo_model'); - final preReceiveDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2PKH"); - final preChangeDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_changeDerivationsP2PKH"); - final preReceiveDerivationsStringP2SH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2SH"); - final preChangeDerivationsStringP2SH = - await secureStore?.read(key: "${testWalletId}_changeDerivationsP2SH"); - final preReceiveDerivationsStringP2WPKH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2WPKH"); - final preChangeDerivationsStringP2WPKH = await secureStore?.read( - key: "${testWalletId}_changeDerivationsP2WPKH"); - - // destroy the data that the rescan will fix - await wallet.put( - 'receivingAddressesP2PKH', ["some address", "some other address"]); - await wallet.put( - 'receivingAddressesP2SH', ["some address", "some other address"]); - await wallet.put( - 'receivingAddressesP2WPKH', ["some address", "some other address"]); - await wallet - .put('changeAddressesP2PKH', ["some address", "some other address"]); - await wallet - .put('changeAddressesP2SH', ["some address", "some other address"]); - await wallet - .put('changeAddressesP2WPKH', ["some address", "some other address"]); - await wallet.put('receivingIndexP2PKH', 123); - await wallet.put('receivingIndexP2SH', 123); - await wallet.put('receivingIndexP2WPKH', 123); - await wallet.put('changeIndexP2PKH', 123); - await wallet.put('changeIndexP2SH', 123); - await wallet.put('changeIndexP2WPKH', 123); - await secureStore?.write( - key: "${testWalletId}_receiveDerivationsP2PKH", value: "{}"); - await secureStore?.write( - key: "${testWalletId}_changeDerivationsP2PKH", value: "{}"); - await secureStore?.write( - key: "${testWalletId}_receiveDerivationsP2SH", value: "{}"); - await secureStore?.write( - key: "${testWalletId}_changeDerivationsP2SH", value: "{}"); - await secureStore?.write( - key: "${testWalletId}_receiveDerivationsP2WPKH", value: "{}"); - await secureStore?.write( - key: "${testWalletId}_changeDerivationsP2WPKH", value: "{}"); - - bool hasThrown = false; - try { - await btc?.fullRescan(2, 1000); - } catch (_) { - hasThrown = true; - } - expect(hasThrown, false); - - // fetch wallet data again - final receivingAddressesP2PKH = - await wallet.get('receivingAddressesP2PKH'); - final receivingAddressesP2SH = await wallet.get('receivingAddressesP2SH'); - final receivingAddressesP2WPKH = - await wallet.get('receivingAddressesP2WPKH'); - final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); - final changeAddressesP2SH = await wallet.get('changeAddressesP2SH'); - final changeAddressesP2WPKH = await wallet.get('changeAddressesP2WPKH'); - final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); - final receivingIndexP2SH = await wallet.get('receivingIndexP2SH'); - final receivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); - final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); - final changeIndexP2SH = await wallet.get('changeIndexP2SH'); - final changeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); - final utxoData = await wallet.get('latest_utxo_model'); - final receiveDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2PKH"); - final changeDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_changeDerivationsP2PKH"); - final receiveDerivationsStringP2SH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2SH"); - final changeDerivationsStringP2SH = - await secureStore?.read(key: "${testWalletId}_changeDerivationsP2SH"); - final receiveDerivationsStringP2WPKH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2WPKH"); - final changeDerivationsStringP2WPKH = await secureStore?.read( - key: "${testWalletId}_changeDerivationsP2WPKH"); - - expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); - expect(preReceivingAddressesP2SH, receivingAddressesP2SH); - expect(preReceivingAddressesP2WPKH, receivingAddressesP2WPKH); - expect(preChangeAddressesP2PKH, changeAddressesP2PKH); - expect(preChangeAddressesP2SH, changeAddressesP2SH); - expect(preChangeAddressesP2WPKH, changeAddressesP2WPKH); - expect(preReceivingIndexP2PKH, receivingIndexP2PKH); - expect(preReceivingIndexP2SH, receivingIndexP2SH); - expect(preReceivingIndexP2WPKH, receivingIndexP2WPKH); - expect(preChangeIndexP2PKH, changeIndexP2PKH); - expect(preChangeIndexP2SH, changeIndexP2SH); - expect(preChangeIndexP2WPKH, changeIndexP2WPKH); - expect(preUtxoData, utxoData); - expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); - expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); - expect(preReceiveDerivationsStringP2SH, receiveDerivationsStringP2SH); - expect(preChangeDerivationsStringP2SH, changeDerivationsStringP2SH); - expect(preReceiveDerivationsStringP2WPKH, receiveDerivationsStringP2WPKH); - expect(preChangeDerivationsStringP2WPKH, changeDerivationsStringP2WPKH); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs4)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs5)).called(2); - verify(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoin)) - .called(1); - - verify(client?.getBatchHistory(args: { - "0": [ - "bf5a6c56814e80eed11e1e459801515f8c2b83da812568aa9dc26e6356f6965b" - ] - })).called(2); - verify(client?.getBatchHistory(args: { - "0": [ - "26f92666caebb9a17b14f5b573b385348cdc80065472b8961091f3226d2f650f" - ] - })).called(2); - verify(client?.getBatchHistory(args: { - "0": [ - "06593b2d896751e8dda288bb6587b6bb6a1dee71d82a85457f5654f781e37b12" - ] - })).called(2); - verify(client?.getBatchHistory(args: { - "0": [ - "11663d093cb17dfbed4a96d148b22d3e094b31d23c639c2814beb79f2ab0ca75" - ] - })).called(2); - verify(client?.getBatchHistory(args: { - "0": [ - "2f18558e5d3015cb6578aee1c3e4b645725fa4e1d26ce22cb31c9949f3b4957c" - ] - })).called(2); - verify(client?.getBatchHistory(args: { - "0": [ - "a328ae88ebce63c0010709ae900c199df2b585cdebce53a6291886dfdcc28c63" - ] - })).called(2); - - expect(secureStore?.writes, 25); - expect(secureStore?.reads, 32); - expect(secureStore?.deletes, 6); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); - - test("fullRescan fails", () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs4)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs5)) - .thenAnswer((_) async => historyBatchResponse); - when(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoin)) - .thenAnswer((realInvocation) async {}); - - when(client?.getBatchHistory(args: { - "0": [ - "bf5a6c56814e80eed11e1e459801515f8c2b83da812568aa9dc26e6356f6965b" - ] - })).thenAnswer((_) async => {"0": []}); - when(client?.getBatchHistory(args: { - "0": [ - "26f92666caebb9a17b14f5b573b385348cdc80065472b8961091f3226d2f650f" - ] - })).thenAnswer((_) async => {"0": []}); - when(client?.getBatchHistory(args: { - "0": [ - "06593b2d896751e8dda288bb6587b6bb6a1dee71d82a85457f5654f781e37b12" - ] - })).thenAnswer((_) async => {"0": []}); - when(client?.getBatchHistory(args: { - "0": [ - "11663d093cb17dfbed4a96d148b22d3e094b31d23c639c2814beb79f2ab0ca75" - ] - })).thenAnswer((_) async => {"0": []}); - when(client?.getBatchHistory(args: { - "0": [ - "2f18558e5d3015cb6578aee1c3e4b645725fa4e1d26ce22cb31c9949f3b4957c" - ] - })).thenAnswer((_) async => {"0": []}); - when(client?.getBatchHistory(args: { - "0": [ - "a328ae88ebce63c0010709ae900c199df2b585cdebce53a6291886dfdcc28c63" - ] - })).thenAnswer((_) async => {"0": []}); - - final wallet = await Hive.openBox(testWalletId); - - // restore so we have something to rescan - await btc?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - // fetch wallet data - final preReceivingAddressesP2PKH = - await wallet.get('receivingAddressesP2PKH'); - final preReceivingAddressesP2SH = - await wallet.get('receivingAddressesP2SH'); - final preReceivingAddressesP2WPKH = - await wallet.get('receivingAddressesP2WPKH'); - final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); - final preChangeAddressesP2SH = await wallet.get('changeAddressesP2SH'); - final preChangeAddressesP2WPKH = - await wallet.get('changeAddressesP2WPKH'); - final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); - final preReceivingIndexP2SH = await wallet.get('receivingIndexP2SH'); - final preReceivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); - final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); - final preChangeIndexP2SH = await wallet.get('changeIndexP2SH'); - final preChangeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); - final preUtxoData = await wallet.get('latest_utxo_model'); - final preReceiveDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2PKH"); - final preChangeDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_changeDerivationsP2PKH"); - final preReceiveDerivationsStringP2SH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2SH"); - final preChangeDerivationsStringP2SH = - await secureStore?.read(key: "${testWalletId}_changeDerivationsP2SH"); - final preReceiveDerivationsStringP2WPKH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2WPKH"); - final preChangeDerivationsStringP2WPKH = await secureStore?.read( - key: "${testWalletId}_changeDerivationsP2WPKH"); - - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenThrow(Exception("fake exception")); - - bool hasThrown = false; - try { - await btc?.fullRescan(2, 1000); - } catch (_) { - hasThrown = true; - } - expect(hasThrown, true); - - // fetch wallet data again - final receivingAddressesP2PKH = - await wallet.get('receivingAddressesP2PKH'); - final receivingAddressesP2SH = await wallet.get('receivingAddressesP2SH'); - final receivingAddressesP2WPKH = - await wallet.get('receivingAddressesP2WPKH'); - final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); - final changeAddressesP2SH = await wallet.get('changeAddressesP2SH'); - final changeAddressesP2WPKH = await wallet.get('changeAddressesP2WPKH'); - final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); - final receivingIndexP2SH = await wallet.get('receivingIndexP2SH'); - final receivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); - final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); - final changeIndexP2SH = await wallet.get('changeIndexP2SH'); - final changeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); - final utxoData = await wallet.get('latest_utxo_model'); - final receiveDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2PKH"); - final changeDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_changeDerivationsP2PKH"); - final receiveDerivationsStringP2SH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2SH"); - final changeDerivationsStringP2SH = - await secureStore?.read(key: "${testWalletId}_changeDerivationsP2SH"); - final receiveDerivationsStringP2WPKH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2WPKH"); - final changeDerivationsStringP2WPKH = await secureStore?.read( - key: "${testWalletId}_changeDerivationsP2WPKH"); - - expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); - expect(preReceivingAddressesP2SH, receivingAddressesP2SH); - expect(preReceivingAddressesP2WPKH, receivingAddressesP2WPKH); - expect(preChangeAddressesP2PKH, changeAddressesP2PKH); - expect(preChangeAddressesP2SH, changeAddressesP2SH); - expect(preChangeAddressesP2WPKH, changeAddressesP2WPKH); - expect(preReceivingIndexP2PKH, receivingIndexP2PKH); - expect(preReceivingIndexP2SH, receivingIndexP2SH); - expect(preReceivingIndexP2WPKH, receivingIndexP2WPKH); - expect(preChangeIndexP2PKH, changeIndexP2PKH); - expect(preChangeIndexP2SH, changeIndexP2SH); - expect(preChangeIndexP2WPKH, changeIndexP2WPKH); - expect(preUtxoData, utxoData); - expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); - expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); - expect(preReceiveDerivationsStringP2SH, receiveDerivationsStringP2SH); - expect(preChangeDerivationsStringP2SH, changeDerivationsStringP2SH); - expect(preReceiveDerivationsStringP2WPKH, receiveDerivationsStringP2WPKH); - expect(preChangeDerivationsStringP2WPKH, changeDerivationsStringP2WPKH); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs4)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs5)).called(2); - verify(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoin)) - .called(1); - - verify(client?.getBatchHistory(args: { - "0": [ - "bf5a6c56814e80eed11e1e459801515f8c2b83da812568aa9dc26e6356f6965b" - ] - })).called(2); - verify(client?.getBatchHistory(args: { - "0": [ - "26f92666caebb9a17b14f5b573b385348cdc80065472b8961091f3226d2f650f" - ] - })).called(2); - verify(client?.getBatchHistory(args: { - "0": [ - "06593b2d896751e8dda288bb6587b6bb6a1dee71d82a85457f5654f781e37b12" - ] - })).called(2); - verify(client?.getBatchHistory(args: { - "0": [ - "11663d093cb17dfbed4a96d148b22d3e094b31d23c639c2814beb79f2ab0ca75" - ] - })).called(2); - verify(client?.getBatchHistory(args: { - "0": [ - "2f18558e5d3015cb6578aee1c3e4b645725fa4e1d26ce22cb31c9949f3b4957c" - ] - })).called(2); - verify(client?.getBatchHistory(args: { - "0": [ - "a328ae88ebce63c0010709ae900c199df2b585cdebce53a6291886dfdcc28c63" - ] - })).called(1); - - expect(secureStore?.writes, 19); - expect(secureStore?.reads, 32); - expect(secureStore?.deletes, 12); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); - - // test("fetchBuildTxData succeeds", () async { + // test("recoverFromMnemonic using empty seed on mainnet succeeds", () async { // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs4)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs5)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // // await DB.instance.init(); + // await Hive.openBox(testWalletId); + // bool hasThrown = false; + // try { + // await btc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // } catch (_) { + // hasThrown = true; + // } + // expect(hasThrown, false); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); + // + // expect(secureStore.interactions, 20); + // expect(secureStore.writes, 7); + // expect(secureStore.reads, 13); + // expect(secureStore.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + + // test("get mnemonic list", () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs4)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs5)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // + // await Hive.openBox(testWalletId); + // + // await btc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // expect(await btc?.mnemonic, TEST_MNEMONIC.split(" ")); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + + // test("recoverFromMnemonic using non empty seed on mainnet succeeds", + // () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs4)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs5)) + // .thenAnswer((_) async => historyBatchResponse); + // + // List dynamicArgValues = []; + // + // when(client?.getBatchHistory(args: anyNamed("args"))) + // .thenAnswer((realInvocation) async { + // if (realInvocation.namedArguments.values.first.length == 1) { + // dynamicArgValues.add(realInvocation.namedArguments.values.first); + // } + // + // return historyBatchResponse; + // }); + // + // await Hive.openBox(testWalletId); + // + // bool hasThrown = false; + // try { + // await btc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // } catch (_) { + // hasThrown = true; + // } + // expect(hasThrown, false); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); + // + // for (final arg in dynamicArgValues) { + // final map = Map>.from(arg as Map); + // + // verify(client?.getBatchHistory(args: map)).called(1); + // expect(activeScriptHashes.contains(map.values.first.first as String), + // true); + // } + // + // expect(secureStore.interactions, 14); + // expect(secureStore.writes, 7); + // expect(secureStore.reads, 7); + // expect(secureStore.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + + // test("fullRescan succeeds", () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs4)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs5)) + // .thenAnswer((_) async => historyBatchResponse); + // when(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoin)) + // .thenAnswer((realInvocation) async {}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "bf5a6c56814e80eed11e1e459801515f8c2b83da812568aa9dc26e6356f6965b" + // ] + // })).thenAnswer((_) async => {"0": []}); + // when(client?.getBatchHistory(args: { + // "0": [ + // "26f92666caebb9a17b14f5b573b385348cdc80065472b8961091f3226d2f650f" + // ] + // })).thenAnswer((_) async => {"0": []}); + // when(client?.getBatchHistory(args: { + // "0": [ + // "06593b2d896751e8dda288bb6587b6bb6a1dee71d82a85457f5654f781e37b12" + // ] + // })).thenAnswer((_) async => {"0": []}); + // when(client?.getBatchHistory(args: { + // "0": [ + // "11663d093cb17dfbed4a96d148b22d3e094b31d23c639c2814beb79f2ab0ca75" + // ] + // })).thenAnswer((_) async => {"0": []}); + // when(client?.getBatchHistory(args: { + // "0": [ + // "2f18558e5d3015cb6578aee1c3e4b645725fa4e1d26ce22cb31c9949f3b4957c" + // ] + // })).thenAnswer((_) async => {"0": []}); + // when(client?.getBatchHistory(args: { + // "0": [ + // "a328ae88ebce63c0010709ae900c199df2b585cdebce53a6291886dfdcc28c63" + // ] + // })).thenAnswer((_) async => {"0": []}); + // + // final wallet = await Hive.openBox(testWalletId); + // + // // restore so we have something to rescan + // await btc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // // fetch valid wallet data + // final preReceivingAddressesP2PKH = + // await wallet.get('receivingAddressesP2PKH'); + // final preReceivingAddressesP2SH = + // await wallet.get('receivingAddressesP2SH'); + // final preReceivingAddressesP2WPKH = + // await wallet.get('receivingAddressesP2WPKH'); + // final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + // final preChangeAddressesP2SH = await wallet.get('changeAddressesP2SH'); + // final preChangeAddressesP2WPKH = + // await wallet.get('changeAddressesP2WPKH'); + // final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + // final preReceivingIndexP2SH = await wallet.get('receivingIndexP2SH'); + // final preReceivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); + // final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + // final preChangeIndexP2SH = await wallet.get('changeIndexP2SH'); + // final preChangeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); + // final preUtxoData = await wallet.get('latest_utxo_model'); + // final preReceiveDerivationsStringP2PKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2PKH"); + // final preChangeDerivationsStringP2PKH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + // final preReceiveDerivationsStringP2SH = + // await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); + // final preChangeDerivationsStringP2SH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); + // final preReceiveDerivationsStringP2WPKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2WPKH"); + // final preChangeDerivationsStringP2WPKH = await secureStore.read( + // key: "${testWalletId}_changeDerivationsP2WPKH"); + // + // // destroy the data that the rescan will fix + // await wallet.put( + // 'receivingAddressesP2PKH', ["some address", "some other address"]); + // await wallet.put( + // 'receivingAddressesP2SH', ["some address", "some other address"]); + // await wallet.put( + // 'receivingAddressesP2WPKH', ["some address", "some other address"]); + // await wallet + // .put('changeAddressesP2PKH', ["some address", "some other address"]); + // await wallet + // .put('changeAddressesP2SH', ["some address", "some other address"]); + // await wallet + // .put('changeAddressesP2WPKH', ["some address", "some other address"]); + // await wallet.put('receivingIndexP2PKH', 123); + // await wallet.put('receivingIndexP2SH', 123); + // await wallet.put('receivingIndexP2WPKH', 123); + // await wallet.put('changeIndexP2PKH', 123); + // await wallet.put('changeIndexP2SH', 123); + // await wallet.put('changeIndexP2WPKH', 123); + // await secureStore.write( + // key: "${testWalletId}_receiveDerivationsP2PKH", value: "{}"); + // await secureStore.write( + // key: "${testWalletId}_changeDerivationsP2PKH", value: "{}"); + // await secureStore.write( + // key: "${testWalletId}_receiveDerivationsP2SH", value: "{}"); + // await secureStore.write( + // key: "${testWalletId}_changeDerivationsP2SH", value: "{}"); + // await secureStore.write( + // key: "${testWalletId}_receiveDerivationsP2WPKH", value: "{}"); + // await secureStore.write( + // key: "${testWalletId}_changeDerivationsP2WPKH", value: "{}"); + // + // bool hasThrown = false; + // try { + // await btc?.fullRescan(2, 1000); + // } catch (_) { + // hasThrown = true; + // } + // expect(hasThrown, false); + // + // // fetch wallet data again + // final receivingAddressesP2PKH = + // await wallet.get('receivingAddressesP2PKH'); + // final receivingAddressesP2SH = await wallet.get('receivingAddressesP2SH'); + // final receivingAddressesP2WPKH = + // await wallet.get('receivingAddressesP2WPKH'); + // final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + // final changeAddressesP2SH = await wallet.get('changeAddressesP2SH'); + // final changeAddressesP2WPKH = await wallet.get('changeAddressesP2WPKH'); + // final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + // final receivingIndexP2SH = await wallet.get('receivingIndexP2SH'); + // final receivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); + // final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + // final changeIndexP2SH = await wallet.get('changeIndexP2SH'); + // final changeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); + // final utxoData = await wallet.get('latest_utxo_model'); + // final receiveDerivationsStringP2PKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2PKH"); + // final changeDerivationsStringP2PKH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + // final receiveDerivationsStringP2SH = + // await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); + // final changeDerivationsStringP2SH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); + // final receiveDerivationsStringP2WPKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2WPKH"); + // final changeDerivationsStringP2WPKH = await secureStore.read( + // key: "${testWalletId}_changeDerivationsP2WPKH"); + // + // expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); + // expect(preReceivingAddressesP2SH, receivingAddressesP2SH); + // expect(preReceivingAddressesP2WPKH, receivingAddressesP2WPKH); + // expect(preChangeAddressesP2PKH, changeAddressesP2PKH); + // expect(preChangeAddressesP2SH, changeAddressesP2SH); + // expect(preChangeAddressesP2WPKH, changeAddressesP2WPKH); + // expect(preReceivingIndexP2PKH, receivingIndexP2PKH); + // expect(preReceivingIndexP2SH, receivingIndexP2SH); + // expect(preReceivingIndexP2WPKH, receivingIndexP2WPKH); + // expect(preChangeIndexP2PKH, changeIndexP2PKH); + // expect(preChangeIndexP2SH, changeIndexP2SH); + // expect(preChangeIndexP2WPKH, changeIndexP2WPKH); + // expect(preUtxoData, utxoData); + // expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); + // expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); + // expect(preReceiveDerivationsStringP2SH, receiveDerivationsStringP2SH); + // expect(preChangeDerivationsStringP2SH, changeDerivationsStringP2SH); + // expect(preReceiveDerivationsStringP2WPKH, receiveDerivationsStringP2WPKH); + // expect(preChangeDerivationsStringP2WPKH, changeDerivationsStringP2WPKH); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(2); + // verify(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoin)) + // .called(1); + // + // verify(client?.getBatchHistory(args: { + // "0": [ + // "bf5a6c56814e80eed11e1e459801515f8c2b83da812568aa9dc26e6356f6965b" + // ] + // })).called(2); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "26f92666caebb9a17b14f5b573b385348cdc80065472b8961091f3226d2f650f" + // ] + // })).called(2); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "06593b2d896751e8dda288bb6587b6bb6a1dee71d82a85457f5654f781e37b12" + // ] + // })).called(2); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "11663d093cb17dfbed4a96d148b22d3e094b31d23c639c2814beb79f2ab0ca75" + // ] + // })).called(2); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "2f18558e5d3015cb6578aee1c3e4b645725fa4e1d26ce22cb31c9949f3b4957c" + // ] + // })).called(2); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "a328ae88ebce63c0010709ae900c199df2b585cdebce53a6291886dfdcc28c63" + // ] + // })).called(2); + // + // expect(secureStore.writes, 25); + // expect(secureStore.reads, 32); + // expect(secureStore.deletes, 6); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + + // test("fullRescan fails", () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs4)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs5)) + // .thenAnswer((_) async => historyBatchResponse); + // when(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoin)) + // .thenAnswer((realInvocation) async {}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "bf5a6c56814e80eed11e1e459801515f8c2b83da812568aa9dc26e6356f6965b" + // ] + // })).thenAnswer((_) async => {"0": []}); + // when(client?.getBatchHistory(args: { + // "0": [ + // "26f92666caebb9a17b14f5b573b385348cdc80065472b8961091f3226d2f650f" + // ] + // })).thenAnswer((_) async => {"0": []}); + // when(client?.getBatchHistory(args: { + // "0": [ + // "06593b2d896751e8dda288bb6587b6bb6a1dee71d82a85457f5654f781e37b12" + // ] + // })).thenAnswer((_) async => {"0": []}); + // when(client?.getBatchHistory(args: { + // "0": [ + // "11663d093cb17dfbed4a96d148b22d3e094b31d23c639c2814beb79f2ab0ca75" + // ] + // })).thenAnswer((_) async => {"0": []}); + // when(client?.getBatchHistory(args: { + // "0": [ + // "2f18558e5d3015cb6578aee1c3e4b645725fa4e1d26ce22cb31c9949f3b4957c" + // ] + // })).thenAnswer((_) async => {"0": []}); + // when(client?.getBatchHistory(args: { + // "0": [ + // "a328ae88ebce63c0010709ae900c199df2b585cdebce53a6291886dfdcc28c63" + // ] + // })).thenAnswer((_) async => {"0": []}); + // + // final wallet = await Hive.openBox(testWalletId); + // + // // restore so we have something to rescan + // await btc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // // fetch wallet data + // final preReceivingAddressesP2PKH = + // await wallet.get('receivingAddressesP2PKH'); + // final preReceivingAddressesP2SH = + // await wallet.get('receivingAddressesP2SH'); + // final preReceivingAddressesP2WPKH = + // await wallet.get('receivingAddressesP2WPKH'); + // final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + // final preChangeAddressesP2SH = await wallet.get('changeAddressesP2SH'); + // final preChangeAddressesP2WPKH = + // await wallet.get('changeAddressesP2WPKH'); + // final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + // final preReceivingIndexP2SH = await wallet.get('receivingIndexP2SH'); + // final preReceivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); + // final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + // final preChangeIndexP2SH = await wallet.get('changeIndexP2SH'); + // final preChangeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); + // final preUtxoData = await wallet.get('latest_utxo_model'); + // final preReceiveDerivationsStringP2PKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2PKH"); + // final preChangeDerivationsStringP2PKH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + // final preReceiveDerivationsStringP2SH = + // await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); + // final preChangeDerivationsStringP2SH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); + // final preReceiveDerivationsStringP2WPKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2WPKH"); + // final preChangeDerivationsStringP2WPKH = await secureStore.read( + // key: "${testWalletId}_changeDerivationsP2WPKH"); + // + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenThrow(Exception("fake exception")); + // + // bool hasThrown = false; + // try { + // await btc?.fullRescan(2, 1000); + // } catch (_) { + // hasThrown = true; + // } + // expect(hasThrown, true); + // + // // fetch wallet data again + // final receivingAddressesP2PKH = + // await wallet.get('receivingAddressesP2PKH'); + // final receivingAddressesP2SH = await wallet.get('receivingAddressesP2SH'); + // final receivingAddressesP2WPKH = + // await wallet.get('receivingAddressesP2WPKH'); + // final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + // final changeAddressesP2SH = await wallet.get('changeAddressesP2SH'); + // final changeAddressesP2WPKH = await wallet.get('changeAddressesP2WPKH'); + // final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + // final receivingIndexP2SH = await wallet.get('receivingIndexP2SH'); + // final receivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); + // final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + // final changeIndexP2SH = await wallet.get('changeIndexP2SH'); + // final changeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); + // final utxoData = await wallet.get('latest_utxo_model'); + // final receiveDerivationsStringP2PKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2PKH"); + // final changeDerivationsStringP2PKH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + // final receiveDerivationsStringP2SH = + // await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); + // final changeDerivationsStringP2SH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); + // final receiveDerivationsStringP2WPKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2WPKH"); + // final changeDerivationsStringP2WPKH = await secureStore.read( + // key: "${testWalletId}_changeDerivationsP2WPKH"); + // + // expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); + // expect(preReceivingAddressesP2SH, receivingAddressesP2SH); + // expect(preReceivingAddressesP2WPKH, receivingAddressesP2WPKH); + // expect(preChangeAddressesP2PKH, changeAddressesP2PKH); + // expect(preChangeAddressesP2SH, changeAddressesP2SH); + // expect(preChangeAddressesP2WPKH, changeAddressesP2WPKH); + // expect(preReceivingIndexP2PKH, receivingIndexP2PKH); + // expect(preReceivingIndexP2SH, receivingIndexP2SH); + // expect(preReceivingIndexP2WPKH, receivingIndexP2WPKH); + // expect(preChangeIndexP2PKH, changeIndexP2PKH); + // expect(preChangeIndexP2SH, changeIndexP2SH); + // expect(preChangeIndexP2WPKH, changeIndexP2WPKH); + // expect(preUtxoData, utxoData); + // expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); + // expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); + // expect(preReceiveDerivationsStringP2SH, receiveDerivationsStringP2SH); + // expect(preChangeDerivationsStringP2SH, changeDerivationsStringP2SH); + // expect(preReceiveDerivationsStringP2WPKH, receiveDerivationsStringP2WPKH); + // expect(preChangeDerivationsStringP2WPKH, changeDerivationsStringP2WPKH); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(2); + // verify(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoin)) + // .called(1); + // + // verify(client?.getBatchHistory(args: { + // "0": [ + // "bf5a6c56814e80eed11e1e459801515f8c2b83da812568aa9dc26e6356f6965b" + // ] + // })).called(2); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "26f92666caebb9a17b14f5b573b385348cdc80065472b8961091f3226d2f650f" + // ] + // })).called(2); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "06593b2d896751e8dda288bb6587b6bb6a1dee71d82a85457f5654f781e37b12" + // ] + // })).called(2); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "11663d093cb17dfbed4a96d148b22d3e094b31d23c639c2814beb79f2ab0ca75" + // ] + // })).called(2); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "2f18558e5d3015cb6578aee1c3e4b645725fa4e1d26ce22cb31c9949f3b4957c" + // ] + // })).called(2); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "a328ae88ebce63c0010709ae900c199df2b585cdebce53a6291886dfdcc28c63" + // ] + // })).called(1); + // + // expect(secureStore.writes, 19); + // expect(secureStore.reads, 32); + // expect(secureStore.deletes, 12); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + + // test("fetchBuildTxData succeeds", () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] // }); // when(client?.getBatchHistory(args: historyBatchArgs0)) // .thenAnswer((_) async => historyBatchResponse); @@ -3040,19 +2935,19 @@ void main() { // // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("fetchBuildTxData throws", () async { // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // when(client?.getBatchHistory(args: historyBatchArgs0)) // .thenAnswer((_) async => historyBatchResponse); @@ -3120,19 +3015,19 @@ void main() { // // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("build transaction succeeds", () async { // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // when(client?.getBatchHistory(args: historyBatchArgs0)) // .thenAnswer((_) async => historyBatchResponse); @@ -3225,19 +3120,19 @@ void main() { // // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("build transaction fails", () async { // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // when(client?.getBatchHistory(args: historyBatchArgs0)) // .thenAnswer((_) async => historyBatchResponse); @@ -3336,19 +3231,19 @@ void main() { // // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("two output coinSelection succeeds", () async { // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // when(client?.getBatchHistory(args: historyBatchArgs0)) // .thenAnswer((_) async => historyBatchResponse); @@ -3441,19 +3336,19 @@ void main() { // // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("one output option A coinSelection", () async { // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // when(client?.getBatchHistory(args: historyBatchArgs0)) // .thenAnswer((_) async => historyBatchResponse); @@ -3545,19 +3440,19 @@ void main() { // // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("one output option B coinSelection", () async { // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // when(client?.getBatchHistory(args: historyBatchArgs0)) // .thenAnswer((_) async => historyBatchResponse); @@ -3649,19 +3544,19 @@ void main() { // // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("insufficient funds option A coinSelection", () async { // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // when(client?.getBatchHistory(args: historyBatchArgs0)) // .thenAnswer((_) async => historyBatchResponse); @@ -3712,19 +3607,19 @@ void main() { // // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("insufficient funds option B coinSelection", () async { // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // when(client?.getBatchHistory(args: historyBatchArgs0)) // .thenAnswer((_) async => historyBatchResponse); @@ -3775,19 +3670,19 @@ void main() { // // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("insufficient funds option C coinSelection", () async { // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // when(client?.getBatchHistory(args: historyBatchArgs0)) // .thenAnswer((_) async => historyBatchResponse); @@ -3874,19 +3769,19 @@ void main() { // // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("check for more outputs coinSelection", () async { // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // when(client?.getBatchHistory(args: historyBatchArgs0)) // .thenAnswer((_) async => historyBatchResponse); @@ -3980,19 +3875,19 @@ void main() { // // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("prepareSend and confirmSend succeed", () async { // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // when(client?.getBatchHistory(args: historyBatchArgs0)) // .thenAnswer((_) async => historyBatchResponse); @@ -4097,149 +3992,148 @@ void main() { // // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); - test("prepareSend fails", () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs4)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs5)) - .thenAnswer((_) async => historyBatchResponse); - - List dynamicArgValues = []; - - when(client?.getBatchHistory(args: anyNamed("args"))) - .thenAnswer((realInvocation) async { - if (realInvocation.namedArguments.values.first.length == 1) { - dynamicArgValues.add(realInvocation.namedArguments.values.first); - } - - return historyBatchResponse; - }); - - await Hive.openBox(testWalletId); - - when(cachedClient?.getTransaction( - txHash: - "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", - coin: Coin.bitcoin)) - .thenAnswer((_) async => tx9Raw); - when(cachedClient?.getTransaction( - txHash: - "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", - coin: Coin.bitcoin)) - .thenAnswer((_) async => tx10Raw); - when(cachedClient?.getTransaction( - txHash: - "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", - coin: Coin.bitcoin, - )).thenAnswer((_) async => tx11Raw); - - // recover to fill data - await btc?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - // modify addresses to properly mock data to build a tx - final rcv44 = await secureStore?.read( - key: testWalletId + "_receiveDerivationsP2PKH"); - await secureStore?.write( - key: testWalletId + "_receiveDerivationsP2PKH", - value: rcv44?.replaceFirst("1RMSPixoLPuaXuhR2v4HsUMcRjLncKDaw", - "16FuTPaeRSPVxxCnwQmdyx2PQWxX6HWzhQ")); - final rcv49 = await secureStore?.read( - key: testWalletId + "_receiveDerivationsP2SH"); - await secureStore?.write( - key: testWalletId + "_receiveDerivationsP2SH", - value: rcv49?.replaceFirst("3AV74rKfibWmvX34F99yEvUcG4LLQ9jZZk", - "36NvZTcMsMowbt78wPzJaHHWaNiyR73Y4g")); - final rcv84 = await secureStore?.read( - key: testWalletId + "_receiveDerivationsP2WPKH"); - await secureStore?.write( - key: testWalletId + "_receiveDerivationsP2WPKH", - value: rcv84?.replaceFirst( - "bc1qggtj4ka8jsaj44hhd5mpamx7mp34m2d3w7k0m0", - "bc1q42lja79elem0anu8q8s3h2n687re9jax556pcc")); - - btc?.outputsList = utxoList; - - bool didThrow = false; - try { - await btc?.prepareSend( - address: "bc1q42lja79elem0anu8q8s3h2n687re9jax556pcc", - satoshiAmount: 15000); - } catch (_) { - didThrow = true; - } - - expect(didThrow, true); - - verify(client?.getServerFeatures()).called(1); - - /// verify transaction no matching calls - - // verify(cachedClient?.getTransaction( - // txHash: - // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", - // coin: Coin.bitcoin, - // callOutSideMainIsolate: false)) - // .called(1); - // verify(cachedClient?.getTransaction( - // txHash: - // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", - // coin: Coin.bitcoin, - // callOutSideMainIsolate: false)) - // .called(1); - // verify(cachedClient?.getTransaction( - // txHash: - // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", - // coin: Coin.bitcoin, - // callOutSideMainIsolate: false)) - // .called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); - - for (final arg in dynamicArgValues) { - final map = Map>.from(arg as Map); - - verify(client?.getBatchHistory(args: map)).called(1); - expect(activeScriptHashes.contains(map.values.first.first as String), - true); - } - - expect(secureStore?.interactions, 20); - expect(secureStore?.writes, 10); - expect(secureStore?.reads, 10); - expect(secureStore?.deletes, 0); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); + // test("prepareSend fails", () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs4)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs5)) + // .thenAnswer((_) async => historyBatchResponse); + // + // List dynamicArgValues = []; + // + // when(client?.getBatchHistory(args: anyNamed("args"))) + // .thenAnswer((realInvocation) async { + // if (realInvocation.namedArguments.values.first.length == 1) { + // dynamicArgValues.add(realInvocation.namedArguments.values.first); + // } + // + // return historyBatchResponse; + // }); + // + // await Hive.openBox(testWalletId); + // + // when(cachedClient?.getTransaction( + // txHash: + // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // coin: Coin.bitcoin)) + // .thenAnswer((_) async => tx9Raw); + // when(cachedClient?.getTransaction( + // txHash: + // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // coin: Coin.bitcoin)) + // .thenAnswer((_) async => tx10Raw); + // when(cachedClient?.getTransaction( + // txHash: + // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // coin: Coin.bitcoin, + // )).thenAnswer((_) async => tx11Raw); + // + // // recover to fill data + // await btc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // // modify addresses to properly mock data to build a tx + // final rcv44 = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2PKH"); + // await secureStore.write( + // key: "${testWalletId}_receiveDerivationsP2PKH", + // value: rcv44?.replaceFirst("1RMSPixoLPuaXuhR2v4HsUMcRjLncKDaw", + // "16FuTPaeRSPVxxCnwQmdyx2PQWxX6HWzhQ")); + // final rcv49 = + // await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); + // await secureStore.write( + // key: "${testWalletId}_receiveDerivationsP2SH", + // value: rcv49?.replaceFirst("3AV74rKfibWmvX34F99yEvUcG4LLQ9jZZk", + // "36NvZTcMsMowbt78wPzJaHHWaNiyR73Y4g")); + // final rcv84 = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2WPKH"); + // await secureStore.write( + // key: "${testWalletId}_receiveDerivationsP2WPKH", + // value: rcv84?.replaceFirst( + // "bc1qggtj4ka8jsaj44hhd5mpamx7mp34m2d3w7k0m0", + // "bc1q42lja79elem0anu8q8s3h2n687re9jax556pcc")); + // + // // btc?.outputsList = utxoList; + // + // bool didThrow = false; + // try { + // await btc?.prepareSend( + // address: "bc1q42lja79elem0anu8q8s3h2n687re9jax556pcc", + // satoshiAmount: 15000); + // } catch (_) { + // didThrow = true; + // } + // + // expect(didThrow, true); + // + // verify(client?.getServerFeatures()).called(1); + // + // /// verify transaction no matching calls + // + // // verify(cachedClient?.getTransaction( + // // txHash: + // // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // // coin: Coin.bitcoin, + // // callOutSideMainIsolate: false)) + // // .called(1); + // // verify(cachedClient?.getTransaction( + // // txHash: + // // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // // coin: Coin.bitcoin, + // // callOutSideMainIsolate: false)) + // // .called(1); + // // verify(cachedClient?.getTransaction( + // // txHash: + // // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // // coin: Coin.bitcoin, + // // callOutSideMainIsolate: false)) + // // .called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); + // + // for (final arg in dynamicArgValues) { + // final map = Map>.from(arg as Map); + // + // verify(client?.getBatchHistory(args: map)).called(1); + // expect(activeScriptHashes.contains(map.values.first.first as String), + // true); + // } + // + // expect(secureStore.interactions, 20); + // expect(secureStore.writes, 10); + // expect(secureStore.reads, 10); + // expect(secureStore.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); test("confirmSend no hex", () async { bool didThrow = false; @@ -4251,10 +4145,9 @@ void main() { expect(didThrow, true); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("confirmSend hex is not string", () async { @@ -4267,10 +4160,9 @@ void main() { expect(didThrow, true); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("confirmSend hex is string but missing other data", () async { @@ -4287,10 +4179,9 @@ void main() { rawTx: "a string", requestID: anyNamed("requestID"))) .called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("confirmSend fails due to vSize being greater than fee", () async { @@ -4308,10 +4199,9 @@ void main() { rawTx: "a string", requestID: anyNamed("requestID"))) .called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("confirmSend fails when broadcast transactions throws", () async { @@ -4333,11 +4223,10 @@ void main() { rawTx: "a string", requestID: anyNamed("requestID"))) .called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); // // // this test will create a non mocked electrumx client that will try to connect @@ -4350,12 +4239,12 @@ void main() { // // networkType: BasicNetworkType.test, // // client: client, // // cachedClient: cachedClient, - // // priceAPI: priceAPI, + // // // // secureStore: secureStore, // // ); // // // // // set node - // // final wallet = await Hive.openBox(testWalletId); + // // final wallet = await Hive.openBox (testWalletId); // // await wallet.put("nodes", { // // "default": { // // "id": "some nodeID", @@ -4386,150 +4275,144 @@ void main() { // // expect(secureStore.interactions, 0); // // verifyNoMoreInteractions(client); // // verifyNoMoreInteractions(cachedClient); - // // verifyNoMoreInteractions(priceAPI); + // // // // }); - test("refresh wallet mutex locked", () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs4)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs5)) - .thenAnswer((_) async => historyBatchResponse); - - List dynamicArgValues = []; - - when(client?.getBatchHistory(args: anyNamed("args"))) - .thenAnswer((realInvocation) async { - if (realInvocation.namedArguments.values.first.length == 1) { - dynamicArgValues.add(realInvocation.namedArguments.values.first); - } - - return historyBatchResponse; - }); - - await Hive.openBox(testWalletId); - - // recover to fill data - await btc?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - btc?.refreshMutex = true; - - await btc?.refresh(); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); - - for (final arg in dynamicArgValues) { - final map = Map>.from(arg as Map); - - verify(client?.getBatchHistory(args: map)).called(1); - expect(activeScriptHashes.contains(map.values.first.first as String), - true); - } - - expect(secureStore?.interactions, 14); - expect(secureStore?.writes, 7); - expect(secureStore?.reads, 7); - expect(secureStore?.deletes, 0); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); - }); - - test("refresh wallet normally", () async { - when(client?.getBlockHeadTip()).thenAnswer((realInvocation) async => - {"height": 520481, "hex": "some block hex"}); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getHistory(scripthash: anyNamed("scripthash"))) - .thenAnswer((_) async => []); - when(client?.estimateFee(blocks: anyNamed("blocks"))) - .thenAnswer((_) async => Decimal.one); - - when(priceAPI?.getPricesAnd24hChange(baseCurrency: "USD")) - .thenAnswer((_) async => {Coin.bitcoin: Tuple2(Decimal.one, 0.3)}); - - final List dynamicArgValues = []; - - when(client?.getBatchHistory(args: anyNamed("args"))) - .thenAnswer((realInvocation) async { - dynamicArgValues.add(realInvocation.namedArguments.values.first); - return historyBatchResponse; - }); - - await Hive.openBox(testWalletId); - - // recover to fill data - await btc?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - when(client?.getBatchHistory(args: anyNamed("args"))) - .thenAnswer((_) async => {}); - when(client?.getBatchUTXOs(args: anyNamed("args"))) - .thenAnswer((_) async => emptyHistoryBatchResponse); - - await btc?.refresh(); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(4); - verify(client?.estimateFee(blocks: anyNamed("blocks"))).called(3); - verify(client?.getBlockHeadTip()).called(1); - verify(priceAPI?.getPricesAnd24hChange(baseCurrency: "USD")).called(2); - - for (final arg in dynamicArgValues) { - final map = Map>.from(arg as Map); - - verify(client?.getBatchHistory(args: map)).called(1); - } - - expect(secureStore?.interactions, 14); - expect(secureStore?.writes, 7); - expect(secureStore?.reads, 7); - expect(secureStore?.deletes, 0); - - // verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); + // test("refresh wallet mutex locked", () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs4)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs5)) + // .thenAnswer((_) async => historyBatchResponse); + // + // List dynamicArgValues = []; + // + // when(client?.getBatchHistory(args: anyNamed("args"))) + // .thenAnswer((realInvocation) async { + // if (realInvocation.namedArguments.values.first.length == 1) { + // dynamicArgValues.add(realInvocation.namedArguments.values.first); + // } + // + // return historyBatchResponse; + // }); + // + // await Hive.openBox(testWalletId); + // + // // recover to fill data + // await btc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // btc?.refreshMutex = true; + // + // await btc?.refresh(); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); + // + // for (final arg in dynamicArgValues) { + // final map = Map>.from(arg as Map); + // + // verify(client?.getBatchHistory(args: map)).called(1); + // expect(activeScriptHashes.contains(map.values.first.first as String), + // true); + // } + // + // expect(secureStore.interactions, 14); + // expect(secureStore.writes, 7); + // expect(secureStore.reads, 7); + // expect(secureStore.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // }); + // + // test("refresh wallet normally", () async { + // when(client?.getBlockHeadTip()).thenAnswer((realInvocation) async => + // {"height": 520481, "hex": "some block hex"}); + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getHistory(scripthash: anyNamed("scripthash"))) + // .thenAnswer((_) async => []); + // when(client?.estimateFee(blocks: anyNamed("blocks"))) + // .thenAnswer((_) async => Decimal.one); + // + // final List dynamicArgValues = []; + // + // when(client?.getBatchHistory(args: anyNamed("args"))) + // .thenAnswer((realInvocation) async { + // dynamicArgValues.add(realInvocation.namedArguments.values.first); + // return historyBatchResponse; + // }); + // + // await Hive.openBox(testWalletId); + // + // // recover to fill data + // await btc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // when(client?.getBatchHistory(args: anyNamed("args"))) + // .thenAnswer((_) async => {}); + // when(client?.getBatchUTXOs(args: anyNamed("args"))) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // + // await btc?.refresh(); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(4); + // verify(client?.estimateFee(blocks: anyNamed("blocks"))).called(3); + // verify(client?.getBlockHeadTip()).called(1); + // + // for (final arg in dynamicArgValues) { + // final map = Map>.from(arg as Map); + // + // verify(client?.getBatchHistory(args: map)).called(1); + // } + // + // expect(secureStore.interactions, 14); + // expect(secureStore.writes, 7); + // expect(secureStore.reads, 7); + // expect(secureStore.deletes, 0); + // + // // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); tearDown(() async { await tearDownTestHive(); diff --git a/test/services/coins/bitcoin/bitcoin_wallet_test.mocks.dart b/test/services/coins/bitcoin/bitcoin_wallet_test.mocks.dart index 32a6c7195..93eb0e1c8 100644 --- a/test/services/coins/bitcoin/bitcoin_wallet_test.mocks.dart +++ b/test/services/coins/bitcoin/bitcoin_wallet_test.mocks.dart @@ -3,19 +3,16 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i6; +import 'dart:async' as _i5; import 'package:decimal/decimal.dart' as _i2; -import 'package:http/http.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i7; -import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i5; -import 'package:stackwallet/services/price.dart' as _i9; +import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i6; +import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i4; import 'package:stackwallet/services/transaction_notification_tracker.dart' - as _i11; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i8; + as _i8; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i7; import 'package:stackwallet/utilities/prefs.dart' as _i3; -import 'package:tuple/tuple.dart' as _i10; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -48,26 +45,16 @@ class _FakePrefs_1 extends _i1.SmartFake implements _i3.Prefs { ); } -class _FakeClient_2 extends _i1.SmartFake implements _i4.Client { - _FakeClient_2( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - /// A class which mocks [ElectrumX]. /// /// See the documentation for Mockito's code generation for more information. -class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { +class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { MockElectrumX() { _i1.throwOnMissingStub(this); } @override - set failovers(List<_i5.ElectrumXNode>? _failovers) => super.noSuchMethod( + set failovers(List<_i4.ElectrumXNode>? _failovers) => super.noSuchMethod( Invocation.setter( #failovers, _failovers, @@ -103,7 +90,7 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { returnValue: false, ) as bool); @override - _i6.Future request({ + _i5.Future request({ required String? command, List? args = const [], Duration? connectionTimeout = const Duration(seconds: 60), @@ -122,10 +109,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #retries: retries, }, ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i5.Future.value(), + ) as _i5.Future); @override - _i6.Future>> batchRequest({ + _i5.Future>> batchRequest({ required String? command, required Map>? args, Duration? connectionTimeout = const Duration(seconds: 60), @@ -142,11 +129,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #retries: retries, }, ), - returnValue: _i6.Future>>.value( + returnValue: _i5.Future>>.value( >[]), - ) as _i6.Future>>); + ) as _i5.Future>>); @override - _i6.Future ping({ + _i5.Future ping({ String? requestID, int? retryCount = 1, }) => @@ -159,10 +146,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #retryCount: retryCount, }, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i5.Future.value(false), + ) as _i5.Future); @override - _i6.Future> getBlockHeadTip({String? requestID}) => + _i5.Future> getBlockHeadTip({String? requestID}) => (super.noSuchMethod( Invocation.method( #getBlockHeadTip, @@ -170,10 +157,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future> getServerFeatures({String? requestID}) => + _i5.Future> getServerFeatures({String? requestID}) => (super.noSuchMethod( Invocation.method( #getServerFeatures, @@ -181,10 +168,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future broadcastTransaction({ + _i5.Future broadcastTransaction({ required String? rawTx, String? requestID, }) => @@ -197,10 +184,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #requestID: requestID, }, ), - returnValue: _i6.Future.value(''), - ) as _i6.Future); + returnValue: _i5.Future.value(''), + ) as _i5.Future); @override - _i6.Future> getBalance({ + _i5.Future> getBalance({ required String? scripthash, String? requestID, }) => @@ -214,10 +201,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future>> getHistory({ + _i5.Future>> getHistory({ required String? scripthash, String? requestID, }) => @@ -230,11 +217,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #requestID: requestID, }, ), - returnValue: _i6.Future>>.value( + returnValue: _i5.Future>>.value( >[]), - ) as _i6.Future>>); + ) as _i5.Future>>); @override - _i6.Future>>> getBatchHistory( + _i5.Future>>> getBatchHistory( {required Map>? args}) => (super.noSuchMethod( Invocation.method( @@ -242,11 +229,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { [], {#args: args}, ), - returnValue: _i6.Future>>>.value( + returnValue: _i5.Future>>>.value( >>{}), - ) as _i6.Future>>>); + ) as _i5.Future>>>); @override - _i6.Future>> getUTXOs({ + _i5.Future>> getUTXOs({ required String? scripthash, String? requestID, }) => @@ -259,11 +246,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #requestID: requestID, }, ), - returnValue: _i6.Future>>.value( + returnValue: _i5.Future>>.value( >[]), - ) as _i6.Future>>); + ) as _i5.Future>>); @override - _i6.Future>>> getBatchUTXOs( + _i5.Future>>> getBatchUTXOs( {required Map>? args}) => (super.noSuchMethod( Invocation.method( @@ -271,11 +258,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { [], {#args: args}, ), - returnValue: _i6.Future>>>.value( + returnValue: _i5.Future>>>.value( >>{}), - ) as _i6.Future>>>); + ) as _i5.Future>>>); @override - _i6.Future> getTransaction({ + _i5.Future> getTransaction({ required String? txHash, bool? verbose = true, String? requestID, @@ -291,10 +278,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future> getAnonymitySet({ + _i5.Future> getAnonymitySet({ String? groupId = r'1', String? blockhash = r'', String? requestID, @@ -310,10 +297,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future getMintData({ + _i5.Future getMintData({ dynamic mints, String? requestID, }) => @@ -326,10 +313,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #requestID: requestID, }, ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i5.Future.value(), + ) as _i5.Future); @override - _i6.Future> getUsedCoinSerials({ + _i5.Future> getUsedCoinSerials({ String? requestID, required int? startNumber, }) => @@ -343,19 +330,19 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future getLatestCoinId({String? requestID}) => (super.noSuchMethod( + _i5.Future getLatestCoinId({String? requestID}) => (super.noSuchMethod( Invocation.method( #getLatestCoinId, [], {#requestID: requestID}, ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i5.Future.value(0), + ) as _i5.Future); @override - _i6.Future> getFeeRate({String? requestID}) => + _i5.Future> getFeeRate({String? requestID}) => (super.noSuchMethod( Invocation.method( #getFeeRate, @@ -363,10 +350,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future<_i2.Decimal> estimateFee({ + _i5.Future<_i2.Decimal> estimateFee({ String? requestID, required int? blocks, }) => @@ -379,7 +366,7 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #blocks: blocks, }, ), - returnValue: _i6.Future<_i2.Decimal>.value(_FakeDecimal_0( + returnValue: _i5.Future<_i2.Decimal>.value(_FakeDecimal_0( this, Invocation.method( #estimateFee, @@ -390,15 +377,15 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), )), - ) as _i6.Future<_i2.Decimal>); + ) as _i5.Future<_i2.Decimal>); @override - _i6.Future<_i2.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( + _i5.Future<_i2.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( Invocation.method( #relayFee, [], {#requestID: requestID}, ), - returnValue: _i6.Future<_i2.Decimal>.value(_FakeDecimal_0( + returnValue: _i5.Future<_i2.Decimal>.value(_FakeDecimal_0( this, Invocation.method( #relayFee, @@ -406,13 +393,13 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { {#requestID: requestID}, ), )), - ) as _i6.Future<_i2.Decimal>); + ) as _i5.Future<_i2.Decimal>); } /// A class which mocks [CachedElectrumX]. /// /// See the documentation for Mockito's code generation for more information. -class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { +class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { MockCachedElectrumX() { _i1.throwOnMissingStub(this); } @@ -441,15 +428,15 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { ), ) as _i3.Prefs); @override - List<_i5.ElectrumXNode> get failovers => (super.noSuchMethod( + List<_i4.ElectrumXNode> get failovers => (super.noSuchMethod( Invocation.getter(#failovers), - returnValue: <_i5.ElectrumXNode>[], - ) as List<_i5.ElectrumXNode>); + returnValue: <_i4.ElectrumXNode>[], + ) as List<_i4.ElectrumXNode>); @override - _i6.Future> getAnonymitySet({ + _i5.Future> getAnonymitySet({ required String? groupId, String? blockhash = r'', - required _i8.Coin? coin, + required _i7.Coin? coin, }) => (super.noSuchMethod( Invocation.method( @@ -462,8 +449,8 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override String base64ToHex(String? source) => (super.noSuchMethod( Invocation.method( @@ -481,9 +468,9 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { returnValue: '', ) as String); @override - _i6.Future> getTransaction({ + _i5.Future> getTransaction({ required String? txHash, - required _i8.Coin? coin, + required _i7.Coin? coin, bool? verbose = true, }) => (super.noSuchMethod( @@ -497,11 +484,11 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future> getUsedCoinSerials({ - required _i8.Coin? coin, + _i5.Future> getUsedCoinSerials({ + required _i7.Coin? coin, int? startNumber = 0, }) => (super.noSuchMethod( @@ -513,66 +500,26 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { #startNumber: startNumber, }, ), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i5.Future>.value([]), + ) as _i5.Future>); @override - _i6.Future clearSharedTransactionCache({required _i8.Coin? coin}) => + _i5.Future clearSharedTransactionCache({required _i7.Coin? coin}) => (super.noSuchMethod( Invocation.method( #clearSharedTransactionCache, [], {#coin: coin}, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); -} - -/// A class which mocks [PriceAPI]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockPriceAPI extends _i1.Mock implements _i9.PriceAPI { - MockPriceAPI() { - _i1.throwOnMissingStub(this); - } - - @override - _i4.Client get client => (super.noSuchMethod( - Invocation.getter(#client), - returnValue: _FakeClient_2( - this, - Invocation.getter(#client), - ), - ) as _i4.Client); - @override - void resetLastCalledToForceNextCallToUpdateCache() => super.noSuchMethod( - Invocation.method( - #resetLastCalledToForceNextCallToUpdateCache, - [], - ), - returnValueForMissingStub: null, - ); - @override - _i6.Future< - Map<_i8.Coin, _i10.Tuple2<_i2.Decimal, double>>> getPricesAnd24hChange( - {required String? baseCurrency}) => - (super.noSuchMethod( - Invocation.method( - #getPricesAnd24hChange, - [], - {#baseCurrency: baseCurrency}, - ), - returnValue: - _i6.Future>>.value( - <_i8.Coin, _i10.Tuple2<_i2.Decimal, double>>{}), - ) as _i6.Future>>); + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); } /// A class which mocks [TransactionNotificationTracker]. /// /// See the documentation for Mockito's code generation for more information. class MockTransactionNotificationTracker extends _i1.Mock - implements _i11.TransactionNotificationTracker { + implements _i8.TransactionNotificationTracker { MockTransactionNotificationTracker() { _i1.throwOnMissingStub(this); } @@ -601,14 +548,14 @@ class MockTransactionNotificationTracker extends _i1.Mock returnValue: false, ) as bool); @override - _i6.Future addNotifiedPending(String? txid) => (super.noSuchMethod( + _i5.Future addNotifiedPending(String? txid) => (super.noSuchMethod( Invocation.method( #addNotifiedPending, [txid], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); @override bool wasNotifiedConfirmed(String? txid) => (super.noSuchMethod( Invocation.method( @@ -618,12 +565,21 @@ class MockTransactionNotificationTracker extends _i1.Mock returnValue: false, ) as bool); @override - _i6.Future addNotifiedConfirmed(String? txid) => (super.noSuchMethod( + _i5.Future addNotifiedConfirmed(String? txid) => (super.noSuchMethod( Invocation.method( #addNotifiedConfirmed, [txid], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future deleteTransaction(String? txid) => (super.noSuchMethod( + Invocation.method( + #deleteTransaction, + [txid], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); } diff --git a/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart b/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart index e469662dd..661c9a1b7 100644 --- a/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart +++ b/test/services/coins/bitcoincash/bitcoincash_wallet_test.dart @@ -4,31 +4,38 @@ import 'package:hive/hive.dart'; import 'package:hive_test/hive_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; +import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; -import 'package:stackwallet/hive/db.dart'; import 'package:stackwallet/models/paymint/fee_object_model.dart'; -import 'package:stackwallet/models/paymint/transactions_model.dart'; -import 'package:stackwallet/models/paymint/utxo_model.dart'; import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart'; -import 'package:stackwallet/services/price.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; -import 'bitcoincash_history_sample_data.dart'; import 'bitcoincash_wallet_test.mocks.dart'; import 'bitcoincash_wallet_test_parameters.dart'; -@GenerateMocks( - [ElectrumX, CachedElectrumX, PriceAPI, TransactionNotificationTracker]) -void main() { +@GenerateMocks([ + ElectrumX, + CachedElectrumX, + TransactionNotificationTracker, +]) +void main() async { group("bitcoincash constants", () { test("bitcoincash minimum confirmations", () async { - expect(MINIMUM_CONFIRMATIONS, 1); + expect(MINIMUM_CONFIRMATIONS, 0); }); test("bitcoincash dust limit", () async { - expect(DUST_LIMIT, 546); + expect( + DUST_LIMIT, + Amount( + rawValue: BigInt.from(546), + fractionDigits: 8, + ), + ); }); test("bitcoincash mainnet genesis block hash", () async { expect(GENESIS_HASH_MAINNET, @@ -41,29 +48,22 @@ void main() { }); }); - test("bitcoincash DerivePathType enum", () { - expect(DerivePathType.values.length, 2); - expect(DerivePathType.values.toString(), - "[DerivePathType.bip44, DerivePathType.bip49]"); - }); - - group("bip32 node/root", () { - test("getBip32Root", () { - final root = getBip32Root(TEST_MNEMONIC, bitcoincash); - expect(root.toWIF(), ROOT_WIF); - }); - - test("basic getBip32Node", () { - final node = - getBip32Node(0, 0, TEST_MNEMONIC, bitcoincash, DerivePathType.bip44); - expect(node.toWIF(), NODE_WIF_44); - }); - }); + // group("bip32 node/root", () { + // test("getBip32Root", () { + // final root = getBip32Root(TEST_MNEMONIC, bitcoincash); + // expect(root.toWIF(), ROOT_WIF); + // }); + // + // test("basic getBip32Node", () { + // final node = + // getBip32Node(0, 0, TEST_MNEMONIC, bitcoincash, DerivePathType.bip44); + // expect(node.toWIF(), NODE_WIF_44); + // }); + // }); group("mainnet bitcoincash addressType", () { MockElectrumX? client; MockCachedElectrumX? cachedClient; - MockPriceAPI? priceAPI; late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; @@ -72,7 +72,6 @@ void main() { setUp(() { client = MockElectrumX(); cachedClient = MockCachedElectrumX(); - priceAPI = MockPriceAPI(); secureStore = FakeSecureStorage(); tracker = MockTransactionNotificationTracker(); @@ -83,7 +82,6 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, ); }); @@ -93,11 +91,10 @@ void main() { mainnetWallet?.addressType( address: "1DP3PUePwMa5CoZwzjznVKhzdLsZftjcAT"), DerivePathType.bip44); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("invalid base58 address type", () { @@ -105,11 +102,10 @@ void main() { () => mainnetWallet?.addressType( address: "mhqpGtwhcR6gFuuRjLTpHo41919QfuGy8Y"), throwsArgumentError); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("invalid bech32 address type", () { @@ -117,11 +113,10 @@ void main() { () => mainnetWallet?.addressType( address: "tb1qzzlm6mnc8k54mx6akehl8p9ray8r439va5ndyq"), throwsArgumentError); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("address has no matching script", () { @@ -129,11 +124,10 @@ void main() { () => mainnetWallet?.addressType( address: "mpMk94ETazqonHutyC1v6ajshgtP8oiFKU"), throwsArgumentError); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("P2PKH cashaddr with prefix", () { @@ -142,11 +136,10 @@ void main() { address: "bitcoincash:qrwjyc4pewj9utzrtnh0whkzkuvy5q8wg52n254x6k"), DerivePathType.bip44); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("P2PKH cashaddr without prefix", () { @@ -154,11 +147,10 @@ void main() { mainnetWallet?.addressType( address: "qrwjyc4pewj9utzrtnh0whkzkuvy5q8wg52n254x6k"), DerivePathType.bip44); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("Multisig cashaddr with prefix", () { @@ -167,11 +159,10 @@ void main() { address: "bitcoincash:pzpp3nchmzzf0gr69lj82ymurg5u3ds6kcwr5m07np"), throwsArgumentError); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("Multisig cashaddr without prefix", () { @@ -179,30 +170,17 @@ void main() { () => mainnetWallet?.addressType( address: "pzpp3nchmzzf0gr69lj82ymurg5u3ds6kcwr5m07np"), throwsArgumentError); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); - }); - - test("Multisig/P2SH address", () { - expect( - mainnetWallet?.addressType( - address: "3DYuVEmuKWQFxJcF7jDPhwPiXLTiNnyMFb"), - DerivePathType.bip49); - expect(secureStore?.interactions, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); }); group("validate mainnet bitcoincash addresses", () { MockElectrumX? client; MockCachedElectrumX? cachedClient; - MockPriceAPI? priceAPI; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; @@ -211,7 +189,7 @@ void main() { setUp(() { client = MockElectrumX(); cachedClient = MockCachedElectrumX(); - priceAPI = MockPriceAPI(); + secureStore = FakeSecureStorage(); tracker = MockTransactionNotificationTracker(); @@ -222,7 +200,6 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, ); }); @@ -231,11 +208,10 @@ void main() { expect( mainnetWallet?.validateAddress("1DP3PUePwMa5CoZwzjznVKhzdLsZftjcAT"), true); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("valid mainnet legacy/p2pkh cashaddr with prefix address type", () { @@ -243,11 +219,10 @@ void main() { mainnetWallet?.validateAddress( "bitcoincash:qrwjyc4pewj9utzrtnh0whkzkuvy5q8wg52n254x6k"), true); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("valid mainnet legacy/p2pkh cashaddr without prefix address type", () { @@ -255,22 +230,20 @@ void main() { mainnetWallet ?.validateAddress("qrwjyc4pewj9utzrtnh0whkzkuvy5q8wg52n254x6k"), true); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("invalid legacy/p2pkh address type", () { expect( mainnetWallet?.validateAddress("mhqpGtwhcR6gFuuRjLTpHo41919QfuGy8Y"), false); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test( @@ -280,40 +253,37 @@ void main() { mainnetWallet ?.validateAddress("pzpp3nchmzzf0gr69lj82ymurg5u3ds6kcwr5m07np"), false); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("multisig address should fail for bitbox", () { expect( mainnetWallet?.validateAddress("3DYuVEmuKWQFxJcF7jDPhwPiXLTiNnyMFb"), false); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("invalid mainnet bitcoincash legacy/p2pkh address", () { expect( mainnetWallet?.validateAddress("mhqpGtwhcR6gFuuRjLTpHo41919QfuGy8Y"), false); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); }); group("testNetworkConnection", () { MockElectrumX? client; MockCachedElectrumX? cachedClient; - MockPriceAPI? priceAPI; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; @@ -322,7 +292,7 @@ void main() { setUp(() { client = MockElectrumX(); cachedClient = MockCachedElectrumX(); - priceAPI = MockPriceAPI(); + secureStore = FakeSecureStorage(); tracker = MockTransactionNotificationTracker(); @@ -333,7 +303,6 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, ); }); @@ -342,47 +311,44 @@ void main() { when(client?.ping()).thenAnswer((_) async => false); final bool? result = await bch?.testNetworkConnection(); expect(result, false); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verify(client?.ping()).called(1); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("attempted connection fails due to exception", () async { when(client?.ping()).thenThrow(Exception); final bool? result = await bch?.testNetworkConnection(); expect(result, false); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verify(client?.ping()).called(1); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("attempted connection test success", () async { when(client?.ping()).thenAnswer((_) async => true); final bool? result = await bch?.testNetworkConnection(); expect(result, true); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verify(client?.ping()).called(1); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); }); group("basic getters, setters, and functions", () { - final bchcoin = Coin.bitcoincash; - final testWalletId = "BCHtestWalletID"; - final testWalletName = "BCHWallet"; + const bchcoin = Coin.bitcoincash; + const testWalletId = "BCHtestWalletID"; + const testWalletName = "BCHWallet"; MockElectrumX? client; MockCachedElectrumX? cachedClient; - MockPriceAPI? priceAPI; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; @@ -391,7 +357,7 @@ void main() { setUp(() async { client = MockElectrumX(); cachedClient = MockCachedElectrumX(); - priceAPI = MockPriceAPI(); + secureStore = FakeSecureStorage(); tracker = MockTransactionNotificationTracker(); @@ -402,18 +368,16 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, ); }); test("get networkType main", () async { expect(bch?.coin, bchcoin); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("get networkType test", () async { @@ -424,53 +388,47 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, ); expect(bch?.coin, bchcoin); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("get cryptoCurrency", () async { expect(Coin.bitcoincash, Coin.bitcoincash); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("get coinName", () async { expect(Coin.bitcoincash, Coin.bitcoincash); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("get coinTicker", () async { expect(Coin.bitcoincash, Coin.bitcoincash); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("get and set walletName", () async { expect(Coin.bitcoincash, Coin.bitcoincash); bch?.walletName = "new name"; expect(bch?.walletName, "new name"); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("estimateTxFee", () async { @@ -482,24 +440,23 @@ void main() { expect(bch?.estimateTxFee(vSize: 356, feeRatePerKB: 1699), 712); expect(bch?.estimateTxFee(vSize: 356, feeRatePerKB: 2000), 712); expect(bch?.estimateTxFee(vSize: 356, feeRatePerKB: 12345), 4628); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("get fees succeeds", () async { when(client?.ping()).thenAnswer((_) async => true); when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_TESTNET, "hash_function": "sha256", - "services": [] + "services": [] }); when(client?.estimateFee(blocks: 1)) .thenAnswer((realInvocation) async => Decimal.zero); @@ -517,24 +474,23 @@ void main() { verify(client?.estimateFee(blocks: 1)).called(1); verify(client?.estimateFee(blocks: 5)).called(1); verify(client?.estimateFee(blocks: 20)).called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("get fees fails", () async { when(client?.ping()).thenAnswer((_) async => true); when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_TESTNET, "hash_function": "sha256", - "services": [] + "services": [] }); when(client?.estimateFee(blocks: 1)) .thenAnswer((realInvocation) async => Decimal.zero); @@ -555,57 +511,24 @@ void main() { verify(client?.estimateFee(blocks: 1)).called(1); verify(client?.estimateFee(blocks: 5)).called(1); verify(client?.estimateFee(blocks: 20)).called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); - }); - - test("get maxFee", () async { - when(client?.ping()).thenAnswer((_) async => true); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_TESTNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.estimateFee(blocks: 20)) - .thenAnswer((realInvocation) async => Decimal.zero); - when(client?.estimateFee(blocks: 5)) - .thenAnswer((realInvocation) async => Decimal.one); - when(client?.estimateFee(blocks: 1)) - .thenAnswer((realInvocation) async => Decimal.ten); - - final maxFee = await bch?.maxFee; - expect(maxFee, 1000000000); - - verify(client?.estimateFee(blocks: 1)).called(1); - verify(client?.estimateFee(blocks: 5)).called(1); - verify(client?.estimateFee(blocks: 20)).called(1); - expect(secureStore?.interactions, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); }); group("BCHWallet service class functions that depend on shared storage", () { - final bchcoin = Coin.bitcoincash; - final bchtestcoin = Coin.bitcoincashTestnet; - final testWalletId = "BCHtestWalletID"; - final testWalletName = "BCHWallet"; + const bchcoin = Coin.bitcoincash; + const bchtestcoin = Coin.bitcoincashTestnet; + const testWalletId = "BCHtestWalletID"; + const testWalletName = "BCHWallet"; bool hiveAdaptersRegistered = false; MockElectrumX? client; MockCachedElectrumX? cachedClient; - MockPriceAPI? priceAPI; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; @@ -616,25 +539,13 @@ void main() { if (!hiveAdaptersRegistered) { hiveAdaptersRegistered = true; - // Registering Transaction Model Adapters - Hive.registerAdapter(TransactionDataAdapter()); - Hive.registerAdapter(TransactionChunkAdapter()); - Hive.registerAdapter(TransactionAdapter()); - Hive.registerAdapter(InputAdapter()); - Hive.registerAdapter(OutputAdapter()); - - // Registering Utxo Model Adapters - Hive.registerAdapter(UtxoDataAdapter()); - Hive.registerAdapter(UtxoObjectAdapter()); - Hive.registerAdapter(StatusAdapter()); - - final wallets = await Hive.openBox('wallets'); + final wallets = await Hive.openBox('wallets'); await wallets.put('currentWalletName', testWalletName); } client = MockElectrumX(); cachedClient = MockCachedElectrumX(); - priceAPI = MockPriceAPI(); + secureStore = FakeSecureStorage(); tracker = MockTransactionNotificationTracker(); @@ -645,7 +556,6 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, ); }); @@ -659,7 +569,7 @@ void main() { // verify(client?.ping()).called(0); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // test("initializeExisting no network exception", () async { @@ -670,19 +580,19 @@ void main() { // verify(client?.ping()).called(1); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // test("initializeNew mainnet throws bad network", () async { // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_TESTNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // // await Hive.openBox(testWalletId); @@ -695,49 +605,48 @@ void main() { // verify(client?.getServerFeatures()).called(1); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // }); test("initializeNew throws mnemonic overwrite exception", () async { when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_MAINNET, "hash_function": "sha256", - "services": [] + "services": [] }); - await secureStore?.write( + await secureStore.write( key: "${testWalletId}_mnemonic", value: "some mnemonic"); await Hive.openBox(testWalletId); await Hive.openBox(DB.boxNamePrefs); - expectLater(() => bch?.initializeNew(), throwsA(isA())) + await expectLater(() => bch?.initializeNew(), throwsA(isA())) .then((_) { - expect(secureStore?.interactions, 2); + expect(secureStore.interactions, 2); verifyNever(client?.ping()).called(0); verify(client?.getServerFeatures()).called(1); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); }); // test("initializeExisting testnet throws bad network", () async { // when(client?.ping()).thenAnswer((_) async => true); // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_TESTNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // // bch = BitcoinCashWallet( @@ -747,8 +656,9 @@ void main() { // client: client!, // cachedClient: cachedClient!, // tracker: tracker!, - // priceAPI: priceAPI, + // // secureStore: secureStore, + // // ); // // await Hive.openBox(testWalletId); @@ -761,7 +671,7 @@ void main() { // verify(client?.getServerFeatures()).called(1); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // }); @@ -770,14 +680,14 @@ void main() { // // .thenAnswer((realInvocation) async => Decimal.fromInt(10)); // when(client?.ping()).thenAnswer((_) async => true); // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // // await DebugService.instance.init(); // expect(bch?.initializeExisting(), true); @@ -792,7 +702,7 @@ void main() { // expect(didThrow, true); // // // set node - // final wallet = await Hive.openBox(testWalletId); + // final wallet = await Hive.openBox (testWalletId); // await wallet.put("nodes", { // "default": { // "id": "some nodeID", @@ -812,7 +722,7 @@ void main() { // verify(client?.getServerFeatures()).called(1); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // test("initializeWallet new main net wallet", () async { @@ -820,18 +730,18 @@ void main() { // .thenAnswer((realInvocation) async => Decimal.fromInt(10)); // when(client?.ping()).thenAnswer((_) async => true); // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // expect(await bch?.initializeWallet(), true); // - // final wallet = await Hive.openBox(testWalletId); + // final wallet = await Hive.openBox (testWalletId); // // expect(await wallet.get("addressBookEntries"), {}); // expect(await wallet.get('notes'), null); @@ -866,7 +776,7 @@ void main() { // verify(client?.getServerFeatures()).called(1); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("initializeWallet existing main net wallet", () async { @@ -876,20 +786,20 @@ void main() { // // when(client?.getBatchHistory(args: anyNamed("args"))) // // .thenAnswer((_) async => {}); // // when(client?.getServerFeatures()).thenAnswer((_) async => { - // // "hosts": {}, + // // "hosts": {}, // // "pruning": null, // // "server_version": "Unit tests", // // "protocol_min": "1.4", // // "protocol_max": "1.4.2", // // "genesis_hash": GENESIS_HASH_MAINNET, // // "hash_function": "sha256", - // // "services": [] + // // "services": [] // // }); // // // init new wallet // // expect(bch?.initializeNew(), true); // // // // // fetch data to compare later - // // final newWallet = await Hive.openBox(testWalletId); + // // final newWallet = await Hive.openBox (testWalletId); // // // // final addressBookEntries = await newWallet.get("addressBookEntries"); // // final notes = await newWallet.get('notes'); @@ -920,15 +830,16 @@ void main() { // // coin: dtestcoin, // // client: client!, // // cachedClient: cachedClient!, - // // priceAPI: priceAPI, + // // // // secureStore: secureStore, + // // // ); // // // // // init existing // // expect(bch?.initializeExisting(), true); // // // // // compare data to ensure state matches state of previously closed wallet - // // final wallet = await Hive.openBox(testWalletId); + // // final wallet = await Hive.openBox (testWalletId); // // // // expect(await wallet.get("addressBookEntries"), addressBookEntries); // // expect(await wallet.get('notes'), notes); @@ -961,90 +872,45 @@ void main() { // // verify(client?.getServerFeatures()).called(1); // // verifyNoMoreInteractions(client); // // verifyNoMoreInteractions(cachedClient); - // // verifyNoMoreInteractions(priceAPI); + // // // // }); - test("get current receiving addresses", () async { - bch = BitcoinCashWallet( - walletId: testWalletId, - walletName: testWalletName, - coin: bchtestcoin, - client: client!, - cachedClient: cachedClient!, - tracker: tracker!, - priceAPI: priceAPI, - secureStore: secureStore, - ); - when(client?.ping()).thenAnswer((_) async => true); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_TESTNET, - "hash_function": "sha256", - "services": [] - }); - - await Hive.openBox(testWalletId); - await Hive.openBox(DB.boxNamePrefs); - - await bch?.initializeNew(); - await bch?.initializeExisting(); - expect(bch?.validateAddress(await bch!.currentReceivingAddress), true); - expect(bch?.validateAddress(await bch!.currentReceivingAddress), true); - expect(bch?.validateAddress(await bch!.currentReceivingAddress), true); - - verifyNever(client?.ping()).called(0); - verify(client?.getServerFeatures()).called(1); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); - - test("get allOwnAddresses", () async { - bch = BitcoinCashWallet( - walletId: testWalletId, - walletName: testWalletName, - coin: bchtestcoin, - client: client!, - cachedClient: cachedClient!, - tracker: tracker!, - priceAPI: priceAPI, - secureStore: secureStore, - ); - when(client?.ping()).thenAnswer((_) async => true); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_TESTNET, - "hash_function": "sha256", - "services": [] - }); - - await Hive.openBox(testWalletId); - await Hive.openBox(DB.boxNamePrefs); - - await bch?.initializeNew(); - await bch?.initializeExisting(); - final addresses = await bch?.allOwnAddresses; - expect(addresses, isA>()); - expect(addresses?.length, 2); - - for (int i = 0; i < 2; i++) { - expect(bch?.validateAddress(addresses![i]), true); - } - - verifyNever(client?.ping()).called(0); - verify(client?.getServerFeatures()).called(1); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); + // test("get current receiving addresses", () async { + // bch = BitcoinCashWallet( + // walletId: testWalletId, + // walletName: testWalletName, + // coin: bchtestcoin, + // client: client!, + // cachedClient: cachedClient!, + // tracker: tracker!, + // secureStore: secureStore, + // ); + // when(client?.ping()).thenAnswer((_) async => true); + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_TESTNET, + // "hash_function": "sha256", + // "services": [] + // }); + // + // await Hive.openBox(testWalletId); + // await Hive.openBox(DB.boxNamePrefs); + // + // await bch?.initializeNew(); + // await bch?.initializeExisting(); + // expect(bch?.validateAddress(await bch!.currentReceivingAddress), true); + // expect(bch?.validateAddress(await bch!.currentReceivingAddress), true); + // expect(bch?.validateAddress(await bch!.currentReceivingAddress), true); + // + // verifyNever(client?.ping()).called(0); + // verify(client?.getServerFeatures()).called(1); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); // test("get utxos and balances", () async { // bch = BitcoinCashWallet( @@ -1054,19 +920,20 @@ void main() { // client: client!, // cachedClient: cachedClient!, // tracker: tracker!, - // priceAPI: priceAPI, + // // secureStore: secureStore, + // // ); // when(client?.ping()).thenAnswer((_) async => true); // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_TESTNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // // await Hive.openBox(testWalletId); @@ -1148,7 +1015,7 @@ void main() { // // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // // test("get utxos - multiple batches", () async { @@ -1158,19 +1025,20 @@ void main() { // // coin: dtestcoin, // // client: client!, // // cachedClient: cachedClient!, - // // priceAPI: priceAPI, + // // // // secureStore: secureStore, + // // // ); // // when(client?.ping()).thenAnswer((_) async => true); // // when(client?.getServerFeatures()).thenAnswer((_) async => { - // // "hosts": {}, + // // "hosts": {}, // // "pruning": null, // // "server_version": "Unit tests", // // "protocol_min": "1.4", // // "protocol_max": "1.4.2", // // "genesis_hash": GENESIS_HASH_TESTNET, // // "hash_function": "sha256", - // // "services": [] + // // "services": [] // // }); // // // // when(client?.getBatchUTXOs(args: anyNamed("args"))) @@ -1182,7 +1050,7 @@ void main() { // // await bch?.initializeWallet(); // // // // // add some extra addresses to make sure we have more than the single batch size of 10 - // // final wallet = await Hive.openBox(testWalletId); + // // final wallet = await Hive.openBox (testWalletId); // // final addresses = await wallet.get("receivingAddressesP2PKH"); // // addresses.add("DQaAi9R58GXMpDyhePys6hHCuif4fhc1sN"); // // addresses.add("DBVhuF8QgeuxU2pssxzMgJqPhGCx5qyVkD"); @@ -1211,116 +1079,107 @@ void main() { // // // // verifyNoMoreInteractions(client); // // verifyNoMoreInteractions(cachedClient); - // // verifyNoMoreInteractions(priceAPI); + // // // // }); // - test("get utxos fails", () async { - bch = BitcoinCashWallet( - walletId: testWalletId, - walletName: testWalletName, - coin: bchtestcoin, - client: client!, - cachedClient: cachedClient!, - tracker: tracker!, - priceAPI: priceAPI, - secureStore: secureStore, - ); - when(client?.ping()).thenAnswer((_) async => true); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_TESTNET, - "hash_function": "sha256", - "services": [] - }); - - await Hive.openBox(testWalletId); - await Hive.openBox(DB.boxNamePrefs); - - when(client?.getBatchUTXOs(args: anyNamed("args"))) - .thenThrow(Exception("some exception")); - - await bch?.initializeNew(); - await bch?.initializeExisting(); - - final utxoData = await bch?.utxoData; - expect(utxoData, isA()); - expect(utxoData.toString(), - r"{totalUserCurrency: 0.00, satoshiBalance: 0, bitcoinBalance: 0, unspentOutputArray: []}"); - - final outputs = await bch?.unspentOutputs; - expect(outputs, isA>()); - expect(outputs?.length, 0); - - verifyNever(client?.ping()).called(0); - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchUTXOs(args: anyNamed("args"))).called(1); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); - - test("chain height fetch, update, and get", () async { - bch = BitcoinCashWallet( - walletId: testWalletId, - walletName: testWalletName, - coin: bchtestcoin, - client: client!, - cachedClient: cachedClient!, - tracker: tracker!, - priceAPI: priceAPI, - secureStore: secureStore, - ); - when(client?.ping()).thenAnswer((_) async => true); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_TESTNET, - "hash_function": "sha256", - "services": [] - }); - - await Hive.openBox(testWalletId); - await Hive.openBox(DB.boxNamePrefs); - - await bch?.initializeNew(); - await bch?.initializeExisting(); - - // get stored - expect(await bch?.storedChainHeight, 0); - - // fetch fails - when(client?.getBlockHeadTip()).thenThrow(Exception("Some exception")); - expect(await bch?.chainHeight, -1); - - // fetch succeeds - when(client?.getBlockHeadTip()).thenAnswer((realInvocation) async => { - "height": 100, - "hex": "some block hex", - }); - expect(await bch?.chainHeight, 100); - - // update - await bch?.updateStoredChainHeight(newHeight: 1000); - - // fetch updated - expect(await bch?.storedChainHeight, 1000); - - verifyNever(client?.ping()).called(0); - verify(client?.getServerFeatures()).called(1); - verify(client?.getBlockHeadTip()).called(2); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); - }); + // test("get utxos fails", () async { + // bch = BitcoinCashWallet( + // walletId: testWalletId, + // walletName: testWalletName, + // coin: bchtestcoin, + // client: client!, + // cachedClient: cachedClient!, + // tracker: tracker!, + // secureStore: secureStore, + // ); + // when(client?.ping()).thenAnswer((_) async => true); + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_TESTNET, + // "hash_function": "sha256", + // "services": [] + // }); + // + // await Hive.openBox(testWalletId); + // await Hive.openBox(DB.boxNamePrefs); + // + // when(client?.getBatchUTXOs(args: anyNamed("args"))) + // .thenThrow(Exception("some exception")); + // + // await bch?.initializeNew(); + // await bch?.initializeExisting(); + // + // final outputs = await bch!.utxos; + // expect(outputs, isA>()); + // expect(outputs.length, 0); + // + // verifyNever(client?.ping()).called(0); + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchUTXOs(args: anyNamed("args"))).called(1); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("chain height fetch, update, and get", () async { + // bch = BitcoinCashWallet( + // walletId: testWalletId, + // walletName: testWalletName, + // coin: bchtestcoin, + // client: client!, + // cachedClient: cachedClient!, + // tracker: tracker!, + // secureStore: secureStore, + // ); + // when(client?.ping()).thenAnswer((_) async => true); + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_TESTNET, + // "hash_function": "sha256", + // "services": [] + // }); + // + // await Hive.openBox(testWalletId); + // await Hive.openBox(DB.boxNamePrefs); + // + // await bch?.initializeNew(); + // await bch?.initializeExisting(); + // + // // get stored + // expect(bch?.storedChainHeight, 0); + // + // // fetch fails + // when(client?.getBlockHeadTip()).thenThrow(Exception("Some exception")); + // expect(await bch?.chainHeight, -1); + // + // // fetch succeeds + // when(client?.getBlockHeadTip()).thenAnswer((realInvocation) async => { + // "height": 100, + // "hex": "some block hex", + // }); + // expect(await bch?.chainHeight, 100); + // + // // update + // await bch?.updateCachedChainHeight(1000); + // + // // fetch updated + // expect(bch?.storedChainHeight, 1000); + // + // verifyNever(client?.ping()).called(0); + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBlockHeadTip()).called(2); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // }); test("getTxCount succeeds", () async { when(client?.getHistory( @@ -1349,11 +1208,10 @@ void main() { "1df1cab6d109d506aa424b00b6a013c5e1947dc13b78d62b4d0e9f518b3035d1")) .called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); //TODO - Needs refactoring test("getTxCount fails", () async { @@ -1375,203 +1233,198 @@ void main() { "64953f7db441a21172de206bf70b920c8c718ed4f03df9a85073c0400be0053c")) .called(0); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); - test("_checkCurrentReceivingAddressesForTransactions succeeds", () async { - when(client?.ping()).thenAnswer((_) async => true); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getHistory(scripthash: anyNamed("scripthash"))) - .thenAnswer((realInvocation) async => [ - { - "height": 4270385, - "tx_hash": - "c07f740ad72c0dd759741f4c9ab4b1586a22bc16545584364ac9b3d845766271" - }, - { - "height": 4270459, - "tx_hash": - "82da70c660daf4d42abd403795d047918c4021ff1d706b61790cda01a1c5ae5a" - } - ]); - - await Hive.openBox(testWalletId); - await Hive.openBox(DB.boxNamePrefs); - - await bch?.initializeNew(); - await bch?.initializeExisting(); - - bool didThrow = false; - try { - await bch?.checkCurrentReceivingAddressesForTransactions(); - } catch (_) { - didThrow = true; - } - expect(didThrow, false); - - verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(2); - verify(client?.getServerFeatures()).called(1); - verifyNever(client?.ping()).called(0); - - expect(secureStore?.interactions, 20); - expect(secureStore?.reads, 13); - expect(secureStore?.writes, 7); - expect(secureStore?.deletes, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); - - test("_checkCurrentReceivingAddressesForTransactions fails", () async { - when(client?.ping()).thenAnswer((_) async => true); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getHistory(scripthash: anyNamed("scripthash"))) - .thenThrow(Exception("some exception")); - - await Hive.openBox(testWalletId); - await Hive.openBox(DB.boxNamePrefs); - - await bch?.initializeNew(); - await bch?.initializeExisting(); - - bool didThrow = false; - try { - await bch?.checkCurrentReceivingAddressesForTransactions(); - } catch (_) { - didThrow = true; - } - expect(didThrow, true); - - verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); - verify(client?.getServerFeatures()).called(1); - verifyNever(client?.ping()).called(0); - - expect(secureStore?.interactions, 14); - expect(secureStore?.reads, 9); - expect(secureStore?.writes, 5); - expect(secureStore?.deletes, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); - - test("_checkCurrentChangeAddressesForTransactions succeeds", () async { - when(client?.ping()).thenAnswer((_) async => true); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getHistory(scripthash: anyNamed("scripthash"))) - .thenAnswer((realInvocation) async => [ - { - "height": 4286283, - "tx_hash": - "4c119685401e28982283e644c57d84fde6aab83324012e35c9b49e6efd99b49b" - }, - { - "height": 4286295, - "tx_hash": - "82da70c660daf4d42abd403795d047918c4021ff1d706b61790cda01a1c5ae5a" - } - ]); - - await Hive.openBox(testWalletId); - await Hive.openBox(DB.boxNamePrefs); - - await bch?.initializeNew(); - await bch?.initializeExisting(); - - bool didThrow = false; - try { - await bch?.checkCurrentChangeAddressesForTransactions(); - } catch (_) { - didThrow = true; - } - expect(didThrow, false); - - verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(2); - verify(client?.getServerFeatures()).called(1); - verifyNever(client?.ping()).called(0); - - expect(secureStore?.interactions, 20); - expect(secureStore?.reads, 13); - expect(secureStore?.writes, 7); - expect(secureStore?.deletes, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); - }); - - test("_checkCurrentChangeAddressesForTransactions fails", () async { - when(client?.ping()).thenAnswer((_) async => true); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getHistory(scripthash: anyNamed("scripthash"))) - .thenThrow(Exception("some exception")); - - await Hive.openBox(testWalletId); - await Hive.openBox(DB.boxNamePrefs); - - await bch?.initializeNew(); - await bch?.initializeExisting(); - - bool didThrow = false; - try { - await bch?.checkCurrentChangeAddressesForTransactions(); - } catch (_) { - didThrow = true; - } - expect(didThrow, true); - - verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); - verify(client?.getServerFeatures()).called(1); - verifyNever(client?.ping()).called(0); - - expect(secureStore?.interactions, 14); - expect(secureStore?.reads, 9); - expect(secureStore?.writes, 5); - expect(secureStore?.deletes, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); + // test("_checkCurrentReceivingAddressesForTransactions succeeds", () async { + // when(client?.ping()).thenAnswer((_) async => true); + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getHistory(scripthash: anyNamed("scripthash"))) + // .thenAnswer((realInvocation) async => [ + // { + // "height": 4270385, + // "tx_hash": + // "c07f740ad72c0dd759741f4c9ab4b1586a22bc16545584364ac9b3d845766271" + // }, + // { + // "height": 4270459, + // "tx_hash": + // "82da70c660daf4d42abd403795d047918c4021ff1d706b61790cda01a1c5ae5a" + // } + // ]); + // + // await Hive.openBox(testWalletId); + // await Hive.openBox(DB.boxNamePrefs); + // + // await bch?.initializeNew(); + // await bch?.initializeExisting(); + // + // bool didThrow = false; + // try { + // await bch?.checkCurrentReceivingAddressesForTransactions(); + // } catch (_) { + // didThrow = true; + // } + // expect(didThrow, false); + // + // verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(2); + // verify(client?.getServerFeatures()).called(1); + // verifyNever(client?.ping()).called(0); + // + // expect(secureStore.interactions, 20); + // expect(secureStore.reads, 13); + // expect(secureStore.writes, 7); + // expect(secureStore.deletes, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("_checkCurrentReceivingAddressesForTransactions fails", () async { + // when(client?.ping()).thenAnswer((_) async => true); + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getHistory(scripthash: anyNamed("scripthash"))) + // .thenThrow(Exception("some exception")); + // + // await Hive.openBox(testWalletId); + // await Hive.openBox(DB.boxNamePrefs); + // + // await bch?.initializeNew(); + // await bch?.initializeExisting(); + // + // bool didThrow = false; + // try { + // await bch?.checkCurrentReceivingAddressesForTransactions(); + // } catch (_) { + // didThrow = true; + // } + // expect(didThrow, true); + // + // verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); + // verify(client?.getServerFeatures()).called(1); + // verifyNever(client?.ping()).called(0); + // + // expect(secureStore.interactions, 14); + // expect(secureStore.reads, 9); + // expect(secureStore.writes, 5); + // expect(secureStore.deletes, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("_checkCurrentChangeAddressesForTransactions succeeds", () async { + // when(client?.ping()).thenAnswer((_) async => true); + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getHistory(scripthash: anyNamed("scripthash"))) + // .thenAnswer((realInvocation) async => [ + // { + // "height": 4286283, + // "tx_hash": + // "4c119685401e28982283e644c57d84fde6aab83324012e35c9b49e6efd99b49b" + // }, + // { + // "height": 4286295, + // "tx_hash": + // "82da70c660daf4d42abd403795d047918c4021ff1d706b61790cda01a1c5ae5a" + // } + // ]); + // + // await Hive.openBox(testWalletId); + // await Hive.openBox(DB.boxNamePrefs); + // + // await bch?.initializeNew(); + // await bch?.initializeExisting(); + // + // bool didThrow = false; + // try { + // await bch?.checkCurrentChangeAddressesForTransactions(); + // } catch (_) { + // didThrow = true; + // } + // expect(didThrow, false); + // + // verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(2); + // verify(client?.getServerFeatures()).called(1); + // verifyNever(client?.ping()).called(0); + // + // expect(secureStore.interactions, 20); + // expect(secureStore.reads, 13); + // expect(secureStore.writes, 7); + // expect(secureStore.deletes, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // }); + // + // test("_checkCurrentChangeAddressesForTransactions fails", () async { + // when(client?.ping()).thenAnswer((_) async => true); + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getHistory(scripthash: anyNamed("scripthash"))) + // .thenThrow(Exception("some exception")); + // + // await Hive.openBox(testWalletId); + // await Hive.openBox(DB.boxNamePrefs); + // + // await bch?.initializeNew(); + // await bch?.initializeExisting(); + // + // bool didThrow = false; + // try { + // await bch?.checkCurrentChangeAddressesForTransactions(); + // } catch (_) { + // didThrow = true; + // } + // expect(didThrow, true); + // + // verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); + // verify(client?.getServerFeatures()).called(1); + // verifyNever(client?.ping()).called(0); + // + // expect(secureStore.interactions, 14); + // expect(secureStore.reads, 9); + // expect(secureStore.writes, 5); + // expect(secureStore.deletes, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); // test("getAllTxsToWatch", () async { // TestWidgetsFlutterBinding.ensureInitialized(); @@ -1598,7 +1451,7 @@ void main() { // expect(secureStore?.interactions, 0); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("refreshIfThereIsNewData true A", () async { @@ -1617,10 +1470,11 @@ void main() { // coin: dtestcoin, // client: client!, // cachedClient: cachedClient!, - // priceAPI: priceAPI, + // // secureStore: secureStore, + // // ); - // final wallet = await Hive.openBox(testWalletId); + // final wallet = await Hive.openBox (testWalletId); // await wallet.put('receivingAddressesP2PKH', []); // // await wallet.put('changeAddressesP2PKH', []); @@ -1646,7 +1500,7 @@ void main() { // expect(secureStore?.interactions, 0); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("refreshIfThereIsNewData true B", () async { @@ -1746,10 +1600,11 @@ void main() { // coin: dtestcoin, // client: client!, // cachedClient: cachedClient!, - // priceAPI: priceAPI, + // // secureStore: secureStore, + // // ); - // final wallet = await Hive.openBox(testWalletId); + // final wallet = await Hive.openBox (testWalletId); // await wallet.put('receivingAddressesP2PKH', []); // // await wallet.put('changeAddressesP2PKH', []); @@ -1778,7 +1633,7 @@ void main() { // expect(secureStore?.interactions, 0); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // test("refreshIfThereIsNewData false A", () async { @@ -1879,10 +1734,11 @@ void main() { // client: client!, // cachedClient: cachedClient!, // tracker: tracker!, - // priceAPI: priceAPI, + // // secureStore: secureStore, + // // ); - // final wallet = await Hive.openBox(testWalletId); + // final wallet = await Hive.openBox (testWalletId); // await wallet.put('receivingAddressesP2PKH', []); // // await wallet.put('changeAddressesP2PKH', []); @@ -1912,7 +1768,7 @@ void main() { // expect(secureStore?.interactions, 0); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("refreshIfThereIsNewData false B", () async { @@ -1931,10 +1787,11 @@ void main() { // // client: client!, // // cachedClient: cachedClient!, // // tracker: tracker!, - // // priceAPI: priceAPI, + // // // // secureStore: secureStore, + // // // ); - // // final wallet = await Hive.openBox(testWalletId); + // // final wallet = await Hive.openBox (testWalletId); // // await wallet.put('receivingAddressesP2PKH', []); // // // // await wallet.put('changeAddressesP2PKH', []); @@ -1956,71 +1813,70 @@ void main() { // // expect(secureStore?.interactions, 0); // // verifyNoMoreInteractions(client); // // verifyNoMoreInteractions(cachedClient); - // // verifyNoMoreInteractions(priceAPI); + // // // // }); - test("get mnemonic list", () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - - // when(client?.getBatchHistory(args: anyNamed("args"))) - // .thenAnswer((thing) async { - // print(jsonEncode(thing.namedArguments.entries.first.value)); - // return {}; - // }); - - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - - final wallet = await Hive.openBox(testWalletId); - - // add maxNumberOfIndexesToCheck and height - await bch?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - expect(await bch?.mnemonic, TEST_MNEMONIC.split(" ")); - // - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); - }); + // test("get mnemonic list", () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // + // // when(client?.getBatchHistory(args: anyNamed("args"))) + // // .thenAnswer((thing) async { + // // print(jsonEncode(thing.namedArguments.entries.first.value)); + // // return {}; + // // }); + // + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // + // await Hive.openBox(testWalletId); + // + // // add maxNumberOfIndexesToCheck and height + // await bch?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // expect(await bch?.mnemonic, TEST_MNEMONIC.split(" ")); + // // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // }); test( "recoverFromMnemonic using empty seed on mainnet fails due to bad genesis hash match", () async { when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_TESTNET, "hash_function": "sha256", - "services": [] + "services": [] }); bool hasThrown = false; @@ -2037,10 +1893,9 @@ void main() { verify(client?.getServerFeatures()).called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test( @@ -2053,18 +1908,17 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, ); when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_MAINNET, "hash_function": "sha256", - "services": [] + "services": [] }); bool hasThrown = false; @@ -2081,27 +1935,26 @@ void main() { verify(client?.getServerFeatures()).called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test( "recoverFromMnemonic using empty seed on mainnet fails due to attempted overwrite of mnemonic", () async { when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_MAINNET, "hash_function": "sha256", - "services": [] + "services": [] }); - await secureStore?.write( + await secureStore.write( key: "${testWalletId}_mnemonic", value: "some mnemonic words"); bool hasThrown = false; @@ -2118,417 +1971,414 @@ void main() { verify(client?.getServerFeatures()).called(1); - expect(secureStore?.interactions, 2); + expect(secureStore.interactions, 2); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); - test("recoverFromMnemonic using non empty seed on mainnet succeeds", - () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => historyBatchResponse); - - List dynamicArgValues = []; - - when(client?.getBatchHistory(args: anyNamed("args"))) - .thenAnswer((realInvocation) async { - if (realInvocation.namedArguments.values.first.length == 1) { - dynamicArgValues.add(realInvocation.namedArguments.values.first); - } - - return historyBatchResponse; - }); - - // final wallet = await Hive.openBox(testWalletId); - await Hive.openBox(testWalletId); - - bool hasThrown = false; - try { - await bch?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - } catch (_) { - hasThrown = true; - } - expect(hasThrown, false); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); - - for (final arg in dynamicArgValues) { - final map = Map>.from(arg as Map); - - verify(client?.getBatchHistory(args: map)).called(1); - expect(activeScriptHashes.contains(map.values.first.first as String), - true); - } - - expect(secureStore?.interactions, 10); - expect(secureStore?.writes, 5); - expect(secureStore?.reads, 5); - expect(secureStore?.deletes, 0); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); - }); - - test("fullRescan succeeds", () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => historyBatchResponse); - when(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) - .thenAnswer((realInvocation) async {}); - - when(client?.getBatchHistory(args: { - "0": [ - "04818da846fe5e03ac993d2e0c1ccc3848ff6073c3aba6a572df4efc5432ae8b" - ] - })).thenAnswer((_) async => {"0": []}); - when(client?.getBatchHistory(args: { - "0": [ - "f0c86f888f2aca0efaf1705247dbd1ebc02347c183e197310c9062ea2c9d2e34" - ] - })).thenAnswer((_) async => {"0": []}); - when(client?.getBatchHistory(args: { - "0": [ - "ff7f0d2a4b8e2805706ece77f4e672550fe4c505a150c781639814338eda1734" - ] - })).thenAnswer((_) async => {"0": []}); - when(client?.getBatchHistory(args: { - "0": [ - "1c2336c32dc62f00862ee6a75643e01017c86edece10b5a9d7defbd5f66b0a80" - ] - })).thenAnswer((_) async => {"0": []}); - - final wallet = await Hive.openBox(testWalletId); - - // restore so we have something to rescan - await bch?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - // fetch valid wallet data - final preReceivingAddressesP2PKH = - await wallet.get('receivingAddressesP2PKH'); - final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); - final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); - final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); - - final preReceivingAddressesP2SH = - await wallet.get('receivingAddressesP2SH'); - final preChangeAddressesP2SH = await wallet.get('changeAddressesP2SH'); - final preReceivingIndexP2SH = await wallet.get('receivingIndexP2PKH'); - final preChangeIndexP2SH = await wallet.get('changeIndexP2SH'); - - final preUtxoData = await wallet.get('latest_utxo_model'); - final preReceiveDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2PKH"); - final preChangeDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_changeDerivationsP2PKH"); - - final preReceiveDerivationsStringP2SH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2SH"); - final preChangeDerivationsStringP2SH = - await secureStore?.read(key: "${testWalletId}_changeDerivationsP2SH"); - - // destroy the data that the rescan will fix - await wallet.put( - 'receivingAddressesP2PKH', ["some address", "some other address"]); - await wallet - .put('changeAddressesP2PKH', ["some address", "some other address"]); - - await wallet.put( - 'receivingAddressesP2SH', ["some address", "some other address"]); - await wallet - .put('changeAddressesP2SH', ["some address", "some other address"]); - - await wallet.put('receivingIndexP2PKH', 123); - await wallet.put('changeIndexP2PKH', 123); - - await wallet.put('receivingIndexP2SH', 123); - await wallet.put('changeIndexP2SH', 123); - - await secureStore?.write( - key: "${testWalletId}_receiveDerivationsP2PKH", value: "{}"); - await secureStore?.write( - key: "${testWalletId}_changeDerivationsP2PKH", value: "{}"); - - await secureStore?.write( - key: "${testWalletId}_receiveDerivationsP2SH", value: "{}"); - await secureStore?.write( - key: "${testWalletId}_changeDerivationsP2SH", value: "{}"); - - bool hasThrown = false; - try { - await bch?.fullRescan(2, 1000); - } catch (_) { - hasThrown = true; - } - expect(hasThrown, false); - - // fetch wallet data again - final receivingAddressesP2PKH = - await wallet.get('receivingAddressesP2PKH'); - final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); - final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); - final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); - - final receivingAddressesP2SH = await wallet.get('receivingAddressesP2SH'); - final changeAddressesP2SH = await wallet.get('changeAddressesP2SH'); - final receivingIndexP2SH = await wallet.get('receivingIndexP2SH'); - final changeIndexP2SH = await wallet.get('changeIndexP2SH'); - - final utxoData = await wallet.get('latest_utxo_model'); - final receiveDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2PKH"); - final changeDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_changeDerivationsP2PKH"); - - final receiveDerivationsStringP2SH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2SH"); - final changeDerivationsStringP2SH = - await secureStore?.read(key: "${testWalletId}_changeDerivationsP2SH"); - - expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); - expect(preChangeAddressesP2PKH, changeAddressesP2PKH); - expect(preReceivingIndexP2PKH, receivingIndexP2PKH); - expect(preChangeIndexP2PKH, changeIndexP2PKH); - - expect(preReceivingAddressesP2SH, receivingAddressesP2SH); - expect(preChangeAddressesP2SH, changeAddressesP2SH); - expect(preReceivingIndexP2SH, receivingIndexP2SH); - expect(preChangeIndexP2SH, changeIndexP2SH); - - expect(preUtxoData, utxoData); - - expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); - expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); - - expect(preReceiveDerivationsStringP2SH, receiveDerivationsStringP2SH); - expect(preChangeDerivationsStringP2SH, changeDerivationsStringP2SH); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(2); - verify(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) - .called(1); - - verify(client?.getBatchHistory(args: { - "0": [ - "04818da846fe5e03ac993d2e0c1ccc3848ff6073c3aba6a572df4efc5432ae8b" - ] - })).called(2); - verify(client?.getBatchHistory(args: { - "0": [ - "f0c86f888f2aca0efaf1705247dbd1ebc02347c183e197310c9062ea2c9d2e34" - ] - })).called(2); - verify(client?.getBatchHistory(args: { - "0": [ - "ff7f0d2a4b8e2805706ece77f4e672550fe4c505a150c781639814338eda1734" - ] - })).called(2); - verify(client?.getBatchHistory(args: { - "0": [ - "1c2336c32dc62f00862ee6a75643e01017c86edece10b5a9d7defbd5f66b0a80" - ] - })).called(2); - - expect(secureStore?.writes, 17); - expect(secureStore?.reads, 22); - expect(secureStore?.deletes, 4); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); - }); - - test("fullRescan fails", () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => historyBatchResponse); - when(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) - .thenAnswer((realInvocation) async {}); - - when(client?.getBatchHistory(args: { - "0": [ - "04818da846fe5e03ac993d2e0c1ccc3848ff6073c3aba6a572df4efc5432ae8b" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - when(client?.getBatchHistory(args: { - "0": [ - "f0c86f888f2aca0efaf1705247dbd1ebc02347c183e197310c9062ea2c9d2e34" - ] - })).thenAnswer((_) async => {"0": []}); - when(client?.getBatchHistory(args: { - "0": [ - "ff7f0d2a4b8e2805706ece77f4e672550fe4c505a150c781639814338eda1734" - ] - })).thenAnswer((_) async => {"0": []}); - when(client?.getBatchHistory(args: { - "0": [ - "1c2336c32dc62f00862ee6a75643e01017c86edece10b5a9d7defbd5f66b0a80" - ] - })).thenAnswer((_) async => {"0": []}); - - final wallet = await Hive.openBox(testWalletId); - - // restore so we have something to rescan - await bch?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - // fetch wallet data - final preReceivingAddressesP2PKH = - await wallet.get('receivingAddressesP2PKH'); - - final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); - final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); - final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); - final preUtxoData = await wallet.get('latest_utxo_model'); - final preReceiveDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2PKH"); - final preChangeDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_changeDerivationsP2PKH"); - - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenThrow(Exception("fake exception")); - - bool hasThrown = false; - try { - await bch?.fullRescan(2, 1000); - } catch (_) { - hasThrown = true; - } - expect(hasThrown, true); - - // fetch wallet data again - final receivingAddressesP2PKH = - await wallet.get('receivingAddressesP2PKH'); - - final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); - final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); - final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); - final utxoData = await wallet.get('latest_utxo_model'); - final receiveDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2PKH"); - final changeDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_changeDerivationsP2PKH"); - - expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); - expect(preChangeAddressesP2PKH, changeAddressesP2PKH); - expect(preReceivingIndexP2PKH, receivingIndexP2PKH); - expect(preChangeIndexP2PKH, changeIndexP2PKH); - expect(preUtxoData, utxoData); - expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); - expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(2); - verify(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) - .called(1); - - verify(client?.getBatchHistory(args: { - "0": [ - "04818da846fe5e03ac993d2e0c1ccc3848ff6073c3aba6a572df4efc5432ae8b" - ] - })).called(1); - verify(client?.getBatchHistory(args: { - "0": [ - "f0c86f888f2aca0efaf1705247dbd1ebc02347c183e197310c9062ea2c9d2e34" - ] - })).called(2); - verify(client?.getBatchHistory(args: { - "0": [ - "ff7f0d2a4b8e2805706ece77f4e672550fe4c505a150c781639814338eda1734" - ] - })).called(2); - verify(client?.getBatchHistory(args: { - "0": [ - "1c2336c32dc62f00862ee6a75643e01017c86edece10b5a9d7defbd5f66b0a80" - ] - })).called(2); - - expect(secureStore?.writes, 13); - expect(secureStore?.reads, 18); - expect(secureStore?.deletes, 8); - }); + // test("recoverFromMnemonic using non empty seed on mainnet succeeds", + // () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => historyBatchResponse); + // + // List dynamicArgValues = []; + // + // when(client?.getBatchHistory(args: anyNamed("args"))) + // .thenAnswer((realInvocation) async { + // if (realInvocation.namedArguments.values.first.length == 1) { + // dynamicArgValues.add(realInvocation.namedArguments.values.first); + // } + // + // return historyBatchResponse; + // }); + // + // // final wallet = await Hive.openBox (testWalletId); + // await Hive.openBox(testWalletId); + // + // bool hasThrown = false; + // try { + // await bch?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // } catch (_) { + // hasThrown = true; + // } + // expect(hasThrown, false); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); + // + // for (final arg in dynamicArgValues) { + // final map = Map>.from(arg as Map); + // + // verify(client?.getBatchHistory(args: map)).called(1); + // expect(activeScriptHashes.contains(map.values.first.first as String), + // true); + // } + // + // expect(secureStore.interactions, 10); + // expect(secureStore.writes, 5); + // expect(secureStore.reads, 5); + // expect(secureStore.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // }); + // + // test("fullRescan succeeds", () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => historyBatchResponse); + // when(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) + // .thenAnswer((realInvocation) async {}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "04818da846fe5e03ac993d2e0c1ccc3848ff6073c3aba6a572df4efc5432ae8b" + // ] + // })).thenAnswer((_) async => {"0": []}); + // when(client?.getBatchHistory(args: { + // "0": [ + // "f0c86f888f2aca0efaf1705247dbd1ebc02347c183e197310c9062ea2c9d2e34" + // ] + // })).thenAnswer((_) async => {"0": []}); + // when(client?.getBatchHistory(args: { + // "0": [ + // "ff7f0d2a4b8e2805706ece77f4e672550fe4c505a150c781639814338eda1734" + // ] + // })).thenAnswer((_) async => {"0": []}); + // when(client?.getBatchHistory(args: { + // "0": [ + // "1c2336c32dc62f00862ee6a75643e01017c86edece10b5a9d7defbd5f66b0a80" + // ] + // })).thenAnswer((_) async => {"0": []}); + // + // final wallet = await Hive.openBox(testWalletId); + // + // // restore so we have something to rescan + // await bch?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // // fetch valid wallet data + // final preReceivingAddressesP2PKH = + // await wallet.get('receivingAddressesP2PKH'); + // final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + // final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + // final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + // + // final preReceivingAddressesP2SH = + // await wallet.get('receivingAddressesP2SH'); + // final preChangeAddressesP2SH = await wallet.get('changeAddressesP2SH'); + // final preReceivingIndexP2SH = await wallet.get('receivingIndexP2PKH'); + // final preChangeIndexP2SH = await wallet.get('changeIndexP2SH'); + // + // final preUtxoData = await wallet.get('latest_utxo_model'); + // final preReceiveDerivationsStringP2PKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2PKH"); + // final preChangeDerivationsStringP2PKH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + // + // final preReceiveDerivationsStringP2SH = + // await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); + // final preChangeDerivationsStringP2SH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); + // + // // destroy the data that the rescan will fix + // await wallet.put( + // 'receivingAddressesP2PKH', ["some address", "some other address"]); + // await wallet + // .put('changeAddressesP2PKH', ["some address", "some other address"]); + // + // await wallet.put( + // 'receivingAddressesP2SH', ["some address", "some other address"]); + // await wallet + // .put('changeAddressesP2SH', ["some address", "some other address"]); + // + // await wallet.put('receivingIndexP2PKH', 123); + // await wallet.put('changeIndexP2PKH', 123); + // + // await wallet.put('receivingIndexP2SH', 123); + // await wallet.put('changeIndexP2SH', 123); + // + // await secureStore.write( + // key: "${testWalletId}_receiveDerivationsP2PKH", value: "{}"); + // await secureStore.write( + // key: "${testWalletId}_changeDerivationsP2PKH", value: "{}"); + // + // await secureStore.write( + // key: "${testWalletId}_receiveDerivationsP2SH", value: "{}"); + // await secureStore.write( + // key: "${testWalletId}_changeDerivationsP2SH", value: "{}"); + // + // bool hasThrown = false; + // try { + // await bch?.fullRescan(2, 1000); + // } catch (_) { + // hasThrown = true; + // } + // expect(hasThrown, false); + // + // // fetch wallet data again + // final receivingAddressesP2PKH = + // await wallet.get('receivingAddressesP2PKH'); + // final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + // final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + // final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + // + // final receivingAddressesP2SH = await wallet.get('receivingAddressesP2SH'); + // final changeAddressesP2SH = await wallet.get('changeAddressesP2SH'); + // final receivingIndexP2SH = await wallet.get('receivingIndexP2SH'); + // final changeIndexP2SH = await wallet.get('changeIndexP2SH'); + // + // final utxoData = await wallet.get('latest_utxo_model'); + // final receiveDerivationsStringP2PKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2PKH"); + // final changeDerivationsStringP2PKH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + // + // final receiveDerivationsStringP2SH = + // await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); + // final changeDerivationsStringP2SH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); + // + // expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); + // expect(preChangeAddressesP2PKH, changeAddressesP2PKH); + // expect(preReceivingIndexP2PKH, receivingIndexP2PKH); + // expect(preChangeIndexP2PKH, changeIndexP2PKH); + // + // expect(preReceivingAddressesP2SH, receivingAddressesP2SH); + // expect(preChangeAddressesP2SH, changeAddressesP2SH); + // expect(preReceivingIndexP2SH, receivingIndexP2SH); + // expect(preChangeIndexP2SH, changeIndexP2SH); + // + // expect(preUtxoData, utxoData); + // + // expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); + // expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); + // + // expect(preReceiveDerivationsStringP2SH, receiveDerivationsStringP2SH); + // expect(preChangeDerivationsStringP2SH, changeDerivationsStringP2SH); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(2); + // verify(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) + // .called(1); + // + // verify(client?.getBatchHistory(args: { + // "0": [ + // "04818da846fe5e03ac993d2e0c1ccc3848ff6073c3aba6a572df4efc5432ae8b" + // ] + // })).called(2); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "f0c86f888f2aca0efaf1705247dbd1ebc02347c183e197310c9062ea2c9d2e34" + // ] + // })).called(2); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "ff7f0d2a4b8e2805706ece77f4e672550fe4c505a150c781639814338eda1734" + // ] + // })).called(2); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "1c2336c32dc62f00862ee6a75643e01017c86edece10b5a9d7defbd5f66b0a80" + // ] + // })).called(2); + // + // expect(secureStore.writes, 17); + // expect(secureStore.reads, 22); + // expect(secureStore.deletes, 4); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // }); + // + // test("fullRescan fails", () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => historyBatchResponse); + // when(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) + // .thenAnswer((realInvocation) async {}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "04818da846fe5e03ac993d2e0c1ccc3848ff6073c3aba6a572df4efc5432ae8b" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // when(client?.getBatchHistory(args: { + // "0": [ + // "f0c86f888f2aca0efaf1705247dbd1ebc02347c183e197310c9062ea2c9d2e34" + // ] + // })).thenAnswer((_) async => {"0": []}); + // when(client?.getBatchHistory(args: { + // "0": [ + // "ff7f0d2a4b8e2805706ece77f4e672550fe4c505a150c781639814338eda1734" + // ] + // })).thenAnswer((_) async => {"0": []}); + // when(client?.getBatchHistory(args: { + // "0": [ + // "1c2336c32dc62f00862ee6a75643e01017c86edece10b5a9d7defbd5f66b0a80" + // ] + // })).thenAnswer((_) async => {"0": []}); + // + // final wallet = await Hive.openBox(testWalletId); + // + // // restore so we have something to rescan + // await bch?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // // fetch wallet data + // final preReceivingAddressesP2PKH = + // await wallet.get('receivingAddressesP2PKH'); + // + // final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + // final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + // final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + // final preUtxoData = await wallet.get('latest_utxo_model'); + // final preReceiveDerivationsStringP2PKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2PKH"); + // final preChangeDerivationsStringP2PKH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + // + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenThrow(Exception("fake exception")); + // + // bool hasThrown = false; + // try { + // await bch?.fullRescan(2, 1000); + // } catch (_) { + // hasThrown = true; + // } + // expect(hasThrown, true); + // + // // fetch wallet data again + // final receivingAddressesP2PKH = + // await wallet.get('receivingAddressesP2PKH'); + // + // final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + // final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + // final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + // final utxoData = await wallet.get('latest_utxo_model'); + // final receiveDerivationsStringP2PKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2PKH"); + // final changeDerivationsStringP2PKH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + // + // expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); + // expect(preChangeAddressesP2PKH, changeAddressesP2PKH); + // expect(preReceivingIndexP2PKH, receivingIndexP2PKH); + // expect(preChangeIndexP2PKH, changeIndexP2PKH); + // expect(preUtxoData, utxoData); + // expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); + // expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(2); + // verify(cachedClient?.clearSharedTransactionCache(coin: Coin.bitcoincash)) + // .called(1); + // + // verify(client?.getBatchHistory(args: { + // "0": [ + // "04818da846fe5e03ac993d2e0c1ccc3848ff6073c3aba6a572df4efc5432ae8b" + // ] + // })).called(1); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "f0c86f888f2aca0efaf1705247dbd1ebc02347c183e197310c9062ea2c9d2e34" + // ] + // })).called(2); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "ff7f0d2a4b8e2805706ece77f4e672550fe4c505a150c781639814338eda1734" + // ] + // })).called(2); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "1c2336c32dc62f00862ee6a75643e01017c86edece10b5a9d7defbd5f66b0a80" + // ] + // })).called(2); + // + // expect(secureStore.writes, 13); + // expect(secureStore.reads, 18); + // expect(secureStore.deletes, 8); + // }); // // test("fetchBuildTxData succeeds", () async { // // when(client.getServerFeatures()).thenAnswer((_) async => { - // // "hosts": {}, + // // "hosts": {}, // // "pruning": null, // // "server_version": "Unit tests", // // "protocol_min": "1.4", // // "protocol_max": "1.4.2", // // "genesis_hash": GENESIS_HASH_MAINNET, // // "hash_function": "sha256", - // // "services": [] + // // "services": [] // // }); // // when(client.getBatchHistory(args: historyBatchArgs0)) // // .thenAnswer((_) async => historyBatchResponse); @@ -2693,19 +2543,19 @@ void main() { // // // // verifyNoMoreInteractions(client); // // verifyNoMoreInteractions(cachedClient); - // // verifyNoMoreInteractions(priceAPI); + // // // // }); // test("fetchBuildTxData throws", () async { // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // when(client?.getBatchHistory(args: historyBatchArgs0)) // .thenAnswer((_) async => historyBatchResponse); @@ -2774,19 +2624,19 @@ void main() { // // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // test("build transaction succeeds", () async { // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // when(client?.getBatchHistory(args: historyBatchArgs0)) // .thenAnswer((_) async => historyBatchResponse); @@ -2867,7 +2717,7 @@ void main() { // // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); test("confirmSend error 1", () async { @@ -2880,11 +2730,10 @@ void main() { expect(didThrow, true); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("confirmSend error 2", () async { @@ -2897,11 +2746,10 @@ void main() { expect(didThrow, true); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("confirmSend some other error code", () async { @@ -2914,11 +2762,10 @@ void main() { expect(didThrow, true); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("confirmSend no hex", () async { @@ -2931,11 +2778,10 @@ void main() { expect(didThrow, true); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("confirmSend fails due to vSize being greater than fee", () async { @@ -2953,11 +2799,10 @@ void main() { rawTx: "a string", requestID: anyNamed("requestID"))) .called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("confirmSend fails when broadcast transactions throws", () async { @@ -2979,212 +2824,209 @@ void main() { rawTx: "a string", requestID: anyNamed("requestID"))) .called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); - test("refresh wallet mutex locked", () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: { - "0": [ - "f0c86f888f2aca0efaf1705247dbd1ebc02347c183e197310c9062ea2c9d2e34" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - when(client?.getBatchHistory(args: { - "0": [ - "04818da846fe5e03ac993d2e0c1ccc3848ff6073c3aba6a572df4efc5432ae8b" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - when(client?.getBatchHistory(args: { - "0": [ - "ff7f0d2a4b8e2805706ece77f4e672550fe4c505a150c781639814338eda1734" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - when(client?.getBatchHistory(args: { - "0": [ - "1c2336c32dc62f00862ee6a75643e01017c86edece10b5a9d7defbd5f66b0a80" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - final wallet = await Hive.openBox(testWalletId); - // recover to fill data - await bch?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - bch?.refreshMutex = true; - - await bch?.refresh(); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); - - verify(client?.getBatchHistory(args: { - "0": [ - "f0c86f888f2aca0efaf1705247dbd1ebc02347c183e197310c9062ea2c9d2e34" - ] - })).called(1); - verify(client?.getBatchHistory(args: { - "0": [ - "04818da846fe5e03ac993d2e0c1ccc3848ff6073c3aba6a572df4efc5432ae8b" - ] - })).called(1); - verify(client?.getBatchHistory(args: { - "0": [ - "ff7f0d2a4b8e2805706ece77f4e672550fe4c505a150c781639814338eda1734" - ] - })).called(1); - verify(client?.getBatchHistory(args: { - "0": [ - "1c2336c32dc62f00862ee6a75643e01017c86edece10b5a9d7defbd5f66b0a80" - ] - })).called(1); - - expect(secureStore?.interactions, 10); - expect(secureStore?.writes, 5); - expect(secureStore?.reads, 5); - expect(secureStore?.deletes, 0); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); - }); - - test("refresh wallet throws", () async { - when(client?.getBlockHeadTip()).thenThrow(Exception("some exception")); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => historyBatchResponse); - - when(client?.getBatchHistory(args: { - "0": [ - "04818da846fe5e03ac993d2e0c1ccc3848ff6073c3aba6a572df4efc5432ae8b" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - when(client?.getBatchHistory(args: { - "0": [ - "f0c86f888f2aca0efaf1705247dbd1ebc02347c183e197310c9062ea2c9d2e34" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - when(client?.getBatchHistory(args: { - "0": [ - "ff7f0d2a4b8e2805706ece77f4e672550fe4c505a150c781639814338eda1734" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - when(client?.getBatchHistory(args: { - "0": [ - "1c2336c32dc62f00862ee6a75643e01017c86edece10b5a9d7defbd5f66b0a80" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - when(client?.getHistory(scripthash: anyNamed("scripthash"))) - .thenThrow(Exception("some exception")); - - final wallet = await Hive.openBox(testWalletId); - - // recover to fill data - await bch?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - await bch?.refresh(); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); - - verify(client?.getBatchHistory(args: { - "0": [ - "04818da846fe5e03ac993d2e0c1ccc3848ff6073c3aba6a572df4efc5432ae8b" - ] - })).called(1); - verify(client?.getBatchHistory(args: { - "0": [ - "f0c86f888f2aca0efaf1705247dbd1ebc02347c183e197310c9062ea2c9d2e34" - ] - })).called(1); - verify(client?.getBatchHistory(args: { - "0": [ - "ff7f0d2a4b8e2805706ece77f4e672550fe4c505a150c781639814338eda1734" - ] - })).called(1); - verify(client?.getBatchHistory(args: { - "0": [ - "1c2336c32dc62f00862ee6a75643e01017c86edece10b5a9d7defbd5f66b0a80" - ] - })).called(1); - - verify(client?.getBlockHeadTip()).called(1); - verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); - - expect(secureStore?.interactions, 10); - expect(secureStore?.writes, 5); - expect(secureStore?.reads, 5); - expect(secureStore?.deletes, 0); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); - }); - - // test("refresh wallet normally", () async { - // when(client?.getBlockHeadTip()).thenAnswer((realInvocation) async => - // {"height": 520481, "hex": "some block hex"}); + // test("refresh wallet mutex locked", () async { // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: { + // "0": [ + // "f0c86f888f2aca0efaf1705247dbd1ebc02347c183e197310c9062ea2c9d2e34" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // when(client?.getBatchHistory(args: { + // "0": [ + // "04818da846fe5e03ac993d2e0c1ccc3848ff6073c3aba6a572df4efc5432ae8b" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // when(client?.getBatchHistory(args: { + // "0": [ + // "ff7f0d2a4b8e2805706ece77f4e672550fe4c505a150c781639814338eda1734" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // when(client?.getBatchHistory(args: { + // "0": [ + // "1c2336c32dc62f00862ee6a75643e01017c86edece10b5a9d7defbd5f66b0a80" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // await Hive.openBox(testWalletId); + // // recover to fill data + // await bch?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // bch?.refreshMutex = true; + // + // await bch?.refresh(); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); + // + // verify(client?.getBatchHistory(args: { + // "0": [ + // "f0c86f888f2aca0efaf1705247dbd1ebc02347c183e197310c9062ea2c9d2e34" + // ] + // })).called(1); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "04818da846fe5e03ac993d2e0c1ccc3848ff6073c3aba6a572df4efc5432ae8b" + // ] + // })).called(1); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "ff7f0d2a4b8e2805706ece77f4e672550fe4c505a150c781639814338eda1734" + // ] + // })).called(1); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "1c2336c32dc62f00862ee6a75643e01017c86edece10b5a9d7defbd5f66b0a80" + // ] + // })).called(1); + // + // expect(secureStore.interactions, 10); + // expect(secureStore.writes, 5); + // expect(secureStore.reads, 5); + // expect(secureStore.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // }); + // + // test("refresh wallet throws", () async { + // when(client?.getBlockHeadTip()).thenThrow(Exception("some exception")); + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => historyBatchResponse); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "04818da846fe5e03ac993d2e0c1ccc3848ff6073c3aba6a572df4efc5432ae8b" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // when(client?.getBatchHistory(args: { + // "0": [ + // "f0c86f888f2aca0efaf1705247dbd1ebc02347c183e197310c9062ea2c9d2e34" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // when(client?.getBatchHistory(args: { + // "0": [ + // "ff7f0d2a4b8e2805706ece77f4e672550fe4c505a150c781639814338eda1734" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // when(client?.getBatchHistory(args: { + // "0": [ + // "1c2336c32dc62f00862ee6a75643e01017c86edece10b5a9d7defbd5f66b0a80" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // when(client?.getHistory(scripthash: anyNamed("scripthash"))) + // .thenThrow(Exception("some exception")); + // + // await Hive.openBox(testWalletId); + // + // // recover to fill data + // await bch?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // await bch?.refresh(); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); + // + // verify(client?.getBatchHistory(args: { + // "0": [ + // "04818da846fe5e03ac993d2e0c1ccc3848ff6073c3aba6a572df4efc5432ae8b" + // ] + // })).called(1); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "f0c86f888f2aca0efaf1705247dbd1ebc02347c183e197310c9062ea2c9d2e34" + // ] + // })).called(1); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "ff7f0d2a4b8e2805706ece77f4e672550fe4c505a150c781639814338eda1734" + // ] + // })).called(1); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "1c2336c32dc62f00862ee6a75643e01017c86edece10b5a9d7defbd5f66b0a80" + // ] + // })).called(1); + // + // verify(client?.getBlockHeadTip()).called(1); + // verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); + // + // expect(secureStore.interactions, 10); + // expect(secureStore.writes, 5); + // expect(secureStore.reads, 5); + // expect(secureStore.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // }); + + // test("refresh wallet normally", () async { + // when(client?.getBlockHeadTip()).thenAnswer((realInvocation) async => + // {"height": 520481, "hex": "some block hex"}); + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] // }); // when(client?.getBatchHistory(args: historyBatchArgs0)) // .thenAnswer((_) async => historyBatchResponse); @@ -3231,7 +3073,7 @@ void main() { // // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); }); diff --git a/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart b/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart index bfc5f793b..05cd24ebd 100644 --- a/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart +++ b/test/services/coins/bitcoincash/bitcoincash_wallet_test.mocks.dart @@ -3,19 +3,16 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i6; +import 'dart:async' as _i5; import 'package:decimal/decimal.dart' as _i2; -import 'package:http/http.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i7; -import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i5; -import 'package:stackwallet/services/price.dart' as _i9; +import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i6; +import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i4; import 'package:stackwallet/services/transaction_notification_tracker.dart' - as _i11; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i8; + as _i8; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i7; import 'package:stackwallet/utilities/prefs.dart' as _i3; -import 'package:tuple/tuple.dart' as _i10; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -48,26 +45,16 @@ class _FakePrefs_1 extends _i1.SmartFake implements _i3.Prefs { ); } -class _FakeClient_2 extends _i1.SmartFake implements _i4.Client { - _FakeClient_2( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - /// A class which mocks [ElectrumX]. /// /// See the documentation for Mockito's code generation for more information. -class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { +class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { MockElectrumX() { _i1.throwOnMissingStub(this); } @override - set failovers(List<_i5.ElectrumXNode>? _failovers) => super.noSuchMethod( + set failovers(List<_i4.ElectrumXNode>? _failovers) => super.noSuchMethod( Invocation.setter( #failovers, _failovers, @@ -103,7 +90,7 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { returnValue: false, ) as bool); @override - _i6.Future request({ + _i5.Future request({ required String? command, List? args = const [], Duration? connectionTimeout = const Duration(seconds: 60), @@ -122,10 +109,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #retries: retries, }, ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i5.Future.value(), + ) as _i5.Future); @override - _i6.Future>> batchRequest({ + _i5.Future>> batchRequest({ required String? command, required Map>? args, Duration? connectionTimeout = const Duration(seconds: 60), @@ -142,11 +129,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #retries: retries, }, ), - returnValue: _i6.Future>>.value( + returnValue: _i5.Future>>.value( >[]), - ) as _i6.Future>>); + ) as _i5.Future>>); @override - _i6.Future ping({ + _i5.Future ping({ String? requestID, int? retryCount = 1, }) => @@ -159,10 +146,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #retryCount: retryCount, }, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i5.Future.value(false), + ) as _i5.Future); @override - _i6.Future> getBlockHeadTip({String? requestID}) => + _i5.Future> getBlockHeadTip({String? requestID}) => (super.noSuchMethod( Invocation.method( #getBlockHeadTip, @@ -170,10 +157,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future> getServerFeatures({String? requestID}) => + _i5.Future> getServerFeatures({String? requestID}) => (super.noSuchMethod( Invocation.method( #getServerFeatures, @@ -181,10 +168,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future broadcastTransaction({ + _i5.Future broadcastTransaction({ required String? rawTx, String? requestID, }) => @@ -197,10 +184,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #requestID: requestID, }, ), - returnValue: _i6.Future.value(''), - ) as _i6.Future); + returnValue: _i5.Future.value(''), + ) as _i5.Future); @override - _i6.Future> getBalance({ + _i5.Future> getBalance({ required String? scripthash, String? requestID, }) => @@ -214,10 +201,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future>> getHistory({ + _i5.Future>> getHistory({ required String? scripthash, String? requestID, }) => @@ -230,11 +217,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #requestID: requestID, }, ), - returnValue: _i6.Future>>.value( + returnValue: _i5.Future>>.value( >[]), - ) as _i6.Future>>); + ) as _i5.Future>>); @override - _i6.Future>>> getBatchHistory( + _i5.Future>>> getBatchHistory( {required Map>? args}) => (super.noSuchMethod( Invocation.method( @@ -242,11 +229,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { [], {#args: args}, ), - returnValue: _i6.Future>>>.value( + returnValue: _i5.Future>>>.value( >>{}), - ) as _i6.Future>>>); + ) as _i5.Future>>>); @override - _i6.Future>> getUTXOs({ + _i5.Future>> getUTXOs({ required String? scripthash, String? requestID, }) => @@ -259,11 +246,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #requestID: requestID, }, ), - returnValue: _i6.Future>>.value( + returnValue: _i5.Future>>.value( >[]), - ) as _i6.Future>>); + ) as _i5.Future>>); @override - _i6.Future>>> getBatchUTXOs( + _i5.Future>>> getBatchUTXOs( {required Map>? args}) => (super.noSuchMethod( Invocation.method( @@ -271,11 +258,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { [], {#args: args}, ), - returnValue: _i6.Future>>>.value( + returnValue: _i5.Future>>>.value( >>{}), - ) as _i6.Future>>>); + ) as _i5.Future>>>); @override - _i6.Future> getTransaction({ + _i5.Future> getTransaction({ required String? txHash, bool? verbose = true, String? requestID, @@ -291,10 +278,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future> getAnonymitySet({ + _i5.Future> getAnonymitySet({ String? groupId = r'1', String? blockhash = r'', String? requestID, @@ -310,10 +297,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future getMintData({ + _i5.Future getMintData({ dynamic mints, String? requestID, }) => @@ -326,10 +313,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #requestID: requestID, }, ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i5.Future.value(), + ) as _i5.Future); @override - _i6.Future> getUsedCoinSerials({ + _i5.Future> getUsedCoinSerials({ String? requestID, required int? startNumber, }) => @@ -343,19 +330,19 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future getLatestCoinId({String? requestID}) => (super.noSuchMethod( + _i5.Future getLatestCoinId({String? requestID}) => (super.noSuchMethod( Invocation.method( #getLatestCoinId, [], {#requestID: requestID}, ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i5.Future.value(0), + ) as _i5.Future); @override - _i6.Future> getFeeRate({String? requestID}) => + _i5.Future> getFeeRate({String? requestID}) => (super.noSuchMethod( Invocation.method( #getFeeRate, @@ -363,10 +350,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future<_i2.Decimal> estimateFee({ + _i5.Future<_i2.Decimal> estimateFee({ String? requestID, required int? blocks, }) => @@ -379,7 +366,7 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #blocks: blocks, }, ), - returnValue: _i6.Future<_i2.Decimal>.value(_FakeDecimal_0( + returnValue: _i5.Future<_i2.Decimal>.value(_FakeDecimal_0( this, Invocation.method( #estimateFee, @@ -390,15 +377,15 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), )), - ) as _i6.Future<_i2.Decimal>); + ) as _i5.Future<_i2.Decimal>); @override - _i6.Future<_i2.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( + _i5.Future<_i2.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( Invocation.method( #relayFee, [], {#requestID: requestID}, ), - returnValue: _i6.Future<_i2.Decimal>.value(_FakeDecimal_0( + returnValue: _i5.Future<_i2.Decimal>.value(_FakeDecimal_0( this, Invocation.method( #relayFee, @@ -406,13 +393,13 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { {#requestID: requestID}, ), )), - ) as _i6.Future<_i2.Decimal>); + ) as _i5.Future<_i2.Decimal>); } /// A class which mocks [CachedElectrumX]. /// /// See the documentation for Mockito's code generation for more information. -class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { +class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { MockCachedElectrumX() { _i1.throwOnMissingStub(this); } @@ -441,15 +428,15 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { ), ) as _i3.Prefs); @override - List<_i5.ElectrumXNode> get failovers => (super.noSuchMethod( + List<_i4.ElectrumXNode> get failovers => (super.noSuchMethod( Invocation.getter(#failovers), - returnValue: <_i5.ElectrumXNode>[], - ) as List<_i5.ElectrumXNode>); + returnValue: <_i4.ElectrumXNode>[], + ) as List<_i4.ElectrumXNode>); @override - _i6.Future> getAnonymitySet({ + _i5.Future> getAnonymitySet({ required String? groupId, String? blockhash = r'', - required _i8.Coin? coin, + required _i7.Coin? coin, }) => (super.noSuchMethod( Invocation.method( @@ -462,8 +449,8 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override String base64ToHex(String? source) => (super.noSuchMethod( Invocation.method( @@ -481,9 +468,9 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { returnValue: '', ) as String); @override - _i6.Future> getTransaction({ + _i5.Future> getTransaction({ required String? txHash, - required _i8.Coin? coin, + required _i7.Coin? coin, bool? verbose = true, }) => (super.noSuchMethod( @@ -497,11 +484,11 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future> getUsedCoinSerials({ - required _i8.Coin? coin, + _i5.Future> getUsedCoinSerials({ + required _i7.Coin? coin, int? startNumber = 0, }) => (super.noSuchMethod( @@ -513,66 +500,26 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { #startNumber: startNumber, }, ), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i5.Future>.value([]), + ) as _i5.Future>); @override - _i6.Future clearSharedTransactionCache({required _i8.Coin? coin}) => + _i5.Future clearSharedTransactionCache({required _i7.Coin? coin}) => (super.noSuchMethod( Invocation.method( #clearSharedTransactionCache, [], {#coin: coin}, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); -} - -/// A class which mocks [PriceAPI]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockPriceAPI extends _i1.Mock implements _i9.PriceAPI { - MockPriceAPI() { - _i1.throwOnMissingStub(this); - } - - @override - _i4.Client get client => (super.noSuchMethod( - Invocation.getter(#client), - returnValue: _FakeClient_2( - this, - Invocation.getter(#client), - ), - ) as _i4.Client); - @override - void resetLastCalledToForceNextCallToUpdateCache() => super.noSuchMethod( - Invocation.method( - #resetLastCalledToForceNextCallToUpdateCache, - [], - ), - returnValueForMissingStub: null, - ); - @override - _i6.Future< - Map<_i8.Coin, _i10.Tuple2<_i2.Decimal, double>>> getPricesAnd24hChange( - {required String? baseCurrency}) => - (super.noSuchMethod( - Invocation.method( - #getPricesAnd24hChange, - [], - {#baseCurrency: baseCurrency}, - ), - returnValue: - _i6.Future>>.value( - <_i8.Coin, _i10.Tuple2<_i2.Decimal, double>>{}), - ) as _i6.Future>>); + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); } /// A class which mocks [TransactionNotificationTracker]. /// /// See the documentation for Mockito's code generation for more information. class MockTransactionNotificationTracker extends _i1.Mock - implements _i11.TransactionNotificationTracker { + implements _i8.TransactionNotificationTracker { MockTransactionNotificationTracker() { _i1.throwOnMissingStub(this); } @@ -601,14 +548,14 @@ class MockTransactionNotificationTracker extends _i1.Mock returnValue: false, ) as bool); @override - _i6.Future addNotifiedPending(String? txid) => (super.noSuchMethod( + _i5.Future addNotifiedPending(String? txid) => (super.noSuchMethod( Invocation.method( #addNotifiedPending, [txid], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); @override bool wasNotifiedConfirmed(String? txid) => (super.noSuchMethod( Invocation.method( @@ -618,12 +565,21 @@ class MockTransactionNotificationTracker extends _i1.Mock returnValue: false, ) as bool); @override - _i6.Future addNotifiedConfirmed(String? txid) => (super.noSuchMethod( + _i5.Future addNotifiedConfirmed(String? txid) => (super.noSuchMethod( Invocation.method( #addNotifiedConfirmed, [txid], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future deleteTransaction(String? txid) => (super.noSuchMethod( + Invocation.method( + #deleteTransaction, + [txid], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); } diff --git a/test/services/coins/dogecoin/dogecoin_wallet_test.dart b/test/services/coins/dogecoin/dogecoin_wallet_test.dart index e04eb5cdd..712000aae 100644 --- a/test/services/coins/dogecoin/dogecoin_wallet_test.dart +++ b/test/services/coins/dogecoin/dogecoin_wallet_test.dart @@ -1,37 +1,41 @@ -// import 'dart:typed_data'; - -import 'package:bitcoindart/bitcoindart.dart'; import 'package:decimal/decimal.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:hive/hive.dart'; import 'package:hive_test/hive_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; +import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; -import 'package:stackwallet/hive/db.dart'; import 'package:stackwallet/models/paymint/fee_object_model.dart'; -import 'package:stackwallet/models/paymint/transactions_model.dart'; -import 'package:stackwallet/models/paymint/utxo_model.dart'; import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart'; -import 'package:stackwallet/services/price.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; -import 'dogecoin_history_sample_data.dart'; import 'dogecoin_wallet_test.mocks.dart'; import 'dogecoin_wallet_test_parameters.dart'; -@GenerateMocks( - [ElectrumX, CachedElectrumX, PriceAPI, TransactionNotificationTracker]) +@GenerateMocks([ + ElectrumX, + CachedElectrumX, + TransactionNotificationTracker, +]) void main() { group("dogecoin constants", () { test("dogecoin minimum confirmations", () async { - expect(MINIMUM_CONFIRMATIONS, 3); + expect(MINIMUM_CONFIRMATIONS, 1); }); test("dogecoin dust limit", () async { - expect(DUST_LIMIT, 1000000); + expect( + DUST_LIMIT, + Amount( + rawValue: BigInt.from(1000000), + fractionDigits: 8, + ), + ); }); test("dogecoin mainnet genesis block hash", () async { expect(GENESIS_HASH_MAINNET, @@ -43,60 +47,9 @@ void main() { }); }); - test("dogecoin DerivePathType enum", () { - expect(DerivePathType.values.length, 1); - expect(DerivePathType.values.toString(), "[DerivePathType.bip44]"); - }); - - group("bip32 node/root", () { - test("getBip32Root", () { - final root = getBip32Root(TEST_MNEMONIC, dogecoin); - expect(root.toWIF(), ROOT_WIF); - }); - - // test("getBip32NodeFromRoot", () { - // final root = getBip32Root(TEST_MNEMONIC, dogecoin); - // // two mainnet - // final node44 = getBip32NodeFromRoot(0, 0, root, DerivePathType.bip44); - // expect(node44.toWIF(), NODE_WIF_44); - // - // // a bad derive path - // bool didThrow = false; - // try { - // getBip32NodeFromRoot(0, 0, root, null); - // } catch (_) { - // didThrow = true; - // } - // expect(didThrow, true); - // // finally an invalid network - // didThrow = false; - // final invalidNetwork = NetworkType( - // messagePrefix: '\x18hello world\n', - // bech32: 'gg', - // bip32: Bip32Type(public: 0x055521e, private: 0x055555), - // pubKeyHash: 0x55, - // scriptHash: 0x55, - // wif: 0x00); - // try { - // getBip32NodeFromRoot(0, 0, getBip32Root(TEST_MNEMONIC, invalidNetwork), - // DerivePathType.bip44); - // } catch (_) { - // didThrow = true; - // } - // expect(didThrow, true); - // }); - - test("basic getBip32Node", () { - final node = getBip32Node( - 0, 0, TEST_MNEMONIC, dogecointestnet, DerivePathType.bip44); - expect(node.toWIF(), NODE_WIF_44); - }); - }); - group("validate mainnet dogecoin addresses", () { MockElectrumX? client; MockCachedElectrumX? cachedClient; - MockPriceAPI? priceAPI; late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; @@ -105,7 +58,6 @@ void main() { setUp(() { client = MockElectrumX(); cachedClient = MockCachedElectrumX(); - priceAPI = MockPriceAPI(); secureStore = FakeSecureStorage(); tracker = MockTransactionNotificationTracker(); @@ -116,7 +68,6 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, ); }); @@ -126,11 +77,10 @@ void main() { mainnetWallet?.addressType( address: "DBYiFr1BRc2zB19p8jxdSu6DvFGTdWvkVF"), DerivePathType.bip44); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("invalid base58 address type", () { @@ -138,11 +88,10 @@ void main() { () => mainnetWallet?.addressType( address: "mhqpGtwhcR6gFuuRjLTpHo41919QfuGy8Y"), throwsArgumentError); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("invalid bech32 address type", () { @@ -150,11 +99,10 @@ void main() { () => mainnetWallet?.addressType( address: "tb1qzzlm6mnc8k54mx6akehl8p9ray8r439va5ndyq"), throwsArgumentError); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("address has no matching script", () { @@ -162,40 +110,37 @@ void main() { () => mainnetWallet?.addressType( address: "mpMk94ETazqonHutyC1v6ajshgtP8oiFKU"), throwsArgumentError); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("valid mainnet dogecoin legacy/p2pkh address", () { expect( mainnetWallet?.validateAddress("DBYiFr1BRc2zB19p8jxdSu6DvFGTdWvkVF"), true); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("invalid mainnet dogecoin legacy/p2pkh address", () { expect( mainnetWallet?.validateAddress("mhqpGtwhcR6gFuuRjLTpHo41919QfuGy8Y"), false); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); }); group("testNetworkConnection", () { MockElectrumX? client; MockCachedElectrumX? cachedClient; - MockPriceAPI? priceAPI; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; @@ -204,7 +149,6 @@ void main() { setUp(() { client = MockElectrumX(); cachedClient = MockCachedElectrumX(); - priceAPI = MockPriceAPI(); secureStore = FakeSecureStorage(); tracker = MockTransactionNotificationTracker(); @@ -215,7 +159,6 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, ); }); @@ -224,48 +167,45 @@ void main() { when(client?.ping()).thenAnswer((_) async => false); final bool? result = await doge?.testNetworkConnection(); expect(result, false); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verify(client?.ping()).called(1); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("attempted connection fails due to exception", () async { when(client?.ping()).thenThrow(Exception); final bool? result = await doge?.testNetworkConnection(); expect(result, false); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verify(client?.ping()).called(1); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("attempted connection test success", () async { when(client?.ping()).thenAnswer((_) async => true); final bool? result = await doge?.testNetworkConnection(); expect(result, true); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verify(client?.ping()).called(1); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); }); group("basic getters, setters, and functions", () { - final dcoin = Coin.dogecoin; - final dtestcoin = Coin.dogecoinTestNet; - final testWalletId = "DOGEtestWalletID"; - final testWalletName = "DOGEWallet"; + const dcoin = Coin.dogecoin; + const dtestcoin = Coin.dogecoinTestNet; + const testWalletId = "DOGEtestWalletID"; + const testWalletName = "DOGEWallet"; MockElectrumX? client; MockCachedElectrumX? cachedClient; - MockPriceAPI? priceAPI; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; @@ -274,7 +214,7 @@ void main() { setUp(() async { client = MockElectrumX(); cachedClient = MockCachedElectrumX(); - priceAPI = MockPriceAPI(); + secureStore = FakeSecureStorage(); tracker = MockTransactionNotificationTracker(); @@ -285,18 +225,16 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, ); }); test("get networkType main", () async { expect(doge?.coin, dcoin); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("get networkType test", () async { @@ -307,53 +245,47 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, ); expect(doge?.coin, dtestcoin); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("get cryptoCurrency", () async { expect(Coin.dogecoin, Coin.dogecoin); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("get coinName", () async { expect(Coin.dogecoin, Coin.dogecoin); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("get coinTicker", () async { expect(Coin.dogecoin, Coin.dogecoin); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("get and set walletName", () async { expect(Coin.dogecoin, Coin.dogecoin); doge?.walletName = "new name"; expect(doge?.walletName, "new name"); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("estimateTxFee", () async { @@ -365,24 +297,23 @@ void main() { expect(doge?.estimateTxFee(vSize: 356, feeRatePerKB: 1699), 712); expect(doge?.estimateTxFee(vSize: 356, feeRatePerKB: 2000), 712); expect(doge?.estimateTxFee(vSize: 356, feeRatePerKB: 12345), 4628); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("get fees succeeds", () async { when(client?.ping()).thenAnswer((_) async => true); when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_TESTNET, "hash_function": "sha256", - "services": [] + "services": [] }); when(client?.estimateFee(blocks: 1)) .thenAnswer((realInvocation) async => Decimal.zero); @@ -400,24 +331,23 @@ void main() { verify(client?.estimateFee(blocks: 1)).called(1); verify(client?.estimateFee(blocks: 5)).called(1); verify(client?.estimateFee(blocks: 20)).called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("get fees fails", () async { when(client?.ping()).thenAnswer((_) async => true); when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_TESTNET, "hash_function": "sha256", - "services": [] + "services": [] }); when(client?.estimateFee(blocks: 1)) .thenAnswer((realInvocation) async => Decimal.zero); @@ -438,57 +368,24 @@ void main() { verify(client?.estimateFee(blocks: 1)).called(1); verify(client?.estimateFee(blocks: 5)).called(1); verify(client?.estimateFee(blocks: 20)).called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); - }); - - test("get maxFee", () async { - when(client?.ping()).thenAnswer((_) async => true); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_TESTNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.estimateFee(blocks: 20)) - .thenAnswer((realInvocation) async => Decimal.zero); - when(client?.estimateFee(blocks: 5)) - .thenAnswer((realInvocation) async => Decimal.one); - when(client?.estimateFee(blocks: 1)) - .thenAnswer((realInvocation) async => Decimal.ten); - - final maxFee = await doge?.maxFee; - expect(maxFee, 1000000000); - - verify(client?.estimateFee(blocks: 1)).called(1); - verify(client?.estimateFee(blocks: 5)).called(1); - verify(client?.estimateFee(blocks: 20)).called(1); - expect(secureStore?.interactions, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); }); group("DogeWallet service class functions that depend on shared storage", () { - final dcoin = Coin.dogecoin; - final dtestcoin = Coin.dogecoinTestNet; - final testWalletId = "DOGEtestWalletID"; - final testWalletName = "DOGEWallet"; + const dcoin = Coin.dogecoin; + const dtestcoin = Coin.dogecoinTestNet; + const testWalletId = "DOGEtestWalletID"; + const testWalletName = "DOGEWallet"; bool hiveAdaptersRegistered = false; MockElectrumX? client; MockCachedElectrumX? cachedClient; - MockPriceAPI? priceAPI; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; @@ -499,25 +396,13 @@ void main() { if (!hiveAdaptersRegistered) { hiveAdaptersRegistered = true; - // Registering Transaction Model Adapters - Hive.registerAdapter(TransactionDataAdapter()); - Hive.registerAdapter(TransactionChunkAdapter()); - Hive.registerAdapter(TransactionAdapter()); - Hive.registerAdapter(InputAdapter()); - Hive.registerAdapter(OutputAdapter()); - - // Registering Utxo Model Adapters - Hive.registerAdapter(UtxoDataAdapter()); - Hive.registerAdapter(UtxoObjectAdapter()); - Hive.registerAdapter(StatusAdapter()); - - final wallets = await Hive.openBox('wallets'); + final wallets = await Hive.openBox('wallets'); await wallets.put('currentWalletName', testWalletName); } client = MockElectrumX(); cachedClient = MockCachedElectrumX(); - priceAPI = MockPriceAPI(); + secureStore = FakeSecureStorage(); tracker = MockTransactionNotificationTracker(); @@ -528,7 +413,6 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, ); }); @@ -542,7 +426,7 @@ void main() { // verify(client?.ping()).called(0); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // test("initializeExisting no network exception", () async { @@ -553,19 +437,19 @@ void main() { // verify(client?.ping()).called(1); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // test("initializeNew mainnet throws bad network", () async { // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_TESTNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // // await Hive.openBox(testWalletId); @@ -578,49 +462,48 @@ void main() { // verify(client?.getServerFeatures()).called(1); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // }); test("initializeNew throws mnemonic overwrite exception", () async { when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_MAINNET, "hash_function": "sha256", - "services": [] + "services": [] }); - await secureStore?.write( + await secureStore.write( key: "${testWalletId}_mnemonic", value: "some mnemonic"); await Hive.openBox(testWalletId); await Hive.openBox(DB.boxNamePrefs); - expectLater(() => doge?.initializeNew(), throwsA(isA())) + await expectLater(() => doge?.initializeNew(), throwsA(isA())) .then((_) { - expect(secureStore?.interactions, 2); + expect(secureStore.interactions, 2); verifyNever(client?.ping()).called(0); verify(client?.getServerFeatures()).called(1); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); }); // test("initializeExisting testnet throws bad network", () async { // when(client?.ping()).thenAnswer((_) async => true); // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // // doge = DogecoinWallet( @@ -630,8 +513,9 @@ void main() { // client: client!, // cachedClient: cachedClient!, // tracker: tracker!, - // priceAPI: priceAPI, + // // secureStore: secureStore, + // // ); // // await Hive.openBox(testWalletId); @@ -644,7 +528,7 @@ void main() { // verify(client?.getServerFeatures()).called(1); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // }); @@ -653,14 +537,14 @@ void main() { // // .thenAnswer((realInvocation) async => Decimal.fromInt(10)); // when(client?.ping()).thenAnswer((_) async => true); // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // // await DebugService.instance.init(); // expect(doge?.initializeExisting(), true); @@ -675,7 +559,7 @@ void main() { // expect(didThrow, true); // // // set node - // final wallet = await Hive.openBox(testWalletId); + // final wallet = await Hive.openBox (testWalletId); // await wallet.put("nodes", { // "default": { // "id": "some nodeID", @@ -695,7 +579,7 @@ void main() { // verify(client?.getServerFeatures()).called(1); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // test("initializeWallet new main net wallet", () async { @@ -703,18 +587,18 @@ void main() { // .thenAnswer((realInvocation) async => Decimal.fromInt(10)); // when(client?.ping()).thenAnswer((_) async => true); // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // expect(await doge?.initializeWallet(), true); // - // final wallet = await Hive.openBox(testWalletId); + // final wallet = await Hive.openBox (testWalletId); // // expect(await wallet.get("addressBookEntries"), {}); // expect(await wallet.get('notes'), null); @@ -749,7 +633,7 @@ void main() { // verify(client?.getServerFeatures()).called(1); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("initializeWallet existing main net wallet", () async { @@ -759,20 +643,20 @@ void main() { // // when(client?.getBatchHistory(args: anyNamed("args"))) // // .thenAnswer((_) async => {}); // // when(client?.getServerFeatures()).thenAnswer((_) async => { - // // "hosts": {}, + // // "hosts": {}, // // "pruning": null, // // "server_version": "Unit tests", // // "protocol_min": "1.4", // // "protocol_max": "1.4.2", // // "genesis_hash": GENESIS_HASH_MAINNET, // // "hash_function": "sha256", - // // "services": [] + // // "services": [] // // }); // // // init new wallet // // expect(doge?.initializeNew(), true); // // // // // fetch data to compare later - // // final newWallet = await Hive.openBox(testWalletId); + // // final newWallet = await Hive.openBox (testWalletId); // // // // final addressBookEntries = await newWallet.get("addressBookEntries"); // // final notes = await newWallet.get('notes'); @@ -803,15 +687,16 @@ void main() { // // coin: dtestcoin, // // client: client!, // // cachedClient: cachedClient!, - // // priceAPI: priceAPI, + // // // // secureStore: secureStore, + // // // ); // // // // // init existing // // expect(doge?.initializeExisting(), true); // // // // // compare data to ensure state matches state of previously closed wallet - // // final wallet = await Hive.openBox(testWalletId); + // // final wallet = await Hive.openBox (testWalletId); // // // // expect(await wallet.get("addressBookEntries"), addressBookEntries); // // expect(await wallet.get('notes'), notes); @@ -844,99 +729,54 @@ void main() { // // verify(client?.getServerFeatures()).called(1); // // verifyNoMoreInteractions(client); // // verifyNoMoreInteractions(cachedClient); - // // verifyNoMoreInteractions(priceAPI); + // // // // }); - test("get current receiving addresses", () async { - doge = DogecoinWallet( - walletId: testWalletId, - walletName: testWalletName, - coin: dtestcoin, - client: client!, - cachedClient: cachedClient!, - tracker: tracker!, - priceAPI: priceAPI, - secureStore: secureStore, - ); - when(client?.ping()).thenAnswer((_) async => true); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_TESTNET, - "hash_function": "sha256", - "services": [] - }); - - await Hive.openBox(testWalletId); - await Hive.openBox(DB.boxNamePrefs); - - await doge?.initializeNew(); - await doge?.initializeExisting(); - expect( - Address.validateAddress( - await doge!.currentReceivingAddress, dogecointestnet), - true); - expect( - Address.validateAddress( - await doge!.currentReceivingAddress, dogecointestnet), - true); - expect( - Address.validateAddress( - await doge!.currentReceivingAddress, dogecointestnet), - true); - - verifyNever(client?.ping()).called(0); - verify(client?.getServerFeatures()).called(1); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); - - test("get allOwnAddresses", () async { - doge = DogecoinWallet( - walletId: testWalletId, - walletName: testWalletName, - coin: dtestcoin, - client: client!, - cachedClient: cachedClient!, - tracker: tracker!, - priceAPI: priceAPI, - secureStore: secureStore, - ); - when(client?.ping()).thenAnswer((_) async => true); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_TESTNET, - "hash_function": "sha256", - "services": [] - }); - - await Hive.openBox(testWalletId); - await Hive.openBox(DB.boxNamePrefs); - - await doge?.initializeNew(); - await doge?.initializeExisting(); - final addresses = await doge?.allOwnAddresses; - expect(addresses, isA>()); - expect(addresses?.length, 2); - - for (int i = 0; i < 2; i++) { - expect(Address.validateAddress(addresses![i], dogecointestnet), true); - } - - verifyNever(client?.ping()).called(0); - verify(client?.getServerFeatures()).called(1); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); + // test("get current receiving addresses", () async { + // doge = DogecoinWallet( + // walletId: testWalletId, + // walletName: testWalletName, + // coin: dtestcoin, + // client: client!, + // cachedClient: cachedClient!, + // tracker: tracker!, + // secureStore: secureStore, + // ); + // when(client?.ping()).thenAnswer((_) async => true); + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_TESTNET, + // "hash_function": "sha256", + // "services": [] + // }); + // + // await Hive.openBox(testWalletId); + // await Hive.openBox(DB.boxNamePrefs); + // + // await doge?.initializeNew(); + // await doge?.initializeExisting(); + // expect( + // Address.validateAddress( + // await doge!.currentReceivingAddress, dogecointestnet), + // true); + // expect( + // Address.validateAddress( + // await doge!.currentReceivingAddress, dogecointestnet), + // true); + // expect( + // Address.validateAddress( + // await doge!.currentReceivingAddress, dogecointestnet), + // true); + // + // verifyNever(client?.ping()).called(0); + // verify(client?.getServerFeatures()).called(1); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); // test("get utxos and balances", () async { // doge = DogecoinWallet( @@ -946,19 +786,20 @@ void main() { // client: client!, // cachedClient: cachedClient!, // tracker: tracker!, - // priceAPI: priceAPI, + // // secureStore: secureStore, + // // ); // when(client?.ping()).thenAnswer((_) async => true); // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_TESTNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // // await Hive.openBox(testWalletId); @@ -1040,7 +881,7 @@ void main() { // // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // // test("get utxos - multiple batches", () async { @@ -1050,19 +891,20 @@ void main() { // // coin: dtestcoin, // // client: client!, // // cachedClient: cachedClient!, - // // priceAPI: priceAPI, + // // // // secureStore: secureStore, + // // // ); // // when(client?.ping()).thenAnswer((_) async => true); // // when(client?.getServerFeatures()).thenAnswer((_) async => { - // // "hosts": {}, + // // "hosts": {}, // // "pruning": null, // // "server_version": "Unit tests", // // "protocol_min": "1.4", // // "protocol_max": "1.4.2", // // "genesis_hash": GENESIS_HASH_TESTNET, // // "hash_function": "sha256", - // // "services": [] + // // "services": [] // // }); // // // // when(client?.getBatchUTXOs(args: anyNamed("args"))) @@ -1074,7 +916,7 @@ void main() { // // await doge?.initializeWallet(); // // // // // add some extra addresses to make sure we have more than the single batch size of 10 - // // final wallet = await Hive.openBox(testWalletId); + // // final wallet = await Hive.openBox (testWalletId); // // final addresses = await wallet.get("receivingAddressesP2PKH"); // // addresses.add("DQaAi9R58GXMpDyhePys6hHCuif4fhc1sN"); // // addresses.add("DBVhuF8QgeuxU2pssxzMgJqPhGCx5qyVkD"); @@ -1103,116 +945,107 @@ void main() { // // // // verifyNoMoreInteractions(client); // // verifyNoMoreInteractions(cachedClient); - // // verifyNoMoreInteractions(priceAPI); + // // // // }); // - test("get utxos fails", () async { - doge = DogecoinWallet( - walletId: testWalletId, - walletName: testWalletName, - coin: dtestcoin, - client: client!, - cachedClient: cachedClient!, - tracker: tracker!, - priceAPI: priceAPI, - secureStore: secureStore, - ); - when(client?.ping()).thenAnswer((_) async => true); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_TESTNET, - "hash_function": "sha256", - "services": [] - }); - - await Hive.openBox(testWalletId); - await Hive.openBox(DB.boxNamePrefs); - - when(client?.getBatchUTXOs(args: anyNamed("args"))) - .thenThrow(Exception("some exception")); - - await doge?.initializeNew(); - await doge?.initializeExisting(); - - final utxoData = await doge?.utxoData; - expect(utxoData, isA()); - expect(utxoData.toString(), - r"{totalUserCurrency: 0.00, satoshiBalance: 0, bitcoinBalance: 0, unspentOutputArray: []}"); - - final outputs = await doge?.unspentOutputs; - expect(outputs, isA>()); - expect(outputs?.length, 0); - - verifyNever(client?.ping()).called(0); - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchUTXOs(args: anyNamed("args"))).called(1); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); - - test("chain height fetch, update, and get", () async { - doge = DogecoinWallet( - walletId: testWalletId, - walletName: testWalletName, - coin: dtestcoin, - client: client!, - cachedClient: cachedClient!, - tracker: tracker!, - priceAPI: priceAPI, - secureStore: secureStore, - ); - when(client?.ping()).thenAnswer((_) async => true); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_TESTNET, - "hash_function": "sha256", - "services": [] - }); - - await Hive.openBox(testWalletId); - await Hive.openBox(DB.boxNamePrefs); - - await doge?.initializeNew(); - await doge?.initializeExisting(); - - // get stored - expect(await doge?.storedChainHeight, 0); - - // fetch fails - when(client?.getBlockHeadTip()).thenThrow(Exception("Some exception")); - expect(await doge?.chainHeight, -1); - - // fetch succeeds - when(client?.getBlockHeadTip()).thenAnswer((realInvocation) async => { - "height": 100, - "hex": "some block hex", - }); - expect(await doge?.chainHeight, 100); - - // update - await doge?.updateStoredChainHeight(newHeight: 1000); - - // fetch updated - expect(await doge?.storedChainHeight, 1000); - - verifyNever(client?.ping()).called(0); - verify(client?.getServerFeatures()).called(1); - verify(client?.getBlockHeadTip()).called(2); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); - }); + // test("get utxos fails", () async { + // doge = DogecoinWallet( + // walletId: testWalletId, + // walletName: testWalletName, + // coin: dtestcoin, + // client: client!, + // cachedClient: cachedClient!, + // tracker: tracker!, + // secureStore: secureStore, + // ); + // when(client?.ping()).thenAnswer((_) async => true); + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_TESTNET, + // "hash_function": "sha256", + // "services": [] + // }); + // + // await Hive.openBox(testWalletId); + // await Hive.openBox(DB.boxNamePrefs); + // + // when(client?.getBatchUTXOs(args: anyNamed("args"))) + // .thenThrow(Exception("some exception")); + // + // await doge?.initializeNew(); + // await doge?.initializeExisting(); + // + // final outputs = await doge!.utxos; + // expect(outputs, isA>()); + // expect(outputs.length, 0); + // + // verifyNever(client?.ping()).called(0); + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchUTXOs(args: anyNamed("args"))).called(1); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("chain height fetch, update, and get", () async { + // doge = DogecoinWallet( + // walletId: testWalletId, + // walletName: testWalletName, + // coin: dtestcoin, + // client: client!, + // cachedClient: cachedClient!, + // tracker: tracker!, + // secureStore: secureStore, + // ); + // when(client?.ping()).thenAnswer((_) async => true); + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_TESTNET, + // "hash_function": "sha256", + // "services": [] + // }); + // + // await Hive.openBox(testWalletId); + // await Hive.openBox(DB.boxNamePrefs); + // + // await doge?.initializeNew(); + // await doge?.initializeExisting(); + // + // // get stored + // expect(doge?.storedChainHeight, 0); + // + // // fetch fails + // when(client?.getBlockHeadTip()).thenThrow(Exception("Some exception")); + // expect(await doge?.chainHeight, -1); + // + // // fetch succeeds + // when(client?.getBlockHeadTip()).thenAnswer((realInvocation) async => { + // "height": 100, + // "hex": "some block hex", + // }); + // expect(await doge?.chainHeight, 100); + // + // // update + // await doge?.updateCachedChainHeight(1000); + // + // // fetch updated + // expect(doge?.storedChainHeight, 1000); + // + // verifyNever(client?.ping()).called(0); + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBlockHeadTip()).called(2); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // }); test("getTxCount succeeds", () async { when(client?.getHistory( @@ -1241,11 +1074,10 @@ void main() { "64953f7db441a21172de206bf70b920c8c718ed4f03df9a85073c0400be0053c")) .called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("getTxCount fails", () async { @@ -1267,203 +1099,198 @@ void main() { "64953f7db441a21172de206bf70b920c8c718ed4f03df9a85073c0400be0053c")) .called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); - test("_checkCurrentReceivingAddressesForTransactions succeeds", () async { - when(client?.ping()).thenAnswer((_) async => true); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getHistory(scripthash: anyNamed("scripthash"))) - .thenAnswer((realInvocation) async => [ - { - "height": 4270385, - "tx_hash": - "c07f740ad72c0dd759741f4c9ab4b1586a22bc16545584364ac9b3d845766271" - }, - { - "height": 4270459, - "tx_hash": - "82da70c660daf4d42abd403795d047918c4021ff1d706b61790cda01a1c5ae5a" - } - ]); - - await Hive.openBox(testWalletId); - await Hive.openBox(DB.boxNamePrefs); - - await doge?.initializeNew(); - await doge?.initializeExisting(); - - bool didThrow = false; - try { - await doge?.checkCurrentReceivingAddressesForTransactions(); - } catch (_) { - didThrow = true; - } - expect(didThrow, false); - - verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); - verify(client?.getServerFeatures()).called(1); - verifyNever(client?.ping()).called(0); - - expect(secureStore?.interactions, 11); - expect(secureStore?.reads, 7); - expect(secureStore?.writes, 4); - expect(secureStore?.deletes, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); - - test("_checkCurrentReceivingAddressesForTransactions fails", () async { - when(client?.ping()).thenAnswer((_) async => true); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getHistory(scripthash: anyNamed("scripthash"))) - .thenThrow(Exception("some exception")); - - await Hive.openBox(testWalletId); - await Hive.openBox(DB.boxNamePrefs); - - await doge?.initializeNew(); - await doge?.initializeExisting(); - - bool didThrow = false; - try { - await doge?.checkCurrentReceivingAddressesForTransactions(); - } catch (_) { - didThrow = true; - } - expect(didThrow, true); - - verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); - verify(client?.getServerFeatures()).called(1); - verifyNever(client?.ping()).called(0); - - expect(secureStore?.interactions, 8); - expect(secureStore?.reads, 5); - expect(secureStore?.writes, 3); - expect(secureStore?.deletes, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); - - test("_checkCurrentChangeAddressesForTransactions succeeds", () async { - when(client?.ping()).thenAnswer((_) async => true); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getHistory(scripthash: anyNamed("scripthash"))) - .thenAnswer((realInvocation) async => [ - { - "height": 4286283, - "tx_hash": - "4c119685401e28982283e644c57d84fde6aab83324012e35c9b49e6efd99b49b" - }, - { - "height": 4286295, - "tx_hash": - "82da70c660daf4d42abd403795d047918c4021ff1d706b61790cda01a1c5ae5a" - } - ]); - - await Hive.openBox(testWalletId); - await Hive.openBox(DB.boxNamePrefs); - - await doge?.initializeNew(); - await doge?.initializeExisting(); - - bool didThrow = false; - try { - await doge?.checkCurrentChangeAddressesForTransactions(); - } catch (_) { - didThrow = true; - } - expect(didThrow, false); - - verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); - verify(client?.getServerFeatures()).called(1); - verifyNever(client?.ping()).called(0); - - expect(secureStore?.interactions, 11); - expect(secureStore?.reads, 7); - expect(secureStore?.writes, 4); - expect(secureStore?.deletes, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); - }); - - test("_checkCurrentChangeAddressesForTransactions fails", () async { - when(client?.ping()).thenAnswer((_) async => true); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getHistory(scripthash: anyNamed("scripthash"))) - .thenThrow(Exception("some exception")); - - await Hive.openBox(testWalletId); - await Hive.openBox(DB.boxNamePrefs); - - await doge?.initializeNew(); - await doge?.initializeExisting(); - - bool didThrow = false; - try { - await doge?.checkCurrentChangeAddressesForTransactions(); - } catch (_) { - didThrow = true; - } - expect(didThrow, true); - - verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); - verify(client?.getServerFeatures()).called(1); - verifyNever(client?.ping()).called(0); - - expect(secureStore?.interactions, 8); - expect(secureStore?.reads, 5); - expect(secureStore?.writes, 3); - expect(secureStore?.deletes, 0); - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); + // test("_checkCurrentReceivingAddressesForTransactions succeeds", () async { + // when(client?.ping()).thenAnswer((_) async => true); + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getHistory(scripthash: anyNamed("scripthash"))) + // .thenAnswer((realInvocation) async => [ + // { + // "height": 4270385, + // "tx_hash": + // "c07f740ad72c0dd759741f4c9ab4b1586a22bc16545584364ac9b3d845766271" + // }, + // { + // "height": 4270459, + // "tx_hash": + // "82da70c660daf4d42abd403795d047918c4021ff1d706b61790cda01a1c5ae5a" + // } + // ]); + // + // await Hive.openBox(testWalletId); + // await Hive.openBox(DB.boxNamePrefs); + // + // await doge?.initializeNew(); + // await doge?.initializeExisting(); + // + // bool didThrow = false; + // try { + // await doge?.checkCurrentReceivingAddressesForTransactions(); + // } catch (_) { + // didThrow = true; + // } + // expect(didThrow, false); + // + // verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); + // verify(client?.getServerFeatures()).called(1); + // verifyNever(client?.ping()).called(0); + // + // expect(secureStore.interactions, 11); + // expect(secureStore.reads, 7); + // expect(secureStore.writes, 4); + // expect(secureStore.deletes, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("_checkCurrentReceivingAddressesForTransactions fails", () async { + // when(client?.ping()).thenAnswer((_) async => true); + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getHistory(scripthash: anyNamed("scripthash"))) + // .thenThrow(Exception("some exception")); + // + // await Hive.openBox(testWalletId); + // await Hive.openBox(DB.boxNamePrefs); + // + // await doge?.initializeNew(); + // await doge?.initializeExisting(); + // + // bool didThrow = false; + // try { + // await doge?.checkCurrentReceivingAddressesForTransactions(); + // } catch (_) { + // didThrow = true; + // } + // expect(didThrow, true); + // + // verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); + // verify(client?.getServerFeatures()).called(1); + // verifyNever(client?.ping()).called(0); + // + // expect(secureStore.interactions, 8); + // expect(secureStore.reads, 5); + // expect(secureStore.writes, 3); + // expect(secureStore.deletes, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("_checkCurrentChangeAddressesForTransactions succeeds", () async { + // when(client?.ping()).thenAnswer((_) async => true); + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getHistory(scripthash: anyNamed("scripthash"))) + // .thenAnswer((realInvocation) async => [ + // { + // "height": 4286283, + // "tx_hash": + // "4c119685401e28982283e644c57d84fde6aab83324012e35c9b49e6efd99b49b" + // }, + // { + // "height": 4286295, + // "tx_hash": + // "82da70c660daf4d42abd403795d047918c4021ff1d706b61790cda01a1c5ae5a" + // } + // ]); + // + // await Hive.openBox(testWalletId); + // await Hive.openBox(DB.boxNamePrefs); + // + // await doge?.initializeNew(); + // await doge?.initializeExisting(); + // + // bool didThrow = false; + // try { + // await doge?.checkCurrentChangeAddressesForTransactions(); + // } catch (_) { + // didThrow = true; + // } + // expect(didThrow, false); + // + // verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); + // verify(client?.getServerFeatures()).called(1); + // verifyNever(client?.ping()).called(0); + // + // expect(secureStore.interactions, 11); + // expect(secureStore.reads, 7); + // expect(secureStore.writes, 4); + // expect(secureStore.deletes, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // }); + // + // test("_checkCurrentChangeAddressesForTransactions fails", () async { + // when(client?.ping()).thenAnswer((_) async => true); + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getHistory(scripthash: anyNamed("scripthash"))) + // .thenThrow(Exception("some exception")); + // + // await Hive.openBox(testWalletId); + // await Hive.openBox(DB.boxNamePrefs); + // + // await doge?.initializeNew(); + // await doge?.initializeExisting(); + // + // bool didThrow = false; + // try { + // await doge?.checkCurrentChangeAddressesForTransactions(); + // } catch (_) { + // didThrow = true; + // } + // expect(didThrow, true); + // + // verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); + // verify(client?.getServerFeatures()).called(1); + // verifyNever(client?.ping()).called(0); + // + // expect(secureStore.interactions, 8); + // expect(secureStore.reads, 5); + // expect(secureStore.writes, 3); + // expect(secureStore.deletes, 0); + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); // test("getAllTxsToWatch", () async { // TestWidgetsFlutterBinding.ensureInitialized(); @@ -1490,7 +1317,7 @@ void main() { // expect(secureStore?.interactions, 0); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("refreshIfThereIsNewData true A", () async { @@ -1509,10 +1336,11 @@ void main() { // coin: dtestcoin, // client: client!, // cachedClient: cachedClient!, - // priceAPI: priceAPI, + // // secureStore: secureStore, + // // ); - // final wallet = await Hive.openBox(testWalletId); + // final wallet = await Hive.openBox (testWalletId); // await wallet.put('receivingAddressesP2PKH', []); // // await wallet.put('changeAddressesP2PKH', []); @@ -1538,7 +1366,7 @@ void main() { // expect(secureStore?.interactions, 0); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("refreshIfThereIsNewData true B", () async { @@ -1638,10 +1466,11 @@ void main() { // coin: dtestcoin, // client: client!, // cachedClient: cachedClient!, - // priceAPI: priceAPI, + // // secureStore: secureStore, + // // ); - // final wallet = await Hive.openBox(testWalletId); + // final wallet = await Hive.openBox (testWalletId); // await wallet.put('receivingAddressesP2PKH', []); // // await wallet.put('changeAddressesP2PKH', []); @@ -1670,7 +1499,7 @@ void main() { // expect(secureStore?.interactions, 0); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // test("refreshIfThereIsNewData false A", () async { @@ -1771,10 +1600,11 @@ void main() { // client: client!, // cachedClient: cachedClient!, // tracker: tracker!, - // priceAPI: priceAPI, + // // secureStore: secureStore, + // // ); - // final wallet = await Hive.openBox(testWalletId); + // final wallet = await Hive.openBox (testWalletId); // await wallet.put('receivingAddressesP2PKH', []); // // await wallet.put('changeAddressesP2PKH', []); @@ -1804,7 +1634,7 @@ void main() { // expect(secureStore?.interactions, 0); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // // test("refreshIfThereIsNewData false B", () async { @@ -1823,10 +1653,11 @@ void main() { // // client: client!, // // cachedClient: cachedClient!, // // tracker: tracker!, - // // priceAPI: priceAPI, + // // // // secureStore: secureStore, + // // // ); - // // final wallet = await Hive.openBox(testWalletId); + // // final wallet = await Hive.openBox (testWalletId); // // await wallet.put('receivingAddressesP2PKH', []); // // // // await wallet.put('changeAddressesP2PKH', []); @@ -1848,58 +1679,57 @@ void main() { // // expect(secureStore?.interactions, 0); // // verifyNoMoreInteractions(client); // // verifyNoMoreInteractions(cachedClient); - // // verifyNoMoreInteractions(priceAPI); + // // // // }); - test("get mnemonic list", () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - - final wallet = await Hive.openBox(testWalletId); - - // add maxNumberOfIndexesToCheck and height - await doge?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - expect(await doge?.mnemonic, TEST_MNEMONIC.split(" ")); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); - }); + // test("get mnemonic list", () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // + // await Hive.openBox(testWalletId); + // + // // add maxNumberOfIndexesToCheck and height + // await doge?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // expect(await doge?.mnemonic, TEST_MNEMONIC.split(" ")); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // }); test( "recoverFromMnemonic using empty seed on mainnet fails due to bad genesis hash match", () async { when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_TESTNET, "hash_function": "sha256", - "services": [] + "services": [] }); bool hasThrown = false; @@ -1916,10 +1746,9 @@ void main() { verify(client?.getServerFeatures()).called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test( @@ -1932,18 +1761,17 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, ); when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_MAINNET, "hash_function": "sha256", - "services": [] + "services": [] }); bool hasThrown = false; @@ -1960,27 +1788,26 @@ void main() { verify(client?.getServerFeatures()).called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test( "recoverFromMnemonic using empty seed on mainnet fails due to attempted overwrite of mnemonic", () async { when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_MAINNET, "hash_function": "sha256", - "services": [] + "services": [] }); - await secureStore?.write( + await secureStore.write( key: "${testWalletId}_mnemonic", value: "some mnemonic words"); bool hasThrown = false; @@ -1997,319 +1824,315 @@ void main() { verify(client?.getServerFeatures()).called(1); - expect(secureStore?.interactions, 2); + expect(secureStore.interactions, 2); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); - test("recoverFromMnemonic using non empty seed on mainnet succeeds", - () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); - - when(client?.getBatchHistory(args: { - "0": [ - "c82d4ac9697408d423d59dc53267f6474bbd4c22c55fd42ba766e80c6068e7dc" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - when(client?.getBatchHistory(args: { - "0": [ - "80badd62a8dd884cc7f61d962484564929340debb27f88fef270e553306a030c" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - final wallet = await Hive.openBox(testWalletId); - - bool hasThrown = false; - try { - await doge?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - } catch (_) { - hasThrown = true; - } - expect(hasThrown, false); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - verify(client?.getBatchHistory(args: { - "0": [ - "c82d4ac9697408d423d59dc53267f6474bbd4c22c55fd42ba766e80c6068e7dc" - ] - })).called(1); - verify(client?.getBatchHistory(args: { - "0": [ - "80badd62a8dd884cc7f61d962484564929340debb27f88fef270e553306a030c" - ] - })).called(1); - - expect(secureStore?.interactions, 6); - expect(secureStore?.writes, 3); - expect(secureStore?.reads, 3); - expect(secureStore?.deletes, 0); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); - }); - - test("fullRescan succeeds", () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); - when(cachedClient?.clearSharedTransactionCache(coin: Coin.dogecoin)) - .thenAnswer((realInvocation) async {}); - - when(client?.getBatchHistory(args: { - "0": [ - "c82d4ac9697408d423d59dc53267f6474bbd4c22c55fd42ba766e80c6068e7dc" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - when(client?.getBatchHistory(args: { - "0": [ - "80badd62a8dd884cc7f61d962484564929340debb27f88fef270e553306a030c" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - final wallet = await Hive.openBox(testWalletId); - - // restore so we have something to rescan - await doge?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - // fetch valid wallet data - final preReceivingAddressesP2PKH = - await wallet.get('receivingAddressesP2PKH'); - final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); - final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); - final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); - final preUtxoData = await wallet.get('latest_utxo_model'); - final preReceiveDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2PKH"); - final preChangeDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_changeDerivationsP2PKH"); - - // destroy the data that the rescan will fix - await wallet.put( - 'receivingAddressesP2PKH', ["some address", "some other address"]); - await wallet - .put('changeAddressesP2PKH', ["some address", "some other address"]); - - await wallet.put('receivingIndexP2PKH', 123); - await wallet.put('changeIndexP2PKH', 123); - await secureStore?.write( - key: "${testWalletId}_receiveDerivationsP2PKH", value: "{}"); - await secureStore?.write( - key: "${testWalletId}_changeDerivationsP2PKH", value: "{}"); - - bool hasThrown = false; - try { - await doge?.fullRescan(2, 1000); - } catch (_) { - hasThrown = true; - } - expect(hasThrown, false); - - // fetch wallet data again - final receivingAddressesP2PKH = - await wallet.get('receivingAddressesP2PKH'); - final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); - final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); - final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); - final utxoData = await wallet.get('latest_utxo_model'); - final receiveDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2PKH"); - final changeDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_changeDerivationsP2PKH"); - - expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); - expect(preChangeAddressesP2PKH, changeAddressesP2PKH); - expect(preReceivingIndexP2PKH, receivingIndexP2PKH); - expect(preChangeIndexP2PKH, changeIndexP2PKH); - expect(preUtxoData, utxoData); - expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); - expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); - verify(client?.getBatchHistory(args: { - "0": [ - "c82d4ac9697408d423d59dc53267f6474bbd4c22c55fd42ba766e80c6068e7dc" - ] - })).called(2); - verify(client?.getBatchHistory(args: { - "0": [ - "80badd62a8dd884cc7f61d962484564929340debb27f88fef270e553306a030c" - ] - })).called(2); - verify(cachedClient?.clearSharedTransactionCache(coin: Coin.dogecoin)) - .called(1); - - expect(secureStore?.writes, 9); - expect(secureStore?.reads, 12); - expect(secureStore?.deletes, 2); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); - }); - - test("fullRescan fails", () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); - - when(client?.getBatchHistory(args: { - "0": [ - "c82d4ac9697408d423d59dc53267f6474bbd4c22c55fd42ba766e80c6068e7dc" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - when(client?.getBatchHistory(args: { - "0": [ - "80badd62a8dd884cc7f61d962484564929340debb27f88fef270e553306a030c" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - when(cachedClient?.clearSharedTransactionCache(coin: Coin.dogecoin)) - .thenAnswer((realInvocation) async {}); - - final wallet = await Hive.openBox(testWalletId); - - // restore so we have something to rescan - await doge?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - // fetch wallet data - final preReceivingAddressesP2PKH = - await wallet.get('receivingAddressesP2PKH'); - - final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); - final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); - final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); - final preUtxoData = await wallet.get('latest_utxo_model'); - final preReceiveDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2PKH"); - final preChangeDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_changeDerivationsP2PKH"); - - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenThrow(Exception("fake exception")); - - bool hasThrown = false; - try { - await doge?.fullRescan(2, 1000); - } catch (_) { - hasThrown = true; - } - expect(hasThrown, true); - - // fetch wallet data again - final receivingAddressesP2PKH = - await wallet.get('receivingAddressesP2PKH'); - - final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); - final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); - final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); - final utxoData = await wallet.get('latest_utxo_model'); - final receiveDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_receiveDerivationsP2PKH"); - final changeDerivationsStringP2PKH = await secureStore?.read( - key: "${testWalletId}_changeDerivationsP2PKH"); - - expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); - expect(preChangeAddressesP2PKH, changeAddressesP2PKH); - expect(preReceivingIndexP2PKH, receivingIndexP2PKH); - expect(preChangeIndexP2PKH, changeIndexP2PKH); - expect(preUtxoData, utxoData); - expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); - expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); - verify(client?.getBatchHistory(args: { - "0": [ - "c82d4ac9697408d423d59dc53267f6474bbd4c22c55fd42ba766e80c6068e7dc" - ] - })).called(2); - verify(client?.getBatchHistory(args: { - "0": [ - "80badd62a8dd884cc7f61d962484564929340debb27f88fef270e553306a030c" - ] - })).called(1); - verify(cachedClient?.clearSharedTransactionCache(coin: Coin.dogecoin)) - .called(1); - - expect(secureStore?.writes, 7); - expect(secureStore?.reads, 12); - expect(secureStore?.deletes, 4); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); - }); + // test("recoverFromMnemonic using non empty seed on mainnet succeeds", + // () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "c82d4ac9697408d423d59dc53267f6474bbd4c22c55fd42ba766e80c6068e7dc" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "80badd62a8dd884cc7f61d962484564929340debb27f88fef270e553306a030c" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // await Hive.openBox(testWalletId); + // + // bool hasThrown = false; + // try { + // await doge?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // } catch (_) { + // hasThrown = true; + // } + // expect(hasThrown, false); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "c82d4ac9697408d423d59dc53267f6474bbd4c22c55fd42ba766e80c6068e7dc" + // ] + // })).called(1); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "80badd62a8dd884cc7f61d962484564929340debb27f88fef270e553306a030c" + // ] + // })).called(1); + // + // expect(secureStore.interactions, 6); + // expect(secureStore.writes, 3); + // expect(secureStore.reads, 3); + // expect(secureStore.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // }); + // + // test("fullRescan succeeds", () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); + // when(cachedClient?.clearSharedTransactionCache(coin: Coin.dogecoin)) + // .thenAnswer((realInvocation) async {}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "c82d4ac9697408d423d59dc53267f6474bbd4c22c55fd42ba766e80c6068e7dc" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "80badd62a8dd884cc7f61d962484564929340debb27f88fef270e553306a030c" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // final wallet = await Hive.openBox(testWalletId); + // + // // restore so we have something to rescan + // await doge?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // // fetch valid wallet data + // final preReceivingAddressesP2PKH = + // await wallet.get('receivingAddressesP2PKH'); + // final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + // final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + // final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + // final preUtxoData = await wallet.get('latest_utxo_model'); + // final preReceiveDerivationsStringP2PKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2PKH"); + // final preChangeDerivationsStringP2PKH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + // + // // destroy the data that the rescan will fix + // await wallet.put( + // 'receivingAddressesP2PKH', ["some address", "some other address"]); + // await wallet + // .put('changeAddressesP2PKH', ["some address", "some other address"]); + // + // await wallet.put('receivingIndexP2PKH', 123); + // await wallet.put('changeIndexP2PKH', 123); + // await secureStore.write( + // key: "${testWalletId}_receiveDerivationsP2PKH", value: "{}"); + // await secureStore.write( + // key: "${testWalletId}_changeDerivationsP2PKH", value: "{}"); + // + // bool hasThrown = false; + // try { + // await doge?.fullRescan(2, 1000); + // } catch (_) { + // hasThrown = true; + // } + // expect(hasThrown, false); + // + // // fetch wallet data again + // final receivingAddressesP2PKH = + // await wallet.get('receivingAddressesP2PKH'); + // final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + // final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + // final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + // final utxoData = await wallet.get('latest_utxo_model'); + // final receiveDerivationsStringP2PKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2PKH"); + // final changeDerivationsStringP2PKH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + // + // expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); + // expect(preChangeAddressesP2PKH, changeAddressesP2PKH); + // expect(preReceivingIndexP2PKH, receivingIndexP2PKH); + // expect(preChangeIndexP2PKH, changeIndexP2PKH); + // expect(preUtxoData, utxoData); + // expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); + // expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "c82d4ac9697408d423d59dc53267f6474bbd4c22c55fd42ba766e80c6068e7dc" + // ] + // })).called(2); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "80badd62a8dd884cc7f61d962484564929340debb27f88fef270e553306a030c" + // ] + // })).called(2); + // verify(cachedClient?.clearSharedTransactionCache(coin: Coin.dogecoin)) + // .called(1); + // + // expect(secureStore.writes, 9); + // expect(secureStore.reads, 12); + // expect(secureStore.deletes, 2); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // }); + // + // test("fullRescan fails", () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "c82d4ac9697408d423d59dc53267f6474bbd4c22c55fd42ba766e80c6068e7dc" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "80badd62a8dd884cc7f61d962484564929340debb27f88fef270e553306a030c" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // when(cachedClient?.clearSharedTransactionCache(coin: Coin.dogecoin)) + // .thenAnswer((realInvocation) async {}); + // + // final wallet = await Hive.openBox(testWalletId); + // + // // restore so we have something to rescan + // await doge?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // // fetch wallet data + // final preReceivingAddressesP2PKH = + // await wallet.get('receivingAddressesP2PKH'); + // + // final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + // final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + // final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + // final preUtxoData = await wallet.get('latest_utxo_model'); + // final preReceiveDerivationsStringP2PKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2PKH"); + // final preChangeDerivationsStringP2PKH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + // + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenThrow(Exception("fake exception")); + // + // bool hasThrown = false; + // try { + // await doge?.fullRescan(2, 1000); + // } catch (_) { + // hasThrown = true; + // } + // expect(hasThrown, true); + // + // // fetch wallet data again + // final receivingAddressesP2PKH = + // await wallet.get('receivingAddressesP2PKH'); + // + // final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + // final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + // final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + // final utxoData = await wallet.get('latest_utxo_model'); + // final receiveDerivationsStringP2PKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2PKH"); + // final changeDerivationsStringP2PKH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + // + // expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); + // expect(preChangeAddressesP2PKH, changeAddressesP2PKH); + // expect(preReceivingIndexP2PKH, receivingIndexP2PKH); + // expect(preChangeIndexP2PKH, changeIndexP2PKH); + // expect(preUtxoData, utxoData); + // expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); + // expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "c82d4ac9697408d423d59dc53267f6474bbd4c22c55fd42ba766e80c6068e7dc" + // ] + // })).called(2); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "80badd62a8dd884cc7f61d962484564929340debb27f88fef270e553306a030c" + // ] + // })).called(1); + // verify(cachedClient?.clearSharedTransactionCache(coin: Coin.dogecoin)) + // .called(1); + // + // expect(secureStore.writes, 7); + // expect(secureStore.reads, 12); + // expect(secureStore.deletes, 4); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // }); // // test("fetchBuildTxData succeeds", () async { // // when(client.getServerFeatures()).thenAnswer((_) async => { - // // "hosts": {}, + // // "hosts": {}, // // "pruning": null, // // "server_version": "Unit tests", // // "protocol_min": "1.4", // // "protocol_max": "1.4.2", // // "genesis_hash": GENESIS_HASH_MAINNET, // // "hash_function": "sha256", - // // "services": [] + // // "services": [] // // }); // // when(client.getBatchHistory(args: historyBatchArgs0)) // // .thenAnswer((_) async => historyBatchResponse); @@ -2474,19 +2297,19 @@ void main() { // // // // verifyNoMoreInteractions(client); // // verifyNoMoreInteractions(cachedClient); - // // verifyNoMoreInteractions(priceAPI); + // // // // }); // test("fetchBuildTxData throws", () async { // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // when(client?.getBatchHistory(args: historyBatchArgs0)) // .thenAnswer((_) async => historyBatchResponse); @@ -2555,19 +2378,19 @@ void main() { // // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // test("build transaction succeeds", () async { // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // when(client?.getBatchHistory(args: historyBatchArgs0)) // .thenAnswer((_) async => historyBatchResponse); @@ -2648,7 +2471,7 @@ void main() { // // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); test("confirmSend error 1", () async { @@ -2661,11 +2484,10 @@ void main() { expect(didThrow, true); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("confirmSend error 2", () async { @@ -2678,11 +2500,10 @@ void main() { expect(didThrow, true); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("confirmSend some other error code", () async { @@ -2695,11 +2516,10 @@ void main() { expect(didThrow, true); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("confirmSend no hex", () async { @@ -2712,11 +2532,10 @@ void main() { expect(didThrow, true); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("confirmSend fails due to vSize being greater than fee", () async { @@ -2734,11 +2553,10 @@ void main() { rawTx: "a string", requestID: anyNamed("requestID"))) .called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("confirmSend fails when broadcast transactions throws", () async { @@ -2760,158 +2578,155 @@ void main() { rawTx: "a string", requestID: anyNamed("requestID"))) .called(1); - expect(secureStore?.interactions, 0); + expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); - test("refresh wallet mutex locked", () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: { - "0": [ - "c82d4ac9697408d423d59dc53267f6474bbd4c22c55fd42ba766e80c6068e7dc" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - when(client?.getBatchHistory(args: { - "0": [ - "80badd62a8dd884cc7f61d962484564929340debb27f88fef270e553306a030c" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - final wallet = await Hive.openBox(testWalletId); - - // recover to fill data - await doge?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - doge?.refreshMutex = true; - - await doge?.refresh(); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - verify(client?.getBatchHistory(args: { - "0": [ - "c82d4ac9697408d423d59dc53267f6474bbd4c22c55fd42ba766e80c6068e7dc" - ] - })).called(1); - verify(client?.getBatchHistory(args: { - "0": [ - "80badd62a8dd884cc7f61d962484564929340debb27f88fef270e553306a030c" - ] - })).called(1); - - expect(secureStore?.interactions, 6); - expect(secureStore?.writes, 3); - expect(secureStore?.reads, 3); - expect(secureStore?.deletes, 0); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); - }); - - test("refresh wallet throws", () async { - when(client?.getBlockHeadTip()).thenThrow(Exception("some exception")); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: { - "0": [ - "c82d4ac9697408d423d59dc53267f6474bbd4c22c55fd42ba766e80c6068e7dc" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - when(client?.getBatchHistory(args: { - "0": [ - "80badd62a8dd884cc7f61d962484564929340debb27f88fef270e553306a030c" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - when(client?.getHistory(scripthash: anyNamed("scripthash"))) - .thenThrow(Exception("some exception")); - - final wallet = await Hive.openBox(testWalletId); - - // recover to fill data - await doge?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - await doge?.refresh(); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - verify(client?.getBatchHistory(args: { - "0": [ - "c82d4ac9697408d423d59dc53267f6474bbd4c22c55fd42ba766e80c6068e7dc" - ] - })).called(1); - verify(client?.getBatchHistory(args: { - "0": [ - "80badd62a8dd884cc7f61d962484564929340debb27f88fef270e553306a030c" - ] - })).called(1); - verify(client?.getBlockHeadTip()).called(1); - verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); - - expect(secureStore?.interactions, 6); - expect(secureStore?.writes, 3); - expect(secureStore?.reads, 3); - expect(secureStore?.deletes, 0); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); - }); - - // test("refresh wallet normally", () async { - // when(client?.getBlockHeadTip()).thenAnswer((realInvocation) async => - // {"height": 520481, "hex": "some block hex"}); + // test("refresh wallet mutex locked", () async { // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: { + // "0": [ + // "c82d4ac9697408d423d59dc53267f6474bbd4c22c55fd42ba766e80c6068e7dc" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "80badd62a8dd884cc7f61d962484564929340debb27f88fef270e553306a030c" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // final wallet = await Hive.openBox(testWalletId); + // + // // recover to fill data + // await doge?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // doge?.refreshMutex = true; + // + // await doge?.refresh(); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "c82d4ac9697408d423d59dc53267f6474bbd4c22c55fd42ba766e80c6068e7dc" + // ] + // })).called(1); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "80badd62a8dd884cc7f61d962484564929340debb27f88fef270e553306a030c" + // ] + // })).called(1); + // + // expect(secureStore.interactions, 6); + // expect(secureStore.writes, 3); + // expect(secureStore.reads, 3); + // expect(secureStore.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // }); + // + // test("refresh wallet throws", () async { + // when(client?.getBlockHeadTip()).thenThrow(Exception("some exception")); + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: { + // "0": [ + // "c82d4ac9697408d423d59dc53267f6474bbd4c22c55fd42ba766e80c6068e7dc" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "80badd62a8dd884cc7f61d962484564929340debb27f88fef270e553306a030c" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // when(client?.getHistory(scripthash: anyNamed("scripthash"))) + // .thenThrow(Exception("some exception")); + // + // await Hive.openBox(testWalletId); + // + // // recover to fill data + // await doge?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // await doge?.refresh(); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "c82d4ac9697408d423d59dc53267f6474bbd4c22c55fd42ba766e80c6068e7dc" + // ] + // })).called(1); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "80badd62a8dd884cc7f61d962484564929340debb27f88fef270e553306a030c" + // ] + // })).called(1); + // verify(client?.getBlockHeadTip()).called(1); + // verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(1); + // + // expect(secureStore.interactions, 6); + // expect(secureStore.writes, 3); + // expect(secureStore.reads, 3); + // expect(secureStore.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // }); + + // test("refresh wallet normally", () async { + // when(client?.getBlockHeadTip()).thenAnswer((realInvocation) async => + // {"height": 520481, "hex": "some block hex"}); + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] // }); // when(client?.getBatchHistory(args: historyBatchArgs0)) // .thenAnswer((_) async => historyBatchResponse); @@ -2958,7 +2773,7 @@ void main() { // // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); }); diff --git a/test/services/coins/dogecoin/dogecoin_wallet_test.mocks.dart b/test/services/coins/dogecoin/dogecoin_wallet_test.mocks.dart index f7220922e..ff04b9f73 100644 --- a/test/services/coins/dogecoin/dogecoin_wallet_test.mocks.dart +++ b/test/services/coins/dogecoin/dogecoin_wallet_test.mocks.dart @@ -3,19 +3,16 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i6; +import 'dart:async' as _i5; import 'package:decimal/decimal.dart' as _i2; -import 'package:http/http.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i7; -import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i5; -import 'package:stackwallet/services/price.dart' as _i9; +import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i6; +import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i4; import 'package:stackwallet/services/transaction_notification_tracker.dart' - as _i11; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i8; + as _i8; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i7; import 'package:stackwallet/utilities/prefs.dart' as _i3; -import 'package:tuple/tuple.dart' as _i10; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -48,26 +45,16 @@ class _FakePrefs_1 extends _i1.SmartFake implements _i3.Prefs { ); } -class _FakeClient_2 extends _i1.SmartFake implements _i4.Client { - _FakeClient_2( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - /// A class which mocks [ElectrumX]. /// /// See the documentation for Mockito's code generation for more information. -class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { +class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { MockElectrumX() { _i1.throwOnMissingStub(this); } @override - set failovers(List<_i5.ElectrumXNode>? _failovers) => super.noSuchMethod( + set failovers(List<_i4.ElectrumXNode>? _failovers) => super.noSuchMethod( Invocation.setter( #failovers, _failovers, @@ -103,7 +90,7 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { returnValue: false, ) as bool); @override - _i6.Future request({ + _i5.Future request({ required String? command, List? args = const [], Duration? connectionTimeout = const Duration(seconds: 60), @@ -122,10 +109,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #retries: retries, }, ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i5.Future.value(), + ) as _i5.Future); @override - _i6.Future>> batchRequest({ + _i5.Future>> batchRequest({ required String? command, required Map>? args, Duration? connectionTimeout = const Duration(seconds: 60), @@ -142,11 +129,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #retries: retries, }, ), - returnValue: _i6.Future>>.value( + returnValue: _i5.Future>>.value( >[]), - ) as _i6.Future>>); + ) as _i5.Future>>); @override - _i6.Future ping({ + _i5.Future ping({ String? requestID, int? retryCount = 1, }) => @@ -159,10 +146,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #retryCount: retryCount, }, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i5.Future.value(false), + ) as _i5.Future); @override - _i6.Future> getBlockHeadTip({String? requestID}) => + _i5.Future> getBlockHeadTip({String? requestID}) => (super.noSuchMethod( Invocation.method( #getBlockHeadTip, @@ -170,10 +157,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future> getServerFeatures({String? requestID}) => + _i5.Future> getServerFeatures({String? requestID}) => (super.noSuchMethod( Invocation.method( #getServerFeatures, @@ -181,10 +168,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future broadcastTransaction({ + _i5.Future broadcastTransaction({ required String? rawTx, String? requestID, }) => @@ -197,10 +184,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #requestID: requestID, }, ), - returnValue: _i6.Future.value(''), - ) as _i6.Future); + returnValue: _i5.Future.value(''), + ) as _i5.Future); @override - _i6.Future> getBalance({ + _i5.Future> getBalance({ required String? scripthash, String? requestID, }) => @@ -214,10 +201,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future>> getHistory({ + _i5.Future>> getHistory({ required String? scripthash, String? requestID, }) => @@ -230,11 +217,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #requestID: requestID, }, ), - returnValue: _i6.Future>>.value( + returnValue: _i5.Future>>.value( >[]), - ) as _i6.Future>>); + ) as _i5.Future>>); @override - _i6.Future>>> getBatchHistory( + _i5.Future>>> getBatchHistory( {required Map>? args}) => (super.noSuchMethod( Invocation.method( @@ -242,11 +229,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { [], {#args: args}, ), - returnValue: _i6.Future>>>.value( + returnValue: _i5.Future>>>.value( >>{}), - ) as _i6.Future>>>); + ) as _i5.Future>>>); @override - _i6.Future>> getUTXOs({ + _i5.Future>> getUTXOs({ required String? scripthash, String? requestID, }) => @@ -259,11 +246,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #requestID: requestID, }, ), - returnValue: _i6.Future>>.value( + returnValue: _i5.Future>>.value( >[]), - ) as _i6.Future>>); + ) as _i5.Future>>); @override - _i6.Future>>> getBatchUTXOs( + _i5.Future>>> getBatchUTXOs( {required Map>? args}) => (super.noSuchMethod( Invocation.method( @@ -271,11 +258,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { [], {#args: args}, ), - returnValue: _i6.Future>>>.value( + returnValue: _i5.Future>>>.value( >>{}), - ) as _i6.Future>>>); + ) as _i5.Future>>>); @override - _i6.Future> getTransaction({ + _i5.Future> getTransaction({ required String? txHash, bool? verbose = true, String? requestID, @@ -291,10 +278,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future> getAnonymitySet({ + _i5.Future> getAnonymitySet({ String? groupId = r'1', String? blockhash = r'', String? requestID, @@ -310,10 +297,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future getMintData({ + _i5.Future getMintData({ dynamic mints, String? requestID, }) => @@ -326,10 +313,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #requestID: requestID, }, ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i5.Future.value(), + ) as _i5.Future); @override - _i6.Future> getUsedCoinSerials({ + _i5.Future> getUsedCoinSerials({ String? requestID, required int? startNumber, }) => @@ -343,19 +330,19 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future getLatestCoinId({String? requestID}) => (super.noSuchMethod( + _i5.Future getLatestCoinId({String? requestID}) => (super.noSuchMethod( Invocation.method( #getLatestCoinId, [], {#requestID: requestID}, ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i5.Future.value(0), + ) as _i5.Future); @override - _i6.Future> getFeeRate({String? requestID}) => + _i5.Future> getFeeRate({String? requestID}) => (super.noSuchMethod( Invocation.method( #getFeeRate, @@ -363,10 +350,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future<_i2.Decimal> estimateFee({ + _i5.Future<_i2.Decimal> estimateFee({ String? requestID, required int? blocks, }) => @@ -379,7 +366,7 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #blocks: blocks, }, ), - returnValue: _i6.Future<_i2.Decimal>.value(_FakeDecimal_0( + returnValue: _i5.Future<_i2.Decimal>.value(_FakeDecimal_0( this, Invocation.method( #estimateFee, @@ -390,15 +377,15 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), )), - ) as _i6.Future<_i2.Decimal>); + ) as _i5.Future<_i2.Decimal>); @override - _i6.Future<_i2.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( + _i5.Future<_i2.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( Invocation.method( #relayFee, [], {#requestID: requestID}, ), - returnValue: _i6.Future<_i2.Decimal>.value(_FakeDecimal_0( + returnValue: _i5.Future<_i2.Decimal>.value(_FakeDecimal_0( this, Invocation.method( #relayFee, @@ -406,13 +393,13 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { {#requestID: requestID}, ), )), - ) as _i6.Future<_i2.Decimal>); + ) as _i5.Future<_i2.Decimal>); } /// A class which mocks [CachedElectrumX]. /// /// See the documentation for Mockito's code generation for more information. -class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { +class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { MockCachedElectrumX() { _i1.throwOnMissingStub(this); } @@ -441,15 +428,15 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { ), ) as _i3.Prefs); @override - List<_i5.ElectrumXNode> get failovers => (super.noSuchMethod( + List<_i4.ElectrumXNode> get failovers => (super.noSuchMethod( Invocation.getter(#failovers), - returnValue: <_i5.ElectrumXNode>[], - ) as List<_i5.ElectrumXNode>); + returnValue: <_i4.ElectrumXNode>[], + ) as List<_i4.ElectrumXNode>); @override - _i6.Future> getAnonymitySet({ + _i5.Future> getAnonymitySet({ required String? groupId, String? blockhash = r'', - required _i8.Coin? coin, + required _i7.Coin? coin, }) => (super.noSuchMethod( Invocation.method( @@ -462,8 +449,8 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override String base64ToHex(String? source) => (super.noSuchMethod( Invocation.method( @@ -481,9 +468,9 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { returnValue: '', ) as String); @override - _i6.Future> getTransaction({ + _i5.Future> getTransaction({ required String? txHash, - required _i8.Coin? coin, + required _i7.Coin? coin, bool? verbose = true, }) => (super.noSuchMethod( @@ -497,11 +484,11 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future> getUsedCoinSerials({ - required _i8.Coin? coin, + _i5.Future> getUsedCoinSerials({ + required _i7.Coin? coin, int? startNumber = 0, }) => (super.noSuchMethod( @@ -513,66 +500,26 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { #startNumber: startNumber, }, ), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i5.Future>.value([]), + ) as _i5.Future>); @override - _i6.Future clearSharedTransactionCache({required _i8.Coin? coin}) => + _i5.Future clearSharedTransactionCache({required _i7.Coin? coin}) => (super.noSuchMethod( Invocation.method( #clearSharedTransactionCache, [], {#coin: coin}, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); -} - -/// A class which mocks [PriceAPI]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockPriceAPI extends _i1.Mock implements _i9.PriceAPI { - MockPriceAPI() { - _i1.throwOnMissingStub(this); - } - - @override - _i4.Client get client => (super.noSuchMethod( - Invocation.getter(#client), - returnValue: _FakeClient_2( - this, - Invocation.getter(#client), - ), - ) as _i4.Client); - @override - void resetLastCalledToForceNextCallToUpdateCache() => super.noSuchMethod( - Invocation.method( - #resetLastCalledToForceNextCallToUpdateCache, - [], - ), - returnValueForMissingStub: null, - ); - @override - _i6.Future< - Map<_i8.Coin, _i10.Tuple2<_i2.Decimal, double>>> getPricesAnd24hChange( - {required String? baseCurrency}) => - (super.noSuchMethod( - Invocation.method( - #getPricesAnd24hChange, - [], - {#baseCurrency: baseCurrency}, - ), - returnValue: - _i6.Future>>.value( - <_i8.Coin, _i10.Tuple2<_i2.Decimal, double>>{}), - ) as _i6.Future>>); + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); } /// A class which mocks [TransactionNotificationTracker]. /// /// See the documentation for Mockito's code generation for more information. class MockTransactionNotificationTracker extends _i1.Mock - implements _i11.TransactionNotificationTracker { + implements _i8.TransactionNotificationTracker { MockTransactionNotificationTracker() { _i1.throwOnMissingStub(this); } @@ -601,14 +548,14 @@ class MockTransactionNotificationTracker extends _i1.Mock returnValue: false, ) as bool); @override - _i6.Future addNotifiedPending(String? txid) => (super.noSuchMethod( + _i5.Future addNotifiedPending(String? txid) => (super.noSuchMethod( Invocation.method( #addNotifiedPending, [txid], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); @override bool wasNotifiedConfirmed(String? txid) => (super.noSuchMethod( Invocation.method( @@ -618,12 +565,21 @@ class MockTransactionNotificationTracker extends _i1.Mock returnValue: false, ) as bool); @override - _i6.Future addNotifiedConfirmed(String? txid) => (super.noSuchMethod( + _i5.Future addNotifiedConfirmed(String? txid) => (super.noSuchMethod( Invocation.method( #addNotifiedConfirmed, [txid], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future deleteTransaction(String? txid) => (super.noSuchMethod( + Invocation.method( + #deleteTransaction, + [txid], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); } diff --git a/test/services/coins/fake_coin_service_api.dart b/test/services/coins/fake_coin_service_api.dart index c5f300c16..ea70545d1 100644 --- a/test/services/coins/fake_coin_service_api.dart +++ b/test/services/coins/fake_coin_service_api.dart @@ -1,19 +1,12 @@ -import 'package:decimal/decimal.dart'; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/utxo.dart'; import 'package:stackwallet/models/paymint/fee_object_model.dart'; -import 'package:stackwallet/models/paymint/transactions_model.dart'; -import 'package:stackwallet/models/paymint/utxo_model.dart'; import 'package:stackwallet/services/coins/coin_service.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; class FakeCoinServiceAPI extends CoinServiceAPI { - @override - // TODO: implement allOwnAddresses - Future> get allOwnAddresses => throw UnimplementedError(); - - @override - // TODO: implement balanceMinusMaxFee - Future get balanceMinusMaxFee => throw UnimplementedError(); - @override // TODO: implement currentReceivingAddress Future get currentReceivingAddress => throw UnimplementedError(); @@ -32,24 +25,12 @@ class FakeCoinServiceAPI extends CoinServiceAPI { // TODO: implement mnemonic Future> get mnemonic => throw UnimplementedError(); - @override - // TODO: implement pendingBalance - Future get pendingBalance => throw UnimplementedError(); - @override Future refresh() { // TODO: implement refresh throw UnimplementedError(); } - @override - // TODO: implement totalBalance - Future get totalBalance => throw UnimplementedError(); - - @override - // TODO: implement transactionData - Future get transactionData => throw UnimplementedError(); - @override bool validateAddress(String address) { // TODO: implement validateAddress @@ -71,10 +52,6 @@ class FakeCoinServiceAPI extends CoinServiceAPI { throw UnimplementedError(); } - @override - // TODO: implement unspentOutputs - Future> get unspentOutputs => throw UnimplementedError(); - @override bool get isFavorite => throw UnimplementedError(); @@ -84,10 +61,6 @@ class FakeCoinServiceAPI extends CoinServiceAPI { @override late bool shouldAutoSync; - @override - // TODO: implement availableBalance - Future get availableBalance => throw UnimplementedError(); - @override // TODO: implement coin Coin get coin => throw UnimplementedError(); @@ -99,7 +72,7 @@ class FakeCoinServiceAPI extends CoinServiceAPI { } @override - Future estimateFeeFor(int satoshiAmount, int feeRate) { + Future estimateFeeFor(Amount amount, int feeRate) { // TODO: implement estimateFeeFor throw UnimplementedError(); } @@ -131,7 +104,7 @@ class FakeCoinServiceAPI extends CoinServiceAPI { @override Future> prepareSend( {required String address, - required int satoshiAmount, + required Amount amount, Map? args}) { // TODO: implement prepareSend throw UnimplementedError(); @@ -153,24 +126,17 @@ class FakeCoinServiceAPI extends CoinServiceAPI { Future get fees => throw UnimplementedError(); @override - Future recoverFromMnemonic( - {required String mnemonic, - required int maxUnusedAddressGap, - required int maxNumberOfIndexesToCheck, - required int height}) { + Future recoverFromMnemonic({ + required String mnemonic, + String? mnemonicPassphrase, + required int maxUnusedAddressGap, + required int maxNumberOfIndexesToCheck, + required int height, + }) { // TODO: implement recoverFromMnemonic throw UnimplementedError(); } - @override - Future send( - {required String toAddress, - required int amount, - Map args = const {}}) { - // TODO: implement send - throw UnimplementedError(); - } - @override Future testNetworkConnection() { // TODO: implement testNetworkConnection @@ -188,4 +154,28 @@ class FakeCoinServiceAPI extends CoinServiceAPI { // TODO: implement updateSentCachedTxData throw UnimplementedError(); } + + @override + // TODO: implement balance + Balance get balance => throw UnimplementedError(); + + @override + // TODO: implement storedChainHeight + int get storedChainHeight => throw UnimplementedError(); + + @override + // TODO: implement transactions + Future> get transactions => throw UnimplementedError(); + + @override + // TODO: implement utxos + Future> get utxos => throw UnimplementedError(); + + @override + // TODO: implement mnemonicPassphrase + Future get mnemonicPassphrase => throw UnimplementedError(); + + @override + // TODO: implement mnemonicString + Future get mnemonicString => throw UnimplementedError(); } diff --git a/test/services/coins/firo/firo_wallet_test.dart b/test/services/coins/firo/firo_wallet_test.dart index 22a505adf..766df279f 100644 --- a/test/services/coins/firo/firo_wallet_test.dart +++ b/test/services/coins/firo/firo_wallet_test.dart @@ -1,8 +1,6 @@ import 'dart:async'; import 'dart:convert'; -import 'dart:typed_data'; -import 'package:crypto/crypto.dart'; import 'package:decimal/decimal.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -10,17 +8,19 @@ import 'package:hive/hive.dart'; import 'package:hive_test/hive_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; +import 'package:stackwallet/db/isar/main_db.dart'; import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; -import 'package:stackwallet/models/models.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/utxo.dart'; +import 'package:stackwallet/models/lelantus_coin.dart'; +import 'package:stackwallet/models/lelantus_fee_data.dart'; +import 'package:stackwallet/models/paymint/transactions_model.dart' as old; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; -import 'package:stackwallet/services/price.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; -import 'package:stackwallet/utilities/address_utils.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; -import 'package:stackwallet/utilities/format.dart'; -import 'package:tuple/tuple.dart'; import 'firo_wallet_test.mocks.dart'; import 'firo_wallet_test_parameters.dart'; @@ -30,21 +30,18 @@ import 'sample_data/get_utxos_sample_data.dart'; import 'sample_data/gethistory_samples.dart'; import 'sample_data/transaction_data_samples.dart'; -@GenerateMocks( - [ElectrumX, CachedElectrumX, PriceAPI, TransactionNotificationTracker]) +@GenerateMocks([ + ElectrumX, + CachedElectrumX, + TransactionNotificationTracker, + MainDB, +]) void main() { group("isolate functions", () { - test("isolateDerive", () async { - final result = - await isolateDerive(IsolateDeriveParams.mnemonic, 0, 2, firoNetwork); - expect(result, isA>()); - expect(result.toString(), IsolateDeriveParams.expected); - }); - test("isolateRestore success", () async { final cachedClient = MockCachedElectrumX(); - final txData = TransactionData.fromJson(dateTimeChunksJson); - final Map setData = {}; + final txDataOLD = old.TransactionData.fromJson(dateTimeChunksJson); + final Map setData = {}; setData[1] = GetAnonymitySetSampleData.data; final usedSerials = GetUsedSerialsSampleData.serials["serials"] as List; @@ -69,13 +66,50 @@ void main() { final message = await isolateRestore( TEST_MNEMONIC, + "", Coin.firo, 1, setData, - usedSerials, + List.from(usedSerials), firoNetwork, ); - final result = await staticProcessRestore(txData, message); + const currentHeight = 100000000000; + + final txData = txDataOLD + .getAllTransactions() + .values + .map( + (t) => Transaction( + walletId: "walletId", + txid: t.txid, + timestamp: t.timestamp, + type: t.txType == "Sent" + ? TransactionType.outgoing + : TransactionType.incoming, + subType: t.subType == "mint" + ? TransactionSubType.mint + : t.subType == "join" + ? TransactionSubType.join + : TransactionSubType.none, + amount: t.amount, + amountString: Amount( + rawValue: BigInt.from(t.amount), + fractionDigits: Coin.firo.decimals, + ).toJsonString(), + fee: t.fees, + height: t.height, + isCancelled: t.isCancelled, + isLelantus: null, + slateId: t.slateId, + otherData: t.otherData, + nonce: null, + inputs: [], + outputs: [], + ), + ) + .toList(); + + final result = await staticProcessRestore(txData, message, currentHeight); expect(result, isA>()); expect(result["mintIndex"], 8); @@ -86,16 +120,17 @@ void main() { }); test("isolateRestore throws", () async { - final Map setData = {}; - final usedSerials = []; + final Map setData = {}; + final usedSerials = []; expect( () => isolateRestore( TEST_MNEMONIC, + "", Coin.firo, 1, setData, - usedSerials, + List.from(usedSerials), firoNetwork, ), throwsA(isA())); @@ -107,14 +142,13 @@ void main() { "aNmsUtzPzQ3SKWNjEH48GacMQJXWN5Rotm", false, TEST_MNEMONIC, + "", 2, - Decimal.ten, [], 459185, Coin.firo, firoNetwork, [GetAnonymitySetSampleData.data], - "en_US", ); expect(result, 1); @@ -190,24 +224,12 @@ void main() { expect(firoTestNetwork.wif, 0xb9); }); - test("getBip32Node", () { - final node = getBip32Node(0, 3, Bip32TestParams.mnemonic, firoNetwork); - expect(node.index, 3); - expect(node.chainCode.toList(), Bip32TestParams.chainCodeList); - expect(node.depth, 5); - expect(node.toBase58(), Bip32TestParams.base58); - expect(node.publicKey.toList(), Bip32TestParams.publicKeyList); - expect(node.privateKey!.toList(), Bip32TestParams.privateKeyList); - expect(node.parentFingerprint, Bip32TestParams.parentFingerprint); - expect(node.fingerprint.toList(), Bip32TestParams.fingerprintList); - }); - // group("getJMintTransactions", () { // test( // "getJMintTransactions throws Error due to some invalid transactions passed to this function", // () { // final cachedClient = MockCachedElectrumX(); - // final priceAPI = MockPriceAPI(); + // // // // mock price calls // when(priceAPI.getPricesAnd24hChange( baseCurrency: "USD")) @@ -268,7 +290,7 @@ void main() { // // test("getJMintTransactions success", () async { // final cachedClient = MockCachedElectrumX(); - // final priceAPI = MockPriceAPI(); + // // // // mock price calls // when(priceAPI.getPricesAnd24hChange( baseCurrency: "USD")) @@ -371,7 +393,6 @@ void main() { client: MockElectrumX(), cachedClient: MockCachedElectrumX(), secureStore: FakeSecureStorage(), - priceAPI: MockPriceAPI(), tracker: MockTransactionNotificationTracker(), ); @@ -386,7 +407,6 @@ void main() { client: MockElectrumX(), cachedClient: MockCachedElectrumX(), secureStore: FakeSecureStorage(), - priceAPI: MockPriceAPI(), tracker: MockTransactionNotificationTracker(), ); @@ -401,7 +421,6 @@ void main() { client: MockElectrumX(), cachedClient: MockCachedElectrumX(), secureStore: FakeSecureStorage(), - priceAPI: MockPriceAPI(), tracker: MockTransactionNotificationTracker(), ); @@ -416,7 +435,6 @@ void main() { client: MockElectrumX(), cachedClient: MockCachedElectrumX(), secureStore: FakeSecureStorage(), - priceAPI: MockPriceAPI(), tracker: MockTransactionNotificationTracker(), ); @@ -431,7 +449,6 @@ void main() { client: MockElectrumX(), cachedClient: MockCachedElectrumX(), secureStore: FakeSecureStorage(), - priceAPI: MockPriceAPI(), tracker: MockTransactionNotificationTracker(), ); @@ -446,7 +463,6 @@ void main() { client: MockElectrumX(), cachedClient: MockCachedElectrumX(), secureStore: FakeSecureStorage(), - priceAPI: MockPriceAPI(), tracker: MockTransactionNotificationTracker(), ); @@ -466,7 +482,6 @@ void main() { client: client, cachedClient: MockCachedElectrumX(), secureStore: FakeSecureStorage(), - priceAPI: MockPriceAPI(), tracker: MockTransactionNotificationTracker(), ); final bool result = await firo.testNetworkConnection(); @@ -485,7 +500,6 @@ void main() { client: client, cachedClient: MockCachedElectrumX(), secureStore: FakeSecureStorage(), - priceAPI: MockPriceAPI(), tracker: MockTransactionNotificationTracker(), ); final bool result = await firo.testNetworkConnection(); @@ -504,7 +518,6 @@ void main() { client: client, cachedClient: MockCachedElectrumX(), secureStore: FakeSecureStorage(), - priceAPI: MockPriceAPI(), tracker: MockTransactionNotificationTracker(), ); final bool result = await firo.testNetworkConnection(); @@ -524,23 +537,11 @@ void main() { if (!hiveAdaptersRegistered) { hiveAdaptersRegistered = true; - // Registering Transaction Model Adapters - Hive.registerAdapter(TransactionDataAdapter()); - Hive.registerAdapter(TransactionChunkAdapter()); - Hive.registerAdapter(TransactionAdapter()); - Hive.registerAdapter(InputAdapter()); - Hive.registerAdapter(OutputAdapter()); - - // Registering Utxo Model Adapters - Hive.registerAdapter(UtxoDataAdapter()); - Hive.registerAdapter(UtxoObjectAdapter()); - Hive.registerAdapter(StatusAdapter()); - // Registering Lelantus Model Adapters Hive.registerAdapter(LelantusCoinAdapter()); } - final wallets = await Hive.openBox('wallets'); + final wallets = await Hive.openBox('wallets'); await wallets.put('currentWalletName', testWalletName); }); @@ -548,7 +549,7 @@ void main() { // final client = MockElectrumX(); // final cachedClient = MockCachedElectrumX(); // final secureStore = FakeSecureStorage(); - // final priceAPI = MockPriceAPI(); + // // // when(client.getServerFeatures()).thenAnswer((_) async => false); // @@ -558,7 +559,8 @@ void main() { // client: client,coin: Coin.firo, // cachedClient: cachedClient, // secureStore: secureStore, - // priceAPI: priceAPI, + // + // // tracker: MockTransactionNotificationTracker(), // ); // @@ -569,7 +571,7 @@ void main() { // final client = MockElectrumX(); // final cachedClient = MockCachedElectrumX(); // final secureStore = FakeSecureStorage(); - // final priceAPI = MockPriceAPI(); + // // // when(client.ping()).thenThrow(Exception("Network connection failed")); // @@ -580,7 +582,8 @@ void main() { // coin: Coin.firo, // cachedClient: cachedClient, // secureStore: secureStore, - // priceAPI: priceAPI, + // + // // tracker: MockTransactionNotificationTracker(), // ); // @@ -591,19 +594,19 @@ void main() { // final client = MockElectrumX(); // final cachedClient = MockCachedElectrumX(); // final secureStore = FakeSecureStorage(); - // final priceAPI = MockPriceAPI(); + // // // when(client.ping()).thenAnswer((_) async => true); // // when(client.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // // final firo = FiroWallet( @@ -613,7 +616,8 @@ void main() { // client: client, // cachedClient: cachedClient, // secureStore: secureStore, - // priceAPI: priceAPI, + // + // // tracker: MockTransactionNotificationTracker(), // ); // @@ -624,19 +628,19 @@ void main() { // final client = MockElectrumX(); // final cachedClient = MockCachedElectrumX(); // final secureStore = FakeSecureStorage(); - // final priceAPI = MockPriceAPI(); + // // // when(client.ping()).thenAnswer((_) async => true); // // when(client.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_TESTNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // // final firo = FiroWallet( @@ -645,7 +649,8 @@ void main() { // client: client, // cachedClient: cachedClient, // secureStore: secureStore, - // priceAPI: priceAPI, + // + // // tracker: MockTransactionNotificationTracker(), // ); // @@ -660,21 +665,21 @@ void main() { // final client = MockElectrumX(); // final cachedClient = MockCachedElectrumX(); // final secureStore = FakeSecureStorage(); - // final priceAPI = MockPriceAPI(); + // // when(priceAPI.getPrice(ticker: "tFIRO", baseCurrency: "USD")) // .thenAnswer((_) async => Decimal.fromInt(-1)); // // when(client.ping()).thenAnswer((_) async => true); // // when(client.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_TESTNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // // final List> emptyList = []; @@ -691,7 +696,8 @@ void main() { // client: client, // cachedClient: cachedClient, // secureStore: secureStore, - // priceAPI: priceAPI, + // + // // tracker: MockTransactionNotificationTracker(), // ); // @@ -731,21 +737,21 @@ void main() { // final client = MockElectrumX(); // final cachedClient = MockCachedElectrumX(); // final secureStore = FakeSecureStorage(); - // final priceAPI = MockPriceAPI(); + // // // when(priceAPI.getPrice(ticker: "tFIRO", baseCurrency: "USD")) // // .thenAnswer((_) async => Decimal.fromInt(-1)); // // when(client.ping()).thenAnswer((_) async => true); // // when(client.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_TESTNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // // final List> emptyList = []; @@ -762,7 +768,8 @@ void main() { // client: client, // cachedClient: cachedClient, // secureStore: secureStore, - // priceAPI: priceAPI, + // + // // tracker: MockTransactionNotificationTracker(), // ); // @@ -836,7 +843,7 @@ void main() { // final client = MockElectrumX(); // final cachedClient = MockCachedElectrumX(); // final secureStore = FakeSecureStorage(); - // final priceAPI = MockPriceAPI(); + // // // mock price calls // when(priceAPI.getPricesAnd24hChange(baseCurrency: "USD")).thenAnswer( // (_) async => {Coin.firo: Tuple2(Decimal.fromInt(10), 1.0)}); @@ -844,14 +851,14 @@ void main() { // when(client.ping()).thenAnswer((_) async => true); // // when(client.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // // final List> emptyList = []; @@ -868,7 +875,8 @@ void main() { // client: client, // cachedClient: cachedClient, // secureStore: secureStore, - // priceAPI: priceAPI, + // + // // tracker: MockTransactionNotificationTracker(), // ); // @@ -913,9 +921,9 @@ void main() { // final client = MockElectrumX(); // final cachedClient = MockCachedElectrumX(); // final secureStore = FakeSecureStorage(); - // final priceAPI = MockPriceAPI(); - // final tracker = MockTransactionNotificationTracker(); // + // final tracker = MockTransactionNotificationTracker(); + // // // await Hive.openBox(DB.boxNamePrefs); // await Prefs.instance.init(); // @@ -954,7 +962,8 @@ void main() { // client: client, // cachedClient: cachedClient, // secureStore: secureStore, - // priceAPI: priceAPI, + // + // // tracker: tracker, // ); // @@ -970,108 +979,102 @@ void main() { // }); group("refreshIfThereIsNewData", () { - test("refreshIfThereIsNewData with no unconfirmed transactions", - () async { - TestWidgetsFlutterBinding.ensureInitialized(); - const MethodChannel('uk.spiralarm.flutter/devicelocale') - .setMockMethodCallHandler((methodCall) async => 'en_US'); - - final client = MockElectrumX(); - final cachedClient = MockCachedElectrumX(); - final secureStore = FakeSecureStorage(); - final priceAPI = MockPriceAPI(); - final tracker = MockTransactionNotificationTracker(); - - // mock price calls - when(priceAPI.getPricesAnd24hChange(baseCurrency: "USD")).thenAnswer( - (_) async => {Coin.firo: Tuple2(Decimal.fromInt(10), 1.0)}); - - when(tracker.pendings).thenAnswer((realInvocation) => [ - "51576e2230c2911a508aabb85bb50045f04b8dc958790ce2372986c3ebbe7d3e", - "FF7e74edecd8c14ff5a8ddeb54e9e5e9c7c301c6f76f0ac1ac8119c6cc15e35", - "f4217364cbe6a81ef7ecaaeba0a6d6b576a9850b3e891fa7b88ed4927c505218" - ]); - when(tracker.wasNotifiedPending( - "51576e2230c2911a508aabb85bb50045f04b8dc958790ce2372986c3ebbe7d3e")) - .thenAnswer((realInvocation) => true); - when(tracker.wasNotifiedPending( - "FF7e74edecd8c14ff5a8ddeb54e9e5e9c7c301c6f76f0ac1ac8119c6cc15e35")) - .thenAnswer((realInvocation) => true); - when(tracker.wasNotifiedPending( - "f4217364cbe6a81ef7ecaaeba0a6d6b576a9850b3e891fa7b88ed4927c505218")) - .thenAnswer((realInvocation) => true); - - when(tracker.wasNotifiedConfirmed( - "51576e2230c2911a508aabb85bb50045f04b8dc958790ce2372986c3ebbe7d3e")) - .thenAnswer((realInvocation) => true); - when(tracker.wasNotifiedConfirmed( - "FF7e74edecd8c14ff5a8ddeb54e9e5e9c7c301c6f76f0ac1ac8119c6cc15e35")) - .thenAnswer((realInvocation) => true); - when(tracker.wasNotifiedConfirmed( - "f4217364cbe6a81ef7ecaaeba0a6d6b576a9850b3e891fa7b88ed4927c505218")) - .thenAnswer((realInvocation) => true); - - when(client.getBatchHistory(args: batchHistoryRequest0)) - .thenAnswer((realInvocation) async => batchHistoryResponse0); - - // mock transaction calls - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash0, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData0); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash1, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData1); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash2, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData2); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash3, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData3); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash4, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData4); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash5, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData5); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash6, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData6); - - final firo = FiroWallet( - walletName: testWalletName, - walletId: "${testWalletId}refreshIfThereIsNewData", - coin: Coin.firo, - client: client, - cachedClient: cachedClient, - secureStore: secureStore, - priceAPI: priceAPI, - tracker: tracker, - ); - - // firo.unconfirmedTxs = {}; - - final wallet = - await Hive.openBox(testWalletId + "refreshIfThereIsNewData"); - await wallet.put('receivingAddresses', [ - "a8VV7vMzJdTQj1eLEJNskhLEBUxfNWhpAg", - "aPjLWDTPQsoPHUTxKBNRzoebDALj3eTcfh", - "aKmXfS7nEZdqWBGRdAXcyMoEoKhZQDPBoq", - ]); - - await wallet.put('changeAddresses', [ - "a5V5r6We6mNZzWJwGwEeRML3mEYLjvK39w", - ]); - - final result = await firo.refreshIfThereIsNewData(); - expect(result, false); - }); + // test("refreshIfThereIsNewData with no unconfirmed transactions", + // () async { + // TestWidgetsFlutterBinding.ensureInitialized(); + // const MethodChannel('uk.spiralarm.flutter/devicelocale') + // .setMockMethodCallHandler((methodCall) async => 'en_US'); + // + // final client = MockElectrumX(); + // final cachedClient = MockCachedElectrumX(); + // final secureStore = FakeSecureStorage(); + // final tracker = MockTransactionNotificationTracker(); + // + // when(tracker.pendings).thenAnswer((realInvocation) => [ + // "51576e2230c2911a508aabb85bb50045f04b8dc958790ce2372986c3ebbe7d3e", + // "FF7e74edecd8c14ff5a8ddeb54e9e5e9c7c301c6f76f0ac1ac8119c6cc15e35", + // "f4217364cbe6a81ef7ecaaeba0a6d6b576a9850b3e891fa7b88ed4927c505218" + // ]); + // when(tracker.wasNotifiedPending( + // "51576e2230c2911a508aabb85bb50045f04b8dc958790ce2372986c3ebbe7d3e")) + // .thenAnswer((realInvocation) => true); + // when(tracker.wasNotifiedPending( + // "FF7e74edecd8c14ff5a8ddeb54e9e5e9c7c301c6f76f0ac1ac8119c6cc15e35")) + // .thenAnswer((realInvocation) => true); + // when(tracker.wasNotifiedPending( + // "f4217364cbe6a81ef7ecaaeba0a6d6b576a9850b3e891fa7b88ed4927c505218")) + // .thenAnswer((realInvocation) => true); + // + // when(tracker.wasNotifiedConfirmed( + // "51576e2230c2911a508aabb85bb50045f04b8dc958790ce2372986c3ebbe7d3e")) + // .thenAnswer((realInvocation) => true); + // when(tracker.wasNotifiedConfirmed( + // "FF7e74edecd8c14ff5a8ddeb54e9e5e9c7c301c6f76f0ac1ac8119c6cc15e35")) + // .thenAnswer((realInvocation) => true); + // when(tracker.wasNotifiedConfirmed( + // "f4217364cbe6a81ef7ecaaeba0a6d6b576a9850b3e891fa7b88ed4927c505218")) + // .thenAnswer((realInvocation) => true); + // + // when(client.getBatchHistory(args: batchHistoryRequest0)) + // .thenAnswer((realInvocation) async => batchHistoryResponse0); + // + // // mock transaction calls + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash0, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData0); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash1, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData1); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash2, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData2); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash3, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData3); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash4, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData4); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash5, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData5); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash6, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData6); + // + // final firo = FiroWallet( + // walletName: testWalletName, + // walletId: "${testWalletId}refreshIfThereIsNewData", + // coin: Coin.firo, + // client: client, + // cachedClient: cachedClient, + // secureStore: secureStore, + // tracker: tracker, + // ); + // + // // firo.unconfirmedTxs = {}; + // + // final wallet = await Hive.openBox( + // "${testWalletId}refreshIfThereIsNewData"); + // await wallet.put('receivingAddresses', [ + // "a8VV7vMzJdTQj1eLEJNskhLEBUxfNWhpAg", + // "aPjLWDTPQsoPHUTxKBNRzoebDALj3eTcfh", + // "aKmXfS7nEZdqWBGRdAXcyMoEoKhZQDPBoq", + // ]); + // + // await wallet.put('changeAddresses', [ + // "a5V5r6We6mNZzWJwGwEeRML3mEYLjvK39w", + // ]); + // + // final result = await firo.refreshIfThereIsNewData(); + // expect(result, false); + // }); // TODO: mock NotificationAPI // test("refreshIfThereIsNewData with two unconfirmed transactions", @@ -1079,9 +1082,9 @@ void main() { // final client = MockElectrumX(); // final cachedClient = MockCachedElectrumX(); // final secureStore = FakeSecureStorage(); - // final priceAPI = MockPriceAPI(); - // final tracker = MockTransactionNotificationTracker(); // + // final tracker = MockTransactionNotificationTracker(); + // // // when(client.getTransaction(txHash: SampleGetTransactionData.txHash6)) // .thenAnswer((_) async => SampleGetTransactionData.txData6); // @@ -1119,7 +1122,8 @@ void main() { // client: client, // cachedClient: cachedClient, // secureStore: secureStore, - // priceAPI: priceAPI, + // + // // tracker: tracker, // ); // @@ -1135,7 +1139,6 @@ void main() { final client = MockElectrumX(); final cachedClient = MockCachedElectrumX(); final secureStore = FakeSecureStorage(); - final priceAPI = MockPriceAPI(); when(client.broadcastTransaction( rawTx: @@ -1150,7 +1153,6 @@ void main() { client: client, cachedClient: cachedClient, secureStore: secureStore, - priceAPI: priceAPI, tracker: MockTransactionNotificationTracker(), ); @@ -1161,72 +1163,39 @@ void main() { "b36161c6e619395b3d40a851c45c1fef7a5c541eed911b5524a66c5703a689c9"); }); - test("fillAddresses", () async { - final client = MockElectrumX(); - final cachedClient = MockCachedElectrumX(); - final secureStore = FakeSecureStorage(); - final priceAPI = MockPriceAPI(); - - final firo = FiroWallet( - walletName: testWalletName, - walletId: "${testWalletId}fillAddresses", - coin: Coin.firo, - client: client, - cachedClient: cachedClient, - secureStore: secureStore, - priceAPI: priceAPI, - tracker: MockTransactionNotificationTracker(), - ); - - await firo.fillAddresses(FillAddressesParams.mnemonic); - - final rcv = await secureStore.read( - key: "${testWalletId}fillAddresses_receiveDerivations"); - final chg = await secureStore.read( - key: "${testWalletId}fillAddresses_changeDerivations"); - final receiveDerivations = - Map.from(jsonDecode(rcv as String) as Map); - final changeDerivations = - Map.from(jsonDecode(chg as String) as Map); - - expect(receiveDerivations.toString(), - FillAddressesParams.expectedReceiveDerivationsString); - - expect(changeDerivations.toString(), - FillAddressesParams.expectedChangeDerivationsString); - }); - // the above test needs to pass in order for this test to pass test("buildMintTransaction", () async { TestWidgetsFlutterBinding.ensureInitialized(); const MethodChannel('uk.spiralarm.flutter/devicelocale') .setMockMethodCallHandler((methodCall) async => 'en_US'); - List utxos = [ - UtxoObject( + List utxos = [ + UTXO( txid: BuildMintTxTestParams.utxoInfo["txid"] as String, vout: BuildMintTxTestParams.utxoInfo["vout"] as int, value: BuildMintTxTestParams.utxoInfo["value"] as int, - txName: '', - status: Status( - confirmed: false, - blockHash: "", - blockHeight: -1, - blockTime: 42, - confirmations: 0), isCoinbase: false, - blocked: false, - fiatWorth: '', + walletId: '', + name: '', + isBlocked: false, + blockedReason: '', + blockHash: '', + blockHeight: -1, + blockTime: 42, ) ]; const sats = 9658; final client = MockElectrumX(); final cachedClient = MockCachedElectrumX(); final secureStore = FakeSecureStorage(); + final mainDB = MockMainDB(); await secureStore.write( key: "${testWalletId}buildMintTransaction_mnemonic", value: BuildMintTxTestParams.mnemonic); + await secureStore.write( + key: "${testWalletId}buildMintTransaction_mnemonicPassphrase", + value: ""); when(cachedClient.getTransaction( txHash: BuildMintTxTestParams.utxoInfo["txid"] as String, @@ -1236,10 +1205,8 @@ void main() { when(client.getBlockHeadTip()).thenAnswer( (_) async => {"height": 455873, "hex": "this value not used here"}); - final priceAPI = MockPriceAPI(); - // mock price calls - when(priceAPI.getPricesAnd24hChange(baseCurrency: "USD")).thenAnswer( - (_) async => {Coin.firo: Tuple2(Decimal.fromInt(10), 1.0)}); + when(mainDB.getAddress("${testWalletId}buildMintTransaction", any)) + .thenAnswer((realInvocation) async => null); final firo = FiroWallet( walletName: testWalletName, @@ -1248,11 +1215,12 @@ void main() { client: client, cachedClient: cachedClient, secureStore: secureStore, - priceAPI: priceAPI, tracker: MockTransactionNotificationTracker(), + mockableOverride: mainDB, ); - final wallet = await Hive.openBox("${testWalletId}buildMintTransaction"); + final wallet = + await Hive.openBox("${testWalletId}buildMintTransaction"); await wallet.put("mintIndex", 0); @@ -1272,904 +1240,880 @@ void main() { expect(result["txHex"], isA()); }); - test("recoverFromMnemonic succeeds", () async { - TestWidgetsFlutterBinding.ensureInitialized(); - const MethodChannel('uk.spiralarm.flutter/devicelocale') - .setMockMethodCallHandler((methodCall) async => 'en_US'); - - final client = MockElectrumX(); - final cachedClient = MockCachedElectrumX(); - final secureStore = FakeSecureStorage(); - final priceAPI = MockPriceAPI(); - - // mock electrumx client calls - when(client.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - - when(client.getLatestCoinId()).thenAnswer((_) async => 1); - when(cachedClient.getUsedCoinSerials( - coin: Coin.firo, - )).thenAnswer( - (_) async => GetUsedSerialsSampleData.serials["serials"] as List); - - when(cachedClient.getAnonymitySet( - groupId: "1", - coin: Coin.firo, - )).thenAnswer( - (_) async => GetAnonymitySetSampleData.data, - ); - - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash0, - coin: Coin.firo, - )).thenAnswer((_) async { - return SampleGetTransactionData.txData0; - }); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash1, - coin: Coin.firo, - )).thenAnswer((_) async { - return SampleGetTransactionData.txData1; - }); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash2, - coin: Coin.firo, - )).thenAnswer((_) async { - return SampleGetTransactionData.txData2; - }); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash3, - coin: Coin.firo, - )).thenAnswer((_) async { - return SampleGetTransactionData.txData3; - }); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash4, - coin: Coin.firo, - )).thenAnswer((_) async { - return SampleGetTransactionData.txData4; - }); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash5, - coin: Coin.firo, - )).thenAnswer((_) async { - return SampleGetTransactionData.txData5; - }); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash6, - coin: Coin.firo, - )).thenAnswer((_) async { - return SampleGetTransactionData.txData6; - }); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash8, - coin: Coin.firo, - )).thenAnswer((_) async { - return SampleGetTransactionData.txData8; - }); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash9, - coin: Coin.firo, - )).thenAnswer((_) async { - return SampleGetTransactionData.txData9; - }); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash7, - coin: Coin.firo, - )).thenAnswer((_) async { - return SampleGetTransactionData.txData7; - }); - - // mock price calls - when(priceAPI.getPricesAnd24hChange(baseCurrency: "USD")).thenAnswer( - (_) async => {Coin.firo: Tuple2(Decimal.fromInt(10), 1.0)}); - - final firo = FiroWallet( - walletName: testWalletName, - walletId: "${testWalletId}recoverFromMnemonic", - coin: Coin.firo, - client: client, - cachedClient: cachedClient, - secureStore: secureStore, - priceAPI: priceAPI, - tracker: MockTransactionNotificationTracker(), - ); - - // pre grab derivations in order to set up mock calls needed later on - await firo.fillAddresses(TEST_MNEMONIC); - final wallet = - await Hive.openBox("${testWalletId}recoverFromMnemonic"); - - final rcv = await secureStore.read( - key: "${testWalletId}recoverFromMnemonic_receiveDerivations"); - final chg = await secureStore.read( - key: "${testWalletId}recoverFromMnemonic_changeDerivations"); - final receiveDerivations = - Map.from(jsonDecode(rcv as String) as Map); - final changeDerivations = - Map.from(jsonDecode(chg as String) as Map); - - for (int i = 0; i < receiveDerivations.length; i++) { - final receiveHash = AddressUtils.convertToScriptHash( - receiveDerivations["$i"]!["address"] as String, firoNetwork); - final changeHash = AddressUtils.convertToScriptHash( - changeDerivations["$i"]!["address"] as String, firoNetwork); - List> data; - switch (receiveHash) { - case SampleGetHistoryData.scripthash0: - data = SampleGetHistoryData.data0; - break; - case SampleGetHistoryData.scripthash1: - data = SampleGetHistoryData.data1; - break; - case SampleGetHistoryData.scripthash2: - data = SampleGetHistoryData.data2; - break; - case SampleGetHistoryData.scripthash3: - data = SampleGetHistoryData.data3; - break; - default: - data = []; - } - when(client.getHistory(scripthash: receiveHash)) - .thenAnswer((_) async => data); - - switch (changeHash) { - case SampleGetHistoryData.scripthash0: - data = SampleGetHistoryData.data0; - break; - case SampleGetHistoryData.scripthash1: - data = SampleGetHistoryData.data1; - break; - case SampleGetHistoryData.scripthash2: - data = SampleGetHistoryData.data2; - break; - case SampleGetHistoryData.scripthash3: - data = SampleGetHistoryData.data3; - break; - default: - data = []; - } - - when(client.getHistory(scripthash: changeHash)) - .thenAnswer((_) async => data); - } - - when(client.getBatchHistory(args: { - "0": [SampleGetHistoryData.scripthash0], - "1": [SampleGetHistoryData.scripthash3] - })).thenAnswer((realInvocation) async => { - "0": SampleGetHistoryData.data0, - "1": SampleGetHistoryData.data3, - }); - - await firo.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 20, - height: 0, - maxNumberOfIndexesToCheck: 1000); - - final receivingAddresses = await wallet.get('receivingAddresses'); - expect(receivingAddresses, ["a8VV7vMzJdTQj1eLEJNskhLEBUxfNWhpAg"]); - - final changeAddresses = await wallet.get('changeAddresses'); - expect(changeAddresses, ["a5V5r6We6mNZzWJwGwEeRML3mEYLjvK39w"]); - - final receivingIndex = await wallet.get('receivingIndex'); - expect(receivingIndex, 0); - - final changeIndex = await wallet.get('changeIndex'); - expect(changeIndex, 0); - - final _rcv = await secureStore.read( - key: "${testWalletId}recoverFromMnemonic_receiveDerivations"); - final _chg = await secureStore.read( - key: "${testWalletId}recoverFromMnemonic_changeDerivations"); - final _receiveDerivations = - Map.from(jsonDecode(_rcv as String) as Map); - final _changeDerivations = - Map.from(jsonDecode(_chg as String) as Map); - // expect(_receiveDerivations.length, 190); - // expect(_changeDerivations.length, 190); - expect(_receiveDerivations.length, 80); - expect(_changeDerivations.length, 80); - - final mintIndex = await wallet.get('mintIndex'); - expect(mintIndex, 8); - - final lelantusCoins = await wallet.get('_lelantus_coins') as List; - expect(lelantusCoins.length, 7); - final lcoin = lelantusCoins - .firstWhere((element) => - (Map.from(element as Map)) - .values - .first - .txId == - "36c92daa4005d368e28cea917fdb2c1e7069319a4a79fb2ff45c089100680232") - .values - .first as LelantusCoin; - expect(lcoin.index, 1); - expect(lcoin.value, 9658); - expect(lcoin.publicCoin, - "7fd927efbea0a9e4ba299209aaee610c63359857596be0a2da276011a0baa84a0000"); - expect(lcoin.txId, - "36c92daa4005d368e28cea917fdb2c1e7069319a4a79fb2ff45c089100680232"); - expect(lcoin.anonymitySetId, 1); - expect(lcoin.isUsed, true); - - final jIndex = await wallet.get('jindex'); - expect(jIndex, [2, 4, 6]); - - final lelantusTxModel = await wallet.get('latest_lelantus_tx_model'); - expect(lelantusTxModel.getAllTransactions().length, 5); - }, timeout: const Timeout(Duration(minutes: 5))); - - test("fullRescan succeeds", () async { - TestWidgetsFlutterBinding.ensureInitialized(); - const MethodChannel('uk.spiralarm.flutter/devicelocale') - .setMockMethodCallHandler((methodCall) async => 'en_US'); - - final client = MockElectrumX(); - final cachedClient = MockCachedElectrumX(); - final secureStore = FakeSecureStorage(); - final priceAPI = MockPriceAPI(); - - await secureStore.write( - key: '${testWalletId}fullRescan_mnemonic', value: TEST_MNEMONIC); - - // mock electrumx client calls - when(client.getLatestCoinId()).thenAnswer((_) async => 1); - // when(client.getCoinsForRecovery(setId: 1)) - // .thenAnswer((_) async => getCoinsForRecoveryResponse); - when(client.getUsedCoinSerials(startNumber: 0)) - .thenAnswer((_) async => GetUsedSerialsSampleData.serials); - - when(cachedClient.getAnonymitySet( - groupId: "1", blockhash: "", coin: Coin.firo)) - .thenAnswer((_) async => GetAnonymitySetSampleData.data); - when(cachedClient.getUsedCoinSerials(startNumber: 0, coin: Coin.firo)) - .thenAnswer( - (_) async => GetUsedSerialsSampleData.serials['serials'] as List); - - when(cachedClient.clearSharedTransactionCache(coin: Coin.firo)) - .thenAnswer((_) async => {}); - - // mock price calls - when(priceAPI.getPricesAnd24hChange(baseCurrency: "USD")).thenAnswer( - (_) async => {Coin.firo: Tuple2(Decimal.fromInt(10), 1.0)}); - - final firo = FiroWallet( - walletName: testWalletName, - walletId: "${testWalletId}fullRescan", - coin: Coin.firo, - client: client, - cachedClient: cachedClient, - secureStore: secureStore, - priceAPI: priceAPI, - tracker: MockTransactionNotificationTracker(), - ); - - // pre grab derivations in order to set up mock calls needed later on - await firo.fillAddresses(TEST_MNEMONIC); - final wallet = await Hive.openBox(testWalletId + "fullRescan"); - - final rcv = await secureStore.read( - key: "${testWalletId}fullRescan_receiveDerivations"); - final chg = await secureStore.read( - key: "${testWalletId}fullRescan_changeDerivations"); - final receiveDerivations = - Map.from(jsonDecode(rcv as String) as Map); - final changeDerivations = - Map.from(jsonDecode(chg as String) as Map); - - for (int i = 0; i < receiveDerivations.length; i++) { - final receiveHash = AddressUtils.convertToScriptHash( - receiveDerivations["$i"]!["address"] as String, firoNetwork); - final changeHash = AddressUtils.convertToScriptHash( - changeDerivations["$i"]!["address"] as String, firoNetwork); - List> data; - switch (receiveHash) { - case SampleGetHistoryData.scripthash0: - data = SampleGetHistoryData.data0; - break; - case SampleGetHistoryData.scripthash1: - data = SampleGetHistoryData.data1; - break; - case SampleGetHistoryData.scripthash2: - data = SampleGetHistoryData.data2; - break; - case SampleGetHistoryData.scripthash3: - data = SampleGetHistoryData.data3; - break; - default: - data = []; - } - when(client.getHistory(scripthash: receiveHash)) - .thenAnswer((_) async => data); - - switch (changeHash) { - case SampleGetHistoryData.scripthash0: - data = SampleGetHistoryData.data0; - break; - case SampleGetHistoryData.scripthash1: - data = SampleGetHistoryData.data1; - break; - case SampleGetHistoryData.scripthash2: - data = SampleGetHistoryData.data2; - break; - case SampleGetHistoryData.scripthash3: - data = SampleGetHistoryData.data3; - break; - default: - data = []; - } - - when(client.getHistory(scripthash: changeHash)) - .thenAnswer((_) async => data); - } - - when(client.getBatchHistory(args: { - "0": [SampleGetHistoryData.scripthash0], - "1": [SampleGetHistoryData.scripthash3] - })).thenAnswer((realInvocation) async => { - "0": SampleGetHistoryData.data0, - "1": SampleGetHistoryData.data3, - }); - - // mock transaction calls - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash0, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData0); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash1, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData1); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash2, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData2); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash3, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData3); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash4, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData4); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash5, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData5); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash6, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData6); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash7, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData7); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash8, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData8); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash9, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData9); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash10, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData10); - - await firo.fullRescan(20, 1000); - - final receivingAddresses = await wallet.get('receivingAddresses'); - expect(receivingAddresses, ["a8VV7vMzJdTQj1eLEJNskhLEBUxfNWhpAg"]); - - final changeAddresses = await wallet.get('changeAddresses'); - expect(changeAddresses, ["a5V5r6We6mNZzWJwGwEeRML3mEYLjvK39w"]); - - final receivingIndex = await wallet.get('receivingIndex'); - expect(receivingIndex, 0); - - final changeIndex = await wallet.get('changeIndex'); - expect(changeIndex, 0); - - final _rcv = await secureStore.read( - key: "${testWalletId}fullRescan_receiveDerivations"); - final _chg = await secureStore.read( - key: "${testWalletId}fullRescan_changeDerivations"); - final _receiveDerivations = - Map.from(jsonDecode(_rcv as String) as Map); - final _changeDerivations = - Map.from(jsonDecode(_chg as String) as Map); - // expect(_receiveDerivations.length, 150); - // expect(_changeDerivations.length, 150); - expect(_receiveDerivations.length, 40); - expect(_changeDerivations.length, 40); - - final mintIndex = await wallet.get('mintIndex'); - expect(mintIndex, 8); - - final lelantusCoins = await wallet.get('_lelantus_coins') as List; - expect(lelantusCoins.length, 7); - final lcoin = lelantusCoins - .firstWhere((element) => - (Map.from(element as Map)) - .values - .first - .txId == - "36c92daa4005d368e28cea917fdb2c1e7069319a4a79fb2ff45c089100680232") - .values - .first as LelantusCoin; - expect(lcoin.index, 1); - expect(lcoin.value, 9658); - expect(lcoin.publicCoin, - "7fd927efbea0a9e4ba299209aaee610c63359857596be0a2da276011a0baa84a0000"); - expect(lcoin.txId, - "36c92daa4005d368e28cea917fdb2c1e7069319a4a79fb2ff45c089100680232"); - expect(lcoin.anonymitySetId, 1); - expect(lcoin.isUsed, true); - - final jIndex = await wallet.get('jindex'); - expect(jIndex, [2, 4, 6]); - - final lelantusTxModel = await wallet.get('latest_lelantus_tx_model'); - expect(lelantusTxModel.getAllTransactions().length, 5); - }, timeout: const Timeout(Duration(minutes: 3))); - - test("fullRescan fails", () async { - TestWidgetsFlutterBinding.ensureInitialized(); - const MethodChannel('uk.spiralarm.flutter/devicelocale') - .setMockMethodCallHandler((methodCall) async => 'en_US'); - - final client = MockElectrumX(); - final cachedClient = MockCachedElectrumX(); - final secureStore = FakeSecureStorage(); - final priceAPI = MockPriceAPI(); - - await secureStore.write( - key: '${testWalletId}fullRescan_mnemonic', value: TEST_MNEMONIC); - - // mock electrumx client calls - when(client.getLatestCoinId()).thenAnswer((_) async => 1); - // when(client.getCoinsForRecovery(setId: 1)) - // .thenAnswer((_) async => getCoinsForRecoveryResponse); - when(client.getUsedCoinSerials(startNumber: 0)) - .thenAnswer((_) async => GetUsedSerialsSampleData.serials); - - when(cachedClient.clearSharedTransactionCache(coin: Coin.firo)) - .thenAnswer((_) async => {}); - - // mock price calls - when(priceAPI.getPricesAnd24hChange(baseCurrency: "USD")).thenAnswer( - (_) async => {Coin.firo: Tuple2(Decimal.fromInt(10), 1.0)}); - - final firo = FiroWallet( - walletName: testWalletName, - walletId: "${testWalletId}fullRescan", - coin: Coin.firo, - client: client, - cachedClient: cachedClient, - secureStore: secureStore, - priceAPI: priceAPI, - tracker: MockTransactionNotificationTracker(), - ); - - // pre grab derivations in order to set up mock calls needed later on - await firo.fillAddresses(TEST_MNEMONIC); - final wallet = await Hive.openBox("${testWalletId}fullRescan"); - - final rcv = await secureStore.read( - key: "${testWalletId}fullRescan_receiveDerivations"); - final chg = await secureStore.read( - key: "${testWalletId}fullRescan_changeDerivations"); - final receiveDerivations = - Map.from(jsonDecode(rcv as String) as Map); - final changeDerivations = - Map.from(jsonDecode(chg as String) as Map); - - for (int i = 0; i < receiveDerivations.length; i++) { - final receiveHash = AddressUtils.convertToScriptHash( - receiveDerivations["$i"]!["address"] as String, firoNetwork); - final changeHash = AddressUtils.convertToScriptHash( - changeDerivations["$i"]!["address"] as String, firoNetwork); - List> data; - switch (receiveHash) { - case SampleGetHistoryData.scripthash0: - data = SampleGetHistoryData.data0; - break; - case SampleGetHistoryData.scripthash1: - data = SampleGetHistoryData.data1; - break; - case SampleGetHistoryData.scripthash2: - data = SampleGetHistoryData.data2; - break; - case SampleGetHistoryData.scripthash3: - data = SampleGetHistoryData.data3; - break; - default: - data = []; - } - when(client.getHistory(scripthash: receiveHash)) - .thenAnswer((_) async => data); - - switch (changeHash) { - case SampleGetHistoryData.scripthash0: - data = SampleGetHistoryData.data0; - break; - case SampleGetHistoryData.scripthash1: - data = SampleGetHistoryData.data1; - break; - case SampleGetHistoryData.scripthash2: - data = SampleGetHistoryData.data2; - break; - case SampleGetHistoryData.scripthash3: - data = SampleGetHistoryData.data3; - break; - default: - data = []; - } - - when(client.getHistory(scripthash: changeHash)) - .thenAnswer((_) async => data); - } - - when(client.getLatestCoinId()).thenThrow(Exception()); - - bool didThrow = false; - try { - await firo.fullRescan(20, 1000); - } catch (e) { - didThrow = true; - } - expect(didThrow, true); - - final receivingAddresses = await wallet.get('receivingAddresses'); - expect(receivingAddresses, null); - - final changeAddresses = await wallet.get('changeAddresses'); - expect(changeAddresses, null); - - final receivingIndex = await wallet.get('receivingIndex'); - expect(receivingIndex, null); - - final changeIndex = await wallet.get('changeIndex'); - expect(changeIndex, null); - - final _rcv = await secureStore.read( - key: "${testWalletId}fullRescan_receiveDerivations"); - final _chg = await secureStore.read( - key: "${testWalletId}fullRescan_changeDerivations"); - final _receiveDerivations = - Map.from(jsonDecode(_rcv as String) as Map); - final _changeDerivations = - Map.from(jsonDecode(_chg as String) as Map); - - expect(_receiveDerivations.length, 40); - expect(_changeDerivations.length, 40); - - final mintIndex = await wallet.get('mintIndex'); - expect(mintIndex, null); - - final lelantusCoins = await wallet.get('_lelantus_coins'); - expect(lelantusCoins, null); - - final jIndex = await wallet.get('jindex'); - expect(jIndex, null); - - final lelantusTxModel = await wallet.get('latest_lelantus_tx_model'); - expect(lelantusTxModel, null); - }, timeout: const Timeout(Duration(minutes: 3))); - - test("recoverFromMnemonic then fullRescan", () async { - TestWidgetsFlutterBinding.ensureInitialized(); - const MethodChannel('uk.spiralarm.flutter/devicelocale') - .setMockMethodCallHandler((methodCall) async => 'en_US'); - - final client = MockElectrumX(); - final cachedClient = MockCachedElectrumX(); - final secureStore = FakeSecureStorage(); - final priceAPI = MockPriceAPI(); - - // mock electrumx client calls - when(client.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - - when(client.getLatestCoinId()).thenAnswer((_) async => 1); - // when(client.getCoinsForRecovery(setId: 1)) - // .thenAnswer((_) async => getCoinsForRecoveryResponse); - when(client.getUsedCoinSerials(startNumber: 0)) - .thenAnswer((_) async => GetUsedSerialsSampleData.serials); - - when(cachedClient.clearSharedTransactionCache(coin: Coin.firo)) - .thenAnswer((_) async => {}); - - when(cachedClient.getAnonymitySet( - groupId: "1", blockhash: "", coin: Coin.firo)) - .thenAnswer((_) async => GetAnonymitySetSampleData.data); - when(cachedClient.getUsedCoinSerials(startNumber: 0, coin: Coin.firo)) - .thenAnswer( - (_) async => GetUsedSerialsSampleData.serials['serials'] as List); - // mock price calls - when(priceAPI.getPricesAnd24hChange(baseCurrency: "USD")).thenAnswer( - (_) async => {Coin.firo: Tuple2(Decimal.fromInt(10), 1.0)}); - - final firo = FiroWallet( - walletName: testWalletName, - walletId: "${testWalletId}recoverFromMnemonic", - coin: Coin.firo, - client: client, - cachedClient: cachedClient, - secureStore: secureStore, - priceAPI: priceAPI, - tracker: MockTransactionNotificationTracker(), - ); - - // pre grab derivations in order to set up mock calls needed later on - await firo.fillAddresses(TEST_MNEMONIC); - final wallet = - await Hive.openBox("${testWalletId}recoverFromMnemonic"); - - final rcv = await secureStore.read( - key: "${testWalletId}recoverFromMnemonic_receiveDerivations"); - final chg = await secureStore.read( - key: "${testWalletId}recoverFromMnemonic_changeDerivations"); - final receiveDerivations = - Map.from(jsonDecode(rcv as String) as Map); - final changeDerivations = - Map.from(jsonDecode(chg as String) as Map); - - for (int i = 0; i < receiveDerivations.length; i++) { - final receiveHash = AddressUtils.convertToScriptHash( - receiveDerivations["$i"]!["address"] as String, firoNetwork); - final changeHash = AddressUtils.convertToScriptHash( - changeDerivations["$i"]!["address"] as String, firoNetwork); - List> data; - switch (receiveHash) { - case SampleGetHistoryData.scripthash0: - data = SampleGetHistoryData.data0; - break; - case SampleGetHistoryData.scripthash1: - data = SampleGetHistoryData.data1; - break; - case SampleGetHistoryData.scripthash2: - data = SampleGetHistoryData.data2; - break; - case SampleGetHistoryData.scripthash3: - data = SampleGetHistoryData.data3; - break; - default: - data = []; - } - when(client.getHistory(scripthash: receiveHash)) - .thenAnswer((_) async => data); - - switch (changeHash) { - case SampleGetHistoryData.scripthash0: - data = SampleGetHistoryData.data0; - break; - case SampleGetHistoryData.scripthash1: - data = SampleGetHistoryData.data1; - break; - case SampleGetHistoryData.scripthash2: - data = SampleGetHistoryData.data2; - break; - case SampleGetHistoryData.scripthash3: - data = SampleGetHistoryData.data3; - break; - default: - data = []; - } - - when(client.getHistory(scripthash: changeHash)) - .thenAnswer((_) async => data); - } - - when(client.getBatchHistory(args: { - "0": [SampleGetHistoryData.scripthash0], - "1": [SampleGetHistoryData.scripthash3] - })).thenAnswer((realInvocation) async => { - "0": SampleGetHistoryData.data0, - "1": SampleGetHistoryData.data3, - }); - - // mock transaction calls - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash0, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData0); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash1, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData1); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash2, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData2); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash3, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData3); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash4, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData4); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash5, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData5); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash6, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData6); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash7, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData7); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash8, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData8); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash9, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData9); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash10, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData10); - - await firo.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 20, - maxNumberOfIndexesToCheck: 1000, - height: 0); - - final receivingAddresses = await wallet.get('receivingAddresses'); - expect(receivingAddresses, ["a8VV7vMzJdTQj1eLEJNskhLEBUxfNWhpAg"]); - - final changeAddresses = await wallet.get('changeAddresses'); - expect(changeAddresses, ["a5V5r6We6mNZzWJwGwEeRML3mEYLjvK39w"]); - - final receivingIndex = await wallet.get('receivingIndex'); - expect(receivingIndex, 0); - - final changeIndex = await wallet.get('changeIndex'); - expect(changeIndex, 0); - - final _rcv = await secureStore.read( - key: "${testWalletId}recoverFromMnemonic_receiveDerivations"); - final _chg = await secureStore.read( - key: "${testWalletId}recoverFromMnemonic_changeDerivations"); - final _receiveDerivations = - Map.from(jsonDecode(_rcv as String) as Map); - final _changeDerivations = - Map.from(jsonDecode(_chg as String) as Map); - // expect(_receiveDerivations.length, 190); - // expect(_changeDerivations.length, 190); - expect(_receiveDerivations.length, 80); - expect(_changeDerivations.length, 80); - - final mintIndex = await wallet.get('mintIndex'); - expect(mintIndex, 8); - - final lelantusCoins = await wallet.get('_lelantus_coins') as List; - expect(lelantusCoins.length, 7); - final lcoin = lelantusCoins - .firstWhere((element) => - (Map.from(element as Map)) - .values - .first - .txId == - "36c92daa4005d368e28cea917fdb2c1e7069319a4a79fb2ff45c089100680232") - .values - .first as LelantusCoin; - expect(lcoin.index, 1); - expect(lcoin.value, 9658); - expect(lcoin.publicCoin, - "7fd927efbea0a9e4ba299209aaee610c63359857596be0a2da276011a0baa84a0000"); - expect(lcoin.txId, - "36c92daa4005d368e28cea917fdb2c1e7069319a4a79fb2ff45c089100680232"); - expect(lcoin.anonymitySetId, 1); - expect(lcoin.isUsed, true); - - final jIndex = await wallet.get('jindex'); - expect(jIndex, [2, 4, 6]); - - final lelantusTxModel = await wallet.get('latest_lelantus_tx_model'); - expect(lelantusTxModel.getAllTransactions().length, 5); - - await firo.fullRescan(20, 1000); - - final _receivingAddresses = await wallet.get('receivingAddresses'); - expect(_receivingAddresses, ["a8VV7vMzJdTQj1eLEJNskhLEBUxfNWhpAg"]); - - final _changeAddresses = await wallet.get('changeAddresses'); - expect(_changeAddresses, ["a5V5r6We6mNZzWJwGwEeRML3mEYLjvK39w"]); - - final _receivingIndex = await wallet.get('receivingIndex'); - expect(_receivingIndex, 0); - - final _changeIndex = await wallet.get('changeIndex'); - expect(_changeIndex, 0); - - final __rcv = await secureStore.read( - key: "${testWalletId}recoverFromMnemonic_receiveDerivations"); - final __chg = await secureStore.read( - key: "${testWalletId}recoverFromMnemonic_changeDerivations"); - final __receiveDerivations = - Map.from(jsonDecode(__rcv as String) as Map); - final __changeDerivations = - Map.from(jsonDecode(__chg as String) as Map); - // expect(__receiveDerivations.length, 150); - // expect(__changeDerivations.length, 150); - expect(__receiveDerivations.length, 40); - expect(__changeDerivations.length, 40); - - final _mintIndex = await wallet.get('mintIndex'); - expect(_mintIndex, 8); - - final _lelantusCoins = await wallet.get('_lelantus_coins') as List; - expect(_lelantusCoins.length, 7); - final _lcoin = _lelantusCoins - .firstWhere((element) => - (Map.from(element as Map)) - .values - .first - .txId == - "36c92daa4005d368e28cea917fdb2c1e7069319a4a79fb2ff45c089100680232") - .values - .first as LelantusCoin; - expect(_lcoin.index, 1); - expect(_lcoin.value, 9658); - expect(_lcoin.publicCoin, - "7fd927efbea0a9e4ba299209aaee610c63359857596be0a2da276011a0baa84a0000"); - expect(_lcoin.txId, - "36c92daa4005d368e28cea917fdb2c1e7069319a4a79fb2ff45c089100680232"); - expect(_lcoin.anonymitySetId, 1); - expect(_lcoin.isUsed, true); - - final _jIndex = await wallet.get('jindex'); - expect(_jIndex, [2, 4, 6]); - - final _lelantusTxModel = await wallet.get('latest_lelantus_tx_model'); - expect(_lelantusTxModel.getAllTransactions().length, 5); - }, timeout: const Timeout(Duration(minutes: 6))); + // test("recoverFromMnemonic succeeds", () async { + // TestWidgetsFlutterBinding.ensureInitialized(); + // const MethodChannel('uk.spiralarm.flutter/devicelocale') + // .setMockMethodCallHandler((methodCall) async => 'en_US'); + // + // final client = MockElectrumX(); + // final cachedClient = MockCachedElectrumX(); + // final secureStore = FakeSecureStorage(); + // + // // mock electrumx client calls + // when(client.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // + // when(client.getLatestCoinId()).thenAnswer((_) async => 1); + // when(cachedClient.getUsedCoinSerials( + // coin: Coin.firo, + // )).thenAnswer( + // (_) async => GetUsedSerialsSampleData.serials["serials"] as List); + // + // when(cachedClient.getAnonymitySet( + // groupId: "1", + // coin: Coin.firo, + // )).thenAnswer( + // (_) async => GetAnonymitySetSampleData.data, + // ); + // + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash0, + // coin: Coin.firo, + // )).thenAnswer((_) async { + // return SampleGetTransactionData.txData0; + // }); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash1, + // coin: Coin.firo, + // )).thenAnswer((_) async { + // return SampleGetTransactionData.txData1; + // }); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash2, + // coin: Coin.firo, + // )).thenAnswer((_) async { + // return SampleGetTransactionData.txData2; + // }); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash3, + // coin: Coin.firo, + // )).thenAnswer((_) async { + // return SampleGetTransactionData.txData3; + // }); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash4, + // coin: Coin.firo, + // )).thenAnswer((_) async { + // return SampleGetTransactionData.txData4; + // }); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash5, + // coin: Coin.firo, + // )).thenAnswer((_) async { + // return SampleGetTransactionData.txData5; + // }); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash6, + // coin: Coin.firo, + // )).thenAnswer((_) async { + // return SampleGetTransactionData.txData6; + // }); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash8, + // coin: Coin.firo, + // )).thenAnswer((_) async { + // return SampleGetTransactionData.txData8; + // }); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash9, + // coin: Coin.firo, + // )).thenAnswer((_) async { + // return SampleGetTransactionData.txData9; + // }); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash7, + // coin: Coin.firo, + // )).thenAnswer((_) async { + // return SampleGetTransactionData.txData7; + // }); + // + // final firo = FiroWallet( + // walletName: testWalletName, + // walletId: "${testWalletId}recoverFromMnemonic", + // coin: Coin.firo, + // client: client, + // cachedClient: cachedClient, + // secureStore: secureStore, + // tracker: MockTransactionNotificationTracker(), + // ); + // + // // pre grab derivations in order to set up mock calls needed later on + // await firo.fillAddresses(TEST_MNEMONIC); + // final wallet = + // await Hive.openBox("${testWalletId}recoverFromMnemonic"); + // + // final rcv = await secureStore.read( + // key: "${testWalletId}recoverFromMnemonic_receiveDerivations"); + // final chg = await secureStore.read( + // key: "${testWalletId}recoverFromMnemonic_changeDerivations"); + // final receiveDerivations = + // Map.from(jsonDecode(rcv as String) as Map); + // final changeDerivations = + // Map.from(jsonDecode(chg as String) as Map); + // + // for (int i = 0; i < receiveDerivations.length; i++) { + // final receiveHash = AddressUtils.convertToScriptHash( + // receiveDerivations["$i"]!["address"] as String, firoNetwork); + // final changeHash = AddressUtils.convertToScriptHash( + // changeDerivations["$i"]!["address"] as String, firoNetwork); + // List> data; + // switch (receiveHash) { + // case SampleGetHistoryData.scripthash0: + // data = SampleGetHistoryData.data0; + // break; + // case SampleGetHistoryData.scripthash1: + // data = SampleGetHistoryData.data1; + // break; + // case SampleGetHistoryData.scripthash2: + // data = SampleGetHistoryData.data2; + // break; + // case SampleGetHistoryData.scripthash3: + // data = SampleGetHistoryData.data3; + // break; + // default: + // data = []; + // } + // when(client.getHistory(scripthash: receiveHash)) + // .thenAnswer((_) async => data); + // + // switch (changeHash) { + // case SampleGetHistoryData.scripthash0: + // data = SampleGetHistoryData.data0; + // break; + // case SampleGetHistoryData.scripthash1: + // data = SampleGetHistoryData.data1; + // break; + // case SampleGetHistoryData.scripthash2: + // data = SampleGetHistoryData.data2; + // break; + // case SampleGetHistoryData.scripthash3: + // data = SampleGetHistoryData.data3; + // break; + // default: + // data = []; + // } + // + // when(client.getHistory(scripthash: changeHash)) + // .thenAnswer((_) async => data); + // } + // + // when(client.getBatchHistory(args: { + // "0": [SampleGetHistoryData.scripthash0], + // "1": [SampleGetHistoryData.scripthash3] + // })).thenAnswer((realInvocation) async => { + // "0": SampleGetHistoryData.data0, + // "1": SampleGetHistoryData.data3, + // }); + // + // await firo.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 20, + // height: 0, + // maxNumberOfIndexesToCheck: 1000); + // + // final receivingAddresses = await wallet.get('receivingAddresses'); + // expect(receivingAddresses, ["a8VV7vMzJdTQj1eLEJNskhLEBUxfNWhpAg"]); + // + // final changeAddresses = await wallet.get('changeAddresses'); + // expect(changeAddresses, ["a5V5r6We6mNZzWJwGwEeRML3mEYLjvK39w"]); + // + // final receivingIndex = await wallet.get('receivingIndex'); + // expect(receivingIndex, 0); + // + // final changeIndex = await wallet.get('changeIndex'); + // expect(changeIndex, 0); + // + // final _rcv = await secureStore.read( + // key: "${testWalletId}recoverFromMnemonic_receiveDerivations"); + // final _chg = await secureStore.read( + // key: "${testWalletId}recoverFromMnemonic_changeDerivations"); + // final _receiveDerivations = + // Map.from(jsonDecode(_rcv as String) as Map); + // final _changeDerivations = + // Map.from(jsonDecode(_chg as String) as Map); + // // expect(_receiveDerivations.length, 190); + // // expect(_changeDerivations.length, 190); + // expect(_receiveDerivations.length, 80); + // expect(_changeDerivations.length, 80); + // + // final mintIndex = await wallet.get('mintIndex'); + // expect(mintIndex, 8); + // + // final lelantusCoins = await wallet.get('_lelantus_coins') as List; + // expect(lelantusCoins.length, 7); + // final lcoin = lelantusCoins + // .firstWhere((element) => + // (Map.from(element as Map)) + // .values + // .first + // .txId == + // "36c92daa4005d368e28cea917fdb2c1e7069319a4a79fb2ff45c089100680232") + // .values + // .first as LelantusCoin; + // expect(lcoin.index, 1); + // expect(lcoin.value, 9658); + // expect(lcoin.publicCoin, + // "7fd927efbea0a9e4ba299209aaee610c63359857596be0a2da276011a0baa84a0000"); + // expect(lcoin.txId, + // "36c92daa4005d368e28cea917fdb2c1e7069319a4a79fb2ff45c089100680232"); + // expect(lcoin.anonymitySetId, 1); + // expect(lcoin.isUsed, true); + // + // final jIndex = await wallet.get('jindex'); + // expect(jIndex, [2, 4, 6]); + // + // final lelantusTxModel = await wallet.get('latest_lelantus_tx_model'); + // expect(lelantusTxModel.getAllTransactions().length, 5); + // }, timeout: const Timeout(Duration(minutes: 5))); + // + // test("fullRescan succeeds", () async { + // TestWidgetsFlutterBinding.ensureInitialized(); + // const MethodChannel('uk.spiralarm.flutter/devicelocale') + // .setMockMethodCallHandler((methodCall) async => 'en_US'); + // + // final client = MockElectrumX(); + // final cachedClient = MockCachedElectrumX(); + // final secureStore = FakeSecureStorage(); + // + // await secureStore.write( + // key: '${testWalletId}fullRescan_mnemonic', value: TEST_MNEMONIC); + // + // // mock electrumx client calls + // when(client.getLatestCoinId()).thenAnswer((_) async => 1); + // // when(client.getCoinsForRecovery(setId: 1)) + // // .thenAnswer((_) async => getCoinsForRecoveryResponse); + // when(client.getUsedCoinSerials(startNumber: 0)) + // .thenAnswer((_) async => GetUsedSerialsSampleData.serials); + // + // when(cachedClient.getAnonymitySet( + // groupId: "1", blockhash: "", coin: Coin.firo)) + // .thenAnswer((_) async => GetAnonymitySetSampleData.data); + // when(cachedClient.getUsedCoinSerials(startNumber: 0, coin: Coin.firo)) + // .thenAnswer( + // (_) async => GetUsedSerialsSampleData.serials['serials'] as List); + // + // when(cachedClient.clearSharedTransactionCache(coin: Coin.firo)) + // .thenAnswer((_) async => {}); + // + // final firo = FiroWallet( + // walletName: testWalletName, + // walletId: "${testWalletId}fullRescan", + // coin: Coin.firo, + // client: client, + // cachedClient: cachedClient, + // secureStore: secureStore, + // tracker: MockTransactionNotificationTracker(), + // ); + // + // // pre grab derivations in order to set up mock calls needed later on + // await firo.fillAddresses(TEST_MNEMONIC); + // final wallet = await Hive.openBox("${testWalletId}fullRescan"); + // + // final rcv = await secureStore.read( + // key: "${testWalletId}fullRescan_receiveDerivations"); + // final chg = await secureStore.read( + // key: "${testWalletId}fullRescan_changeDerivations"); + // final receiveDerivations = + // Map.from(jsonDecode(rcv as String) as Map); + // final changeDerivations = + // Map.from(jsonDecode(chg as String) as Map); + // + // for (int i = 0; i < receiveDerivations.length; i++) { + // final receiveHash = AddressUtils.convertToScriptHash( + // receiveDerivations["$i"]!["address"] as String, firoNetwork); + // final changeHash = AddressUtils.convertToScriptHash( + // changeDerivations["$i"]!["address"] as String, firoNetwork); + // List> data; + // switch (receiveHash) { + // case SampleGetHistoryData.scripthash0: + // data = SampleGetHistoryData.data0; + // break; + // case SampleGetHistoryData.scripthash1: + // data = SampleGetHistoryData.data1; + // break; + // case SampleGetHistoryData.scripthash2: + // data = SampleGetHistoryData.data2; + // break; + // case SampleGetHistoryData.scripthash3: + // data = SampleGetHistoryData.data3; + // break; + // default: + // data = []; + // } + // when(client.getHistory(scripthash: receiveHash)) + // .thenAnswer((_) async => data); + // + // switch (changeHash) { + // case SampleGetHistoryData.scripthash0: + // data = SampleGetHistoryData.data0; + // break; + // case SampleGetHistoryData.scripthash1: + // data = SampleGetHistoryData.data1; + // break; + // case SampleGetHistoryData.scripthash2: + // data = SampleGetHistoryData.data2; + // break; + // case SampleGetHistoryData.scripthash3: + // data = SampleGetHistoryData.data3; + // break; + // default: + // data = []; + // } + // + // when(client.getHistory(scripthash: changeHash)) + // .thenAnswer((_) async => data); + // } + // + // when(client.getBatchHistory(args: { + // "0": [SampleGetHistoryData.scripthash0], + // "1": [SampleGetHistoryData.scripthash3] + // })).thenAnswer((realInvocation) async => { + // "0": SampleGetHistoryData.data0, + // "1": SampleGetHistoryData.data3, + // }); + // + // // mock transaction calls + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash0, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData0); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash1, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData1); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash2, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData2); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash3, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData3); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash4, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData4); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash5, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData5); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash6, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData6); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash7, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData7); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash8, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData8); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash9, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData9); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash10, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData10); + // + // await firo.fullRescan(20, 1000); + // + // final receivingAddresses = await wallet.get('receivingAddresses'); + // expect(receivingAddresses, ["a8VV7vMzJdTQj1eLEJNskhLEBUxfNWhpAg"]); + // + // final changeAddresses = await wallet.get('changeAddresses'); + // expect(changeAddresses, ["a5V5r6We6mNZzWJwGwEeRML3mEYLjvK39w"]); + // + // final receivingIndex = await wallet.get('receivingIndex'); + // expect(receivingIndex, 0); + // + // final changeIndex = await wallet.get('changeIndex'); + // expect(changeIndex, 0); + // + // final _rcv = await secureStore.read( + // key: "${testWalletId}fullRescan_receiveDerivations"); + // final _chg = await secureStore.read( + // key: "${testWalletId}fullRescan_changeDerivations"); + // final _receiveDerivations = + // Map.from(jsonDecode(_rcv as String) as Map); + // final _changeDerivations = + // Map.from(jsonDecode(_chg as String) as Map); + // // expect(_receiveDerivations.length, 150); + // // expect(_changeDerivations.length, 150); + // expect(_receiveDerivations.length, 40); + // expect(_changeDerivations.length, 40); + // + // final mintIndex = await wallet.get('mintIndex'); + // expect(mintIndex, 8); + // + // final lelantusCoins = await wallet.get('_lelantus_coins') as List; + // expect(lelantusCoins.length, 7); + // final lcoin = lelantusCoins + // .firstWhere((element) => + // (Map.from(element as Map)) + // .values + // .first + // .txId == + // "36c92daa4005d368e28cea917fdb2c1e7069319a4a79fb2ff45c089100680232") + // .values + // .first as LelantusCoin; + // expect(lcoin.index, 1); + // expect(lcoin.value, 9658); + // expect(lcoin.publicCoin, + // "7fd927efbea0a9e4ba299209aaee610c63359857596be0a2da276011a0baa84a0000"); + // expect(lcoin.txId, + // "36c92daa4005d368e28cea917fdb2c1e7069319a4a79fb2ff45c089100680232"); + // expect(lcoin.anonymitySetId, 1); + // expect(lcoin.isUsed, true); + // + // final jIndex = await wallet.get('jindex'); + // expect(jIndex, [2, 4, 6]); + // + // final lelantusTxModel = await wallet.get('latest_lelantus_tx_model'); + // expect(lelantusTxModel.getAllTransactions().length, 5); + // }, timeout: const Timeout(Duration(minutes: 3))); + // + // test("fullRescan fails", () async { + // TestWidgetsFlutterBinding.ensureInitialized(); + // const MethodChannel('uk.spiralarm.flutter/devicelocale') + // .setMockMethodCallHandler((methodCall) async => 'en_US'); + // + // final client = MockElectrumX(); + // final cachedClient = MockCachedElectrumX(); + // final secureStore = FakeSecureStorage(); + // + // await secureStore.write( + // key: '${testWalletId}fullRescan_mnemonic', value: TEST_MNEMONIC); + // + // // mock electrumx client calls + // when(client.getLatestCoinId()).thenAnswer((_) async => 1); + // // when(client.getCoinsForRecovery(setId: 1)) + // // .thenAnswer((_) async => getCoinsForRecoveryResponse); + // when(client.getUsedCoinSerials(startNumber: 0)) + // .thenAnswer((_) async => GetUsedSerialsSampleData.serials); + // + // when(cachedClient.clearSharedTransactionCache(coin: Coin.firo)) + // .thenAnswer((_) async => {}); + // + // final firo = FiroWallet( + // walletName: testWalletName, + // walletId: "${testWalletId}fullRescan", + // coin: Coin.firo, + // client: client, + // cachedClient: cachedClient, + // secureStore: secureStore, + // tracker: MockTransactionNotificationTracker(), + // ); + // + // // pre grab derivations in order to set up mock calls needed later on + // await firo.fillAddresses(TEST_MNEMONIC); + // final wallet = await Hive.openBox("${testWalletId}fullRescan"); + // + // final rcv = await secureStore.read( + // key: "${testWalletId}fullRescan_receiveDerivations"); + // final chg = await secureStore.read( + // key: "${testWalletId}fullRescan_changeDerivations"); + // final receiveDerivations = + // Map.from(jsonDecode(rcv as String) as Map); + // final changeDerivations = + // Map.from(jsonDecode(chg as String) as Map); + // + // for (int i = 0; i < receiveDerivations.length; i++) { + // final receiveHash = AddressUtils.convertToScriptHash( + // receiveDerivations["$i"]!["address"] as String, firoNetwork); + // final changeHash = AddressUtils.convertToScriptHash( + // changeDerivations["$i"]!["address"] as String, firoNetwork); + // List> data; + // switch (receiveHash) { + // case SampleGetHistoryData.scripthash0: + // data = SampleGetHistoryData.data0; + // break; + // case SampleGetHistoryData.scripthash1: + // data = SampleGetHistoryData.data1; + // break; + // case SampleGetHistoryData.scripthash2: + // data = SampleGetHistoryData.data2; + // break; + // case SampleGetHistoryData.scripthash3: + // data = SampleGetHistoryData.data3; + // break; + // default: + // data = []; + // } + // when(client.getHistory(scripthash: receiveHash)) + // .thenAnswer((_) async => data); + // + // switch (changeHash) { + // case SampleGetHistoryData.scripthash0: + // data = SampleGetHistoryData.data0; + // break; + // case SampleGetHistoryData.scripthash1: + // data = SampleGetHistoryData.data1; + // break; + // case SampleGetHistoryData.scripthash2: + // data = SampleGetHistoryData.data2; + // break; + // case SampleGetHistoryData.scripthash3: + // data = SampleGetHistoryData.data3; + // break; + // default: + // data = []; + // } + // + // when(client.getHistory(scripthash: changeHash)) + // .thenAnswer((_) async => data); + // } + // + // when(client.getLatestCoinId()).thenThrow(Exception()); + // + // bool didThrow = false; + // try { + // await firo.fullRescan(20, 1000); + // } catch (e) { + // didThrow = true; + // } + // expect(didThrow, true); + // + // final receivingAddresses = await wallet.get('receivingAddresses'); + // expect(receivingAddresses, null); + // + // final changeAddresses = await wallet.get('changeAddresses'); + // expect(changeAddresses, null); + // + // final receivingIndex = await wallet.get('receivingIndex'); + // expect(receivingIndex, null); + // + // final changeIndex = await wallet.get('changeIndex'); + // expect(changeIndex, null); + // + // final _rcv = await secureStore.read( + // key: "${testWalletId}fullRescan_receiveDerivations"); + // final _chg = await secureStore.read( + // key: "${testWalletId}fullRescan_changeDerivations"); + // final _receiveDerivations = + // Map.from(jsonDecode(_rcv as String) as Map); + // final _changeDerivations = + // Map.from(jsonDecode(_chg as String) as Map); + // + // expect(_receiveDerivations.length, 40); + // expect(_changeDerivations.length, 40); + // + // final mintIndex = await wallet.get('mintIndex'); + // expect(mintIndex, null); + // + // final lelantusCoins = await wallet.get('_lelantus_coins'); + // expect(lelantusCoins, null); + // + // final jIndex = await wallet.get('jindex'); + // expect(jIndex, null); + // + // final lelantusTxModel = await wallet.get('latest_lelantus_tx_model'); + // expect(lelantusTxModel, null); + // }, timeout: const Timeout(Duration(minutes: 3))); + // + // test("recoverFromMnemonic then fullRescan", () async { + // TestWidgetsFlutterBinding.ensureInitialized(); + // const MethodChannel('uk.spiralarm.flutter/devicelocale') + // .setMockMethodCallHandler((methodCall) async => 'en_US'); + // + // final client = MockElectrumX(); + // final cachedClient = MockCachedElectrumX(); + // final secureStore = FakeSecureStorage(); + // + // // mock electrumx client calls + // when(client.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // + // when(client.getLatestCoinId()).thenAnswer((_) async => 1); + // // when(client.getCoinsForRecovery(setId: 1)) + // // .thenAnswer((_) async => getCoinsForRecoveryResponse); + // when(client.getUsedCoinSerials(startNumber: 0)) + // .thenAnswer((_) async => GetUsedSerialsSampleData.serials); + // + // when(cachedClient.clearSharedTransactionCache(coin: Coin.firo)) + // .thenAnswer((_) async => {}); + // + // when(cachedClient.getAnonymitySet( + // groupId: "1", blockhash: "", coin: Coin.firo)) + // .thenAnswer((_) async => GetAnonymitySetSampleData.data); + // when(cachedClient.getUsedCoinSerials(startNumber: 0, coin: Coin.firo)) + // .thenAnswer( + // (_) async => GetUsedSerialsSampleData.serials['serials'] as List); + // + // final firo = FiroWallet( + // walletName: testWalletName, + // walletId: "${testWalletId}recoverFromMnemonic", + // coin: Coin.firo, + // client: client, + // cachedClient: cachedClient, + // secureStore: secureStore, + // tracker: MockTransactionNotificationTracker(), + // ); + // + // // pre grab derivations in order to set up mock calls needed later on + // await firo.fillAddresses(TEST_MNEMONIC); + // final wallet = + // await Hive.openBox("${testWalletId}recoverFromMnemonic"); + // + // final rcv = await secureStore.read( + // key: "${testWalletId}recoverFromMnemonic_receiveDerivations"); + // final chg = await secureStore.read( + // key: "${testWalletId}recoverFromMnemonic_changeDerivations"); + // final receiveDerivations = + // Map.from(jsonDecode(rcv as String) as Map); + // final changeDerivations = + // Map.from(jsonDecode(chg as String) as Map); + // + // for (int i = 0; i < receiveDerivations.length; i++) { + // final receiveHash = AddressUtils.convertToScriptHash( + // receiveDerivations["$i"]!["address"] as String, firoNetwork); + // final changeHash = AddressUtils.convertToScriptHash( + // changeDerivations["$i"]!["address"] as String, firoNetwork); + // List> data; + // switch (receiveHash) { + // case SampleGetHistoryData.scripthash0: + // data = SampleGetHistoryData.data0; + // break; + // case SampleGetHistoryData.scripthash1: + // data = SampleGetHistoryData.data1; + // break; + // case SampleGetHistoryData.scripthash2: + // data = SampleGetHistoryData.data2; + // break; + // case SampleGetHistoryData.scripthash3: + // data = SampleGetHistoryData.data3; + // break; + // default: + // data = []; + // } + // when(client.getHistory(scripthash: receiveHash)) + // .thenAnswer((_) async => data); + // + // switch (changeHash) { + // case SampleGetHistoryData.scripthash0: + // data = SampleGetHistoryData.data0; + // break; + // case SampleGetHistoryData.scripthash1: + // data = SampleGetHistoryData.data1; + // break; + // case SampleGetHistoryData.scripthash2: + // data = SampleGetHistoryData.data2; + // break; + // case SampleGetHistoryData.scripthash3: + // data = SampleGetHistoryData.data3; + // break; + // default: + // data = []; + // } + // + // when(client.getHistory(scripthash: changeHash)) + // .thenAnswer((_) async => data); + // } + // + // when(client.getBatchHistory(args: { + // "0": [SampleGetHistoryData.scripthash0], + // "1": [SampleGetHistoryData.scripthash3] + // })).thenAnswer((realInvocation) async => { + // "0": SampleGetHistoryData.data0, + // "1": SampleGetHistoryData.data3, + // }); + // + // // mock transaction calls + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash0, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData0); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash1, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData1); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash2, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData2); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash3, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData3); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash4, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData4); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash5, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData5); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash6, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData6); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash7, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData7); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash8, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData8); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash9, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData9); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash10, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData10); + // + // await firo.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 20, + // maxNumberOfIndexesToCheck: 1000, + // height: 0); + // + // final receivingAddresses = await wallet.get('receivingAddresses'); + // expect(receivingAddresses, ["a8VV7vMzJdTQj1eLEJNskhLEBUxfNWhpAg"]); + // + // final changeAddresses = await wallet.get('changeAddresses'); + // expect(changeAddresses, ["a5V5r6We6mNZzWJwGwEeRML3mEYLjvK39w"]); + // + // final receivingIndex = await wallet.get('receivingIndex'); + // expect(receivingIndex, 0); + // + // final changeIndex = await wallet.get('changeIndex'); + // expect(changeIndex, 0); + // + // final _rcv = await secureStore.read( + // key: "${testWalletId}recoverFromMnemonic_receiveDerivations"); + // final _chg = await secureStore.read( + // key: "${testWalletId}recoverFromMnemonic_changeDerivations"); + // final _receiveDerivations = + // Map.from(jsonDecode(_rcv as String) as Map); + // final _changeDerivations = + // Map.from(jsonDecode(_chg as String) as Map); + // // expect(_receiveDerivations.length, 190); + // // expect(_changeDerivations.length, 190); + // expect(_receiveDerivations.length, 80); + // expect(_changeDerivations.length, 80); + // + // final mintIndex = await wallet.get('mintIndex'); + // expect(mintIndex, 8); + // + // final lelantusCoins = await wallet.get('_lelantus_coins') as List; + // expect(lelantusCoins.length, 7); + // final lcoin = lelantusCoins + // .firstWhere((element) => + // (Map.from(element as Map)) + // .values + // .first + // .txId == + // "36c92daa4005d368e28cea917fdb2c1e7069319a4a79fb2ff45c089100680232") + // .values + // .first as LelantusCoin; + // expect(lcoin.index, 1); + // expect(lcoin.value, 9658); + // expect(lcoin.publicCoin, + // "7fd927efbea0a9e4ba299209aaee610c63359857596be0a2da276011a0baa84a0000"); + // expect(lcoin.txId, + // "36c92daa4005d368e28cea917fdb2c1e7069319a4a79fb2ff45c089100680232"); + // expect(lcoin.anonymitySetId, 1); + // expect(lcoin.isUsed, true); + // + // final jIndex = await wallet.get('jindex'); + // expect(jIndex, [2, 4, 6]); + // + // final lelantusTxModel = await wallet.get('latest_lelantus_tx_model'); + // expect(lelantusTxModel.getAllTransactions().length, 5); + // + // await firo.fullRescan(20, 1000); + // + // final _receivingAddresses = await wallet.get('receivingAddresses'); + // expect(_receivingAddresses, ["a8VV7vMzJdTQj1eLEJNskhLEBUxfNWhpAg"]); + // + // final _changeAddresses = await wallet.get('changeAddresses'); + // expect(_changeAddresses, ["a5V5r6We6mNZzWJwGwEeRML3mEYLjvK39w"]); + // + // final _receivingIndex = await wallet.get('receivingIndex'); + // expect(_receivingIndex, 0); + // + // final _changeIndex = await wallet.get('changeIndex'); + // expect(_changeIndex, 0); + // + // final __rcv = await secureStore.read( + // key: "${testWalletId}recoverFromMnemonic_receiveDerivations"); + // final __chg = await secureStore.read( + // key: "${testWalletId}recoverFromMnemonic_changeDerivations"); + // final __receiveDerivations = + // Map.from(jsonDecode(__rcv as String) as Map); + // final __changeDerivations = + // Map.from(jsonDecode(__chg as String) as Map); + // // expect(__receiveDerivations.length, 150); + // // expect(__changeDerivations.length, 150); + // expect(__receiveDerivations.length, 40); + // expect(__changeDerivations.length, 40); + // + // final _mintIndex = await wallet.get('mintIndex'); + // expect(_mintIndex, 8); + // + // final _lelantusCoins = await wallet.get('_lelantus_coins') as List; + // expect(_lelantusCoins.length, 7); + // final _lcoin = _lelantusCoins + // .firstWhere((element) => + // (Map.from(element as Map)) + // .values + // .first + // .txId == + // "36c92daa4005d368e28cea917fdb2c1e7069319a4a79fb2ff45c089100680232") + // .values + // .first as LelantusCoin; + // expect(_lcoin.index, 1); + // expect(_lcoin.value, 9658); + // expect(_lcoin.publicCoin, + // "7fd927efbea0a9e4ba299209aaee610c63359857596be0a2da276011a0baa84a0000"); + // expect(_lcoin.txId, + // "36c92daa4005d368e28cea917fdb2c1e7069319a4a79fb2ff45c089100680232"); + // expect(_lcoin.anonymitySetId, 1); + // expect(_lcoin.isUsed, true); + // + // final _jIndex = await wallet.get('jindex'); + // expect(_jIndex, [2, 4, 6]); + // + // final _lelantusTxModel = await wallet.get('latest_lelantus_tx_model'); + // expect(_lelantusTxModel.getAllTransactions().length, 5); + // }, timeout: const Timeout(Duration(minutes: 6))); test("recoverFromMnemonic fails testnet", () async { final client = MockElectrumX(); final cachedClient = MockCachedElectrumX(); final secureStore = FakeSecureStorage(); - final priceAPI = MockPriceAPI(); // mock electrumx client calls when(client.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_MAINNET, "hash_function": "sha256", - "services": [] + "services": [] }); final firo = FiroWallet( @@ -2179,7 +2123,6 @@ void main() { client: client, cachedClient: cachedClient, secureStore: secureStore, - priceAPI: priceAPI, tracker: MockTransactionNotificationTracker(), ); @@ -2196,28 +2139,26 @@ void main() { final client = MockElectrumX(); final cachedClient = MockCachedElectrumX(); final secureStore = FakeSecureStorage(); - final priceAPI = MockPriceAPI(); // mock electrumx client calls when(client.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_TESTNET, "hash_function": "sha256", - "services": [] + "services": [] }); final firo = FiroWallet( walletName: testWalletName, - walletId: testWalletId + "recoverFromMnemonic fails mainnet", + walletId: "${testWalletId}recoverFromMnemonic fails mainnet", coin: Coin.firo, client: client, cachedClient: cachedClient, secureStore: secureStore, - priceAPI: priceAPI, tracker: MockTransactionNotificationTracker(), ); @@ -2230,98 +2171,6 @@ void main() { throwsA(isA())); }); - test("incrementAddressIndexForChain receiving", () async { - final firo = FiroWallet( - walletId: "${testWalletId}incrementAddressIndexForChain receiving", - walletName: testWalletName, - coin: Coin.firo, - client: MockElectrumX(), - cachedClient: MockCachedElectrumX(), - secureStore: FakeSecureStorage(), - priceAPI: MockPriceAPI(), - tracker: MockTransactionNotificationTracker(), - ); - - final wallet = await Hive.openBox( - "${testWalletId}incrementAddressIndexForChain receiving"); - await wallet.put("receivingIndex", 1); - - await expectLater(() async => await firo.incrementAddressIndexForChain(0), - returnsNormally); - - expect(wallet.get("receivingIndex"), 2); - }); - - test("incrementAddressIndexForChain change", () async { - final firo = FiroWallet( - walletId: "${testWalletId}incrementAddressIndexForChain change", - walletName: testWalletName, - coin: Coin.firo, - client: MockElectrumX(), - cachedClient: MockCachedElectrumX(), - secureStore: FakeSecureStorage(), - priceAPI: MockPriceAPI(), - tracker: MockTransactionNotificationTracker(), - ); - - final wallet = await Hive.openBox( - "${testWalletId}incrementAddressIndexForChain change"); - await wallet.put("changeIndex", 1); - - await expectLater(() async => await firo.incrementAddressIndexForChain(1), - returnsNormally); - - expect(wallet.get("changeIndex"), 2); - }); - - test("addToAddressesArrayForChain receiving", () async { - final firo = FiroWallet( - walletId: "${testWalletId}addToAddressesArrayForChain receiving", - walletName: testWalletName, - coin: Coin.firo, - client: MockElectrumX(), - cachedClient: MockCachedElectrumX(), - secureStore: FakeSecureStorage(), - priceAPI: MockPriceAPI(), - tracker: MockTransactionNotificationTracker(), - ); - - final wallet = await Hive.openBox( - "${testWalletId}addToAddressesArrayForChain receiving"); - - await expectLater( - () async => - await firo.addToAddressesArrayForChain("Some Address String", 0), - returnsNormally); - - expect(wallet.get("receivingAddresses"), ["Some Address String"]); - }); - - test("addToAddressesArrayForChain change", () async { - final firo = FiroWallet( - walletId: "${testWalletId}addToAddressesArrayForChain change", - walletName: testWalletName, - coin: Coin.firo, - client: MockElectrumX(), - cachedClient: MockCachedElectrumX(), - secureStore: FakeSecureStorage(), - priceAPI: MockPriceAPI(), - tracker: MockTransactionNotificationTracker(), - ); - - final wallet = await Hive.openBox( - "${testWalletId}addToAddressesArrayForChain change"); - await wallet.put("changeAddresses", ["some address A"]); - - await expectLater( - () async => - await firo.addToAddressesArrayForChain("Some Address B", 1), - returnsNormally); - - expect( - wallet.get("changeAddresses"), ["some address A", "Some Address B"]); - }); - test("checkReceivingAddressForTransactions fails", () async { final firo = FiroWallet( walletId: "${testWalletId}checkReceivingAddressForTransactions fails", @@ -2330,7 +2179,6 @@ void main() { client: MockElectrumX(), cachedClient: MockCachedElectrumX(), secureStore: FakeSecureStorage(), - priceAPI: MockPriceAPI(), tracker: MockTransactionNotificationTracker(), ); @@ -2343,41 +2191,40 @@ void main() { expect(didThrow, true); }); - test("checkReceivingAddressForTransactions numtxs >= 1", () async { - final client = MockElectrumX(); - final secureStore = FakeSecureStorage(); - - when(client.getHistory(scripthash: SampleGetHistoryData.scripthash1)) - .thenAnswer((_) async => SampleGetHistoryData.data1); - - final firo = FiroWallet( - walletId: - "${testWalletId}checkReceivingAddressForTransactions numtxs >= 1", - walletName: testWalletName, - coin: Coin.firo, - client: client, - cachedClient: MockCachedElectrumX(), - secureStore: secureStore, - priceAPI: MockPriceAPI(), - tracker: MockTransactionNotificationTracker(), - ); - - final wallet = await Hive.openBox( - "${testWalletId}checkReceivingAddressForTransactions numtxs >= 1"); - await secureStore.write( - key: - "${testWalletId}checkReceivingAddressForTransactions numtxs >= 1_mnemonic", - value: TEST_MNEMONIC); - await wallet - .put("receivingAddresses", ["aPjLWDTPQsoPHUTxKBNRzoebDALj3eTcfh"]); - - await wallet.put("receivingIndex", 1); - - await firo.checkReceivingAddressForTransactions(); - - expect(await wallet.get("receivingIndex"), 2); - expect((await wallet.get("receivingAddresses")).length, 2); - }); + // test("checkReceivingAddressForTransactions numtxs >= 1", () async { + // final client = MockElectrumX(); + // final secureStore = FakeSecureStorage(); + // + // when(client.getHistory(scripthash: SampleGetHistoryData.scripthash1)) + // .thenAnswer((_) async => SampleGetHistoryData.data1); + // + // final firo = FiroWallet( + // walletId: + // "${testWalletId}checkReceivingAddressForTransactions numtxs >= 1", + // walletName: testWalletName, + // coin: Coin.firo, + // client: client, + // cachedClient: MockCachedElectrumX(), + // secureStore: secureStore, + // tracker: MockTransactionNotificationTracker(), + // ); + // + // final wallet = await Hive.openBox( + // "${testWalletId}checkReceivingAddressForTransactions numtxs >= 1"); + // await secureStore.write( + // key: + // "${testWalletId}checkReceivingAddressForTransactions numtxs >= 1_mnemonic", + // value: TEST_MNEMONIC); + // await wallet + // .put("receivingAddresses", ["aPjLWDTPQsoPHUTxKBNRzoebDALj3eTcfh"]); + // + // await wallet.put("receivingIndex", 1); + // + // await firo.checkReceivingAddressForTransactions(); + // + // expect(await wallet.get("receivingIndex"), 2); + // expect((await wallet.get("receivingAddresses")).length, 2); + // }); test("getLatestSetId", () async { final client = MockElectrumX(); @@ -2385,13 +2232,12 @@ void main() { when(client.getLatestCoinId()).thenAnswer((_) async => 1); final firo = FiroWallet( - walletId: testWalletId + "exit", + walletId: "${testWalletId}exit", walletName: testWalletName, coin: Coin.firo, client: client, cachedClient: MockCachedElectrumX(), secureStore: FakeSecureStorage(), - priceAPI: MockPriceAPI(), tracker: MockTransactionNotificationTracker(), ); @@ -2412,7 +2258,7 @@ void main() { // client: client, // cachedClient: MockCachedElectrumX(), // secureStore: FakeSecureStorage(), - // priceAPI: MockPriceAPI(), + // // tracker: MockTransactionNotificationTracker(), // ); // @@ -2431,17 +2277,16 @@ void main() { groupId: "1", blockhash: "", coin: Coin.firo)) .thenAnswer((_) async => GetAnonymitySetSampleData.data); when(cachedClient.getUsedCoinSerials(startNumber: 0, coin: Coin.firo)) - .thenAnswer( - (_) async => GetUsedSerialsSampleData.serials['serials'] as List); + .thenAnswer((_) async => List.from( + GetUsedSerialsSampleData.serials['serials'] as List)); final firo = FiroWallet( - walletId: testWalletId + "getUsedCoinSerials", + walletId: "${testWalletId}getUsedCoinSerials", walletName: testWalletName, coin: Coin.firo, client: client, cachedClient: cachedClient, secureStore: FakeSecureStorage(), - priceAPI: MockPriceAPI(), tracker: MockTransactionNotificationTracker(), ); @@ -2457,7 +2302,6 @@ void main() { final client = MockElectrumX(); final cachedClient = MockCachedElectrumX(); final secureStore = FakeSecureStorage(); - final priceAPI = MockPriceAPI(); // set mnemonic await secureStore.write( @@ -2481,14 +2325,14 @@ void main() { // mock electrumx client calls when(client.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_MAINNET, "hash_function": "sha256", - "services": [] + "services": [] }); when(client.getLatestCoinId()).thenAnswer((_) async => 1); @@ -2548,10 +2392,6 @@ void main() { when(client.getUTXOs(scripthash: anyNamed("scripthash"))) .thenAnswer((_) async => []); - // mock price calls - when(priceAPI.getPricesAnd24hChange(baseCurrency: "USD")).thenAnswer( - (_) async => {Coin.firo: Tuple2(Decimal.fromInt(10), 1.0)}); - final firo = FiroWallet( walletName: testWalletName, walletId: "${testWalletId}refresh", @@ -2559,7 +2399,6 @@ void main() { client: client, cachedClient: cachedClient, secureStore: secureStore, - priceAPI: priceAPI, tracker: MockTransactionNotificationTracker(), ); @@ -2584,7 +2423,7 @@ void main() { // final client = MockElectrumX(); // final cachedClient = MockCachedElectrumX(); // final secureStore = FakeSecureStorage(); - // final priceAPI = MockPriceAPI(); + // // // String expectedTxid = "-1"; // when(client.getLatestCoinId()).thenAnswer((_) async => 1); @@ -2669,7 +2508,8 @@ void main() { // client: client, // cachedClient: cachedClient, // secureStore: secureStore, - // priceAPI: priceAPI, + // + // // tracker: MockTransactionNotificationTracker(), // ); // @@ -2758,754 +2598,368 @@ void main() { // expect(result.length, 64); // }, timeout: const Timeout(Duration(minutes: 3))); - test("send fails due to insufficient balance", () async { - TestWidgetsFlutterBinding.ensureInitialized(); - const MethodChannel('uk.spiralarm.flutter/devicelocale') - .setMockMethodCallHandler((methodCall) async => 'en_US'); - - final client = MockElectrumX(); - final cachedClient = MockCachedElectrumX(); - final secureStore = FakeSecureStorage(); - final priceAPI = MockPriceAPI(); - when(client.getLatestCoinId()).thenAnswer((_) async => 1); - when(client.getBlockHeadTip()).thenAnswer( - (_) async => {"height": 459185, "hex": "... some block hex ..."}); - - when(client.broadcastTransaction(rawTx: anyNamed("rawTx"))) - .thenAnswer((realInvocation) async { - final rawTx = - realInvocation.namedArguments[const Symbol("rawTx")] as String; - final rawTxData = Format.stringToUint8List(rawTx); - - final hash = sha256 - .convert(sha256.convert(rawTxData.toList(growable: false)).bytes); - - final reversedBytes = - Uint8List.fromList(hash.bytes.reversed.toList(growable: false)); - - final txid = Format.uint8listToString(reversedBytes); - return txid; - }); - when(client.getBatchHistory(args: batchHistoryRequest0)) - .thenAnswer((realInvocation) async => batchHistoryResponse0); - - when(cachedClient.getAnonymitySet( - groupId: "1", - coin: Coin.firo, - )).thenAnswer((_) async => GetAnonymitySetSampleData.data); - - // mock price calls - when(priceAPI.getPricesAnd24hChange(baseCurrency: "USD")).thenAnswer( - (_) async => {Coin.firo: Tuple2(Decimal.fromInt(10), 1.0)}); - - // mock transaction calls - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash0, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData0); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash1, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData1); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash2, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData2); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash3, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData3); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash4, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData4); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash5, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData5); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash6, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData6); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash7, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData7); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash8, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData8); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash9, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData9); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash10, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData10); - - final firo = FiroWallet( - walletId: "${testWalletId}send", - coin: Coin.firo, - walletName: testWalletName, - client: client, - cachedClient: cachedClient, - secureStore: secureStore, - priceAPI: priceAPI, - tracker: MockTransactionNotificationTracker(), - ); - - // set mnemonic - await secureStore.write( - key: "${testWalletId}send_mnemonic", value: TEST_MNEMONIC); - - // set timer to non null so a periodic timer isn't created - firo.timer = Timer(const Duration(), () {}); - - // build sending wallet - await firo.fillAddresses(TEST_MNEMONIC); - final wallet = await Hive.openBox("${testWalletId}send"); - - final rcv = - await secureStore.read(key: "${testWalletId}send_receiveDerivations"); - final chg = - await secureStore.read(key: "${testWalletId}send_changeDerivations"); - final receiveDerivations = - Map.from(jsonDecode(rcv as String) as Map); - final changeDerivations = - Map.from(jsonDecode(chg as String) as Map); - - for (int i = 0; i < receiveDerivations.length; i++) { - final receiveHash = AddressUtils.convertToScriptHash( - receiveDerivations["$i"]!["address"] as String, firoNetwork); - final changeHash = AddressUtils.convertToScriptHash( - changeDerivations["$i"]!["address"] as String, firoNetwork); - List> data; - switch (receiveHash) { - case SampleGetHistoryData.scripthash0: - data = SampleGetHistoryData.data0; - break; - case SampleGetHistoryData.scripthash1: - data = SampleGetHistoryData.data1; - break; - case SampleGetHistoryData.scripthash2: - data = SampleGetHistoryData.data2; - break; - case SampleGetHistoryData.scripthash3: - data = SampleGetHistoryData.data3; - break; - default: - data = []; - } - when(client.getHistory(scripthash: receiveHash)) - .thenAnswer((_) async => data); - - switch (changeHash) { - case SampleGetHistoryData.scripthash0: - data = SampleGetHistoryData.data0; - break; - case SampleGetHistoryData.scripthash1: - data = SampleGetHistoryData.data1; - break; - case SampleGetHistoryData.scripthash2: - data = SampleGetHistoryData.data2; - break; - case SampleGetHistoryData.scripthash3: - data = SampleGetHistoryData.data3; - break; - default: - data = []; - } - - when(client.getHistory(scripthash: changeHash)) - .thenAnswer((_) async => data); - } - - await wallet.put('_lelantus_coins', []); - await wallet.put('jindex', []); - await wallet.put('mintIndex', 0); - await wallet.put('receivingAddresses', [ - "a8VV7vMzJdTQj1eLEJNskhLEBUxfNWhpAg", - "aPjLWDTPQsoPHUTxKBNRzoebDALj3eTcfh", - "aKmXfS7nEZdqWBGRdAXcyMoEoKhZQDPBoq" - ]); - await wallet - .put('changeAddresses', ["a5V5r6We6mNZzWJwGwEeRML3mEYLjvK39w"]); - - expect( - () async => await firo.send( - toAddress: "aHZJsucDrhr4Uzzx6XXrKnaTgLxsEAokvV", amount: 100), - throwsA(isA())); - }, timeout: const Timeout(Duration(minutes: 3))); - - test("send fails due to bad transaction created", () async { - TestWidgetsFlutterBinding.ensureInitialized(); - const MethodChannel('uk.spiralarm.flutter/devicelocale') - .setMockMethodCallHandler((methodCall) async => 'en_US'); - - final client = MockElectrumX(); - final cachedClient = MockCachedElectrumX(); - final secureStore = FakeSecureStorage(); - final priceAPI = MockPriceAPI(); - when(client.getLatestCoinId()).thenAnswer((_) async => 1); - when(client.getBlockHeadTip()).thenAnswer( - (_) async => {"height": 459185, "hex": "... some block hex ..."}); - - when(client.broadcastTransaction(rawTx: anyNamed("rawTx"))) - .thenAnswer((_) async { - return "some bad txid"; - }); - - when(client.getBatchHistory(args: batchHistoryRequest0)) - .thenAnswer((realInvocation) async => batchHistoryResponse0); - - when(cachedClient.getAnonymitySet( - groupId: "1", - coin: Coin.firo, - )).thenAnswer((_) async => GetAnonymitySetSampleData.data); - - // mock price calls - when(priceAPI.getPricesAnd24hChange(baseCurrency: "USD")).thenAnswer( - (_) async => {Coin.firo: Tuple2(Decimal.fromInt(10), 1.0)}); - - // mock transaction calls - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash0, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData0); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash1, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData1); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash2, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData2); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash3, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData3); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash4, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData4); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash5, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData5); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash6, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData6); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash7, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData7); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash8, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData8); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash9, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData9); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash10, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData10); - - final firo = FiroWallet( - walletId: "${testWalletId}send", - coin: Coin.firo, - walletName: testWalletName, - client: client, - cachedClient: cachedClient, - secureStore: secureStore, - priceAPI: priceAPI, - tracker: MockTransactionNotificationTracker(), - ); - - // set mnemonic - await secureStore.write( - key: "${testWalletId}send_mnemonic", value: TEST_MNEMONIC); - - // set timer to non null so a periodic timer isn't created - firo.timer = Timer(const Duration(), () {}); - - // build sending wallet - await firo.fillAddresses(TEST_MNEMONIC); - final wallet = await Hive.openBox("${testWalletId}send"); - - final rcv = - await secureStore.read(key: "${testWalletId}send_receiveDerivations"); - final chg = - await secureStore.read(key: "${testWalletId}send_changeDerivations"); - final receiveDerivations = - Map.from(jsonDecode(rcv as String) as Map); - final changeDerivations = - Map.from(jsonDecode(chg as String) as Map); - - for (int i = 0; i < receiveDerivations.length; i++) { - final receiveHash = AddressUtils.convertToScriptHash( - receiveDerivations["$i"]!["address"] as String, firoNetwork); - final changeHash = AddressUtils.convertToScriptHash( - changeDerivations["$i"]!["address"] as String, firoNetwork); - List> data; - switch (receiveHash) { - case SampleGetHistoryData.scripthash0: - data = SampleGetHistoryData.data0; - break; - case SampleGetHistoryData.scripthash1: - data = SampleGetHistoryData.data1; - break; - case SampleGetHistoryData.scripthash2: - data = SampleGetHistoryData.data2; - break; - case SampleGetHistoryData.scripthash3: - data = SampleGetHistoryData.data3; - break; - default: - data = []; - } - when(client.getHistory(scripthash: receiveHash)) - .thenAnswer((_) async => data); - - switch (changeHash) { - case SampleGetHistoryData.scripthash0: - data = SampleGetHistoryData.data0; - break; - case SampleGetHistoryData.scripthash1: - data = SampleGetHistoryData.data1; - break; - case SampleGetHistoryData.scripthash2: - data = SampleGetHistoryData.data2; - break; - case SampleGetHistoryData.scripthash3: - data = SampleGetHistoryData.data3; - break; - default: - data = []; - } - - when(client.getHistory(scripthash: changeHash)) - .thenAnswer((_) async => data); - } - - await wallet.put('_lelantus_coins', SampleLelantus.lelantusCoins); - await wallet.put('jindex', [2, 4, 6]); - await wallet.put('mintIndex', 8); - await wallet.put('receivingAddresses', [ - "a8VV7vMzJdTQj1eLEJNskhLEBUxfNWhpAg", - "aPjLWDTPQsoPHUTxKBNRzoebDALj3eTcfh", - "aKmXfS7nEZdqWBGRdAXcyMoEoKhZQDPBoq" - ]); - await wallet - .put('changeAddresses', ["a5V5r6We6mNZzWJwGwEeRML3mEYLjvK39w"]); - - expect( - () async => await firo.send( - toAddress: "aHZJsucDrhr4Uzzx6XXrKnaTgLxsEAokvV", amount: 100), - throwsA(isA())); - }, timeout: const Timeout(Duration(minutes: 3))); - - test("wallet balances", () async { - TestWidgetsFlutterBinding.ensureInitialized(); - const MethodChannel('uk.spiralarm.flutter/devicelocale') - .setMockMethodCallHandler((methodCall) async => 'en_US'); - - final client = MockElectrumX(); - final cachedClient = MockCachedElectrumX(); - final priceAPI = MockPriceAPI(); - // mock price calls - when(priceAPI.getPricesAnd24hChange(baseCurrency: "USD")).thenAnswer( - (_) async => {Coin.firo: Tuple2(Decimal.fromInt(10), 1.0)}); - - // mock history calls - when(client.getHistory(scripthash: SampleGetHistoryData.scripthash0)) - .thenAnswer((_) async => SampleGetHistoryData.data0); - when(client.getHistory(scripthash: SampleGetHistoryData.scripthash1)) - .thenAnswer((_) async => SampleGetHistoryData.data1); - when(client.getHistory(scripthash: SampleGetHistoryData.scripthash2)) - .thenAnswer((_) async => SampleGetHistoryData.data2); - when(client.getHistory(scripthash: SampleGetHistoryData.scripthash3)) - .thenAnswer((_) async => SampleGetHistoryData.data3); - - when(client.getBatchHistory(args: batchHistoryRequest0)) - .thenAnswer((realInvocation) async => batchHistoryResponse0); - - when(client.getBatchUTXOs(args: batchUtxoRequest)) - .thenAnswer((realInvocation) async => {}); - - // mock transaction calls - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash0, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData0); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash1, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData1); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash2, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData2); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash3, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData3); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash4, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData4); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash5, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData5); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash6, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData6); - - final firo = FiroWallet( - walletId: "${testWalletId}wallet balances", - walletName: "pendingBalance wallet name", - coin: Coin.firo, - client: client, - cachedClient: cachedClient, - secureStore: FakeSecureStorage(), - priceAPI: priceAPI, - tracker: MockTransactionNotificationTracker(), - ); - - final wallet = - await Hive.openBox("${testWalletId}wallet balances"); - await wallet.put('_lelantus_coins', SampleLelantus.lelantusCoins); - await wallet.put('jindex', [2, 4, 6]); - await wallet.put('mintIndex', 8); - await wallet.put('receivingAddresses', [ - "a8VV7vMzJdTQj1eLEJNskhLEBUxfNWhpAg", - "aPjLWDTPQsoPHUTxKBNRzoebDALj3eTcfh", - "aKmXfS7nEZdqWBGRdAXcyMoEoKhZQDPBoq", - ]); - - await wallet.put('changeAddresses', [ - "a5V5r6We6mNZzWJwGwEeRML3mEYLjvK39w", - ]); - - expect(await firo.pendingBalance, Decimal.zero); - expect(await firo.availableBalance, Decimal.parse("0.00021594")); - expect(await firo.totalBalance, Decimal.parse("0.00021594")); - }); - - test("wallet balance minus maxfee - wallet balance is zero", () async { - TestWidgetsFlutterBinding.ensureInitialized(); - const MethodChannel('uk.spiralarm.flutter/devicelocale') - .setMockMethodCallHandler((methodCall) async => 'en_US'); - final client = MockElectrumX(); - final cachedClient = MockCachedElectrumX(); - final priceAPI = MockPriceAPI(); - final secureStore = FakeSecureStorage(); - - when(client.getBatchHistory(args: batchHistoryRequest0)) - .thenAnswer((realInvocation) async => batchHistoryResponse0); - - when(client.getBatchUTXOs(args: batchUtxoRequest)) - .thenAnswer((realInvocation) async => {}); - - // mock price calls - when(priceAPI.getPricesAnd24hChange(baseCurrency: "USD")).thenAnswer( - (_) async => {Coin.firo: Tuple2(Decimal.fromInt(10), 1.0)}); - - // mock history calls - when(client.getHistory(scripthash: SampleGetHistoryData.scripthash0)) - .thenAnswer((_) async => SampleGetHistoryData.data0); - when(client.getHistory(scripthash: SampleGetHistoryData.scripthash1)) - .thenAnswer((_) async => SampleGetHistoryData.data1); - when(client.getHistory(scripthash: SampleGetHistoryData.scripthash2)) - .thenAnswer((_) async => SampleGetHistoryData.data2); - when(client.getHistory(scripthash: SampleGetHistoryData.scripthash3)) - .thenAnswer((_) async => SampleGetHistoryData.data3); - - // mock transaction calls - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash0, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData0); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash1, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData1); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash2, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData2); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash3, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData3); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash4, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData4); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash5, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData5); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash6, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData6); - - final firo = FiroWallet( - walletId: "${testWalletId}wallet balance minus maxfee", - walletName: "pendingBalance wallet name", - coin: Coin.firo, - client: client, - cachedClient: cachedClient, - secureStore: secureStore, - priceAPI: priceAPI, - tracker: MockTransactionNotificationTracker(), - ); - - await secureStore.write( - key: "${testWalletId}wallet balance minus maxfee_mnemonic", - value: TEST_MNEMONIC); - - final wallet = await Hive.openBox( - "${testWalletId}wallet balance minus maxfee"); - await wallet.put('receivingAddresses', [ - "a8VV7vMzJdTQj1eLEJNskhLEBUxfNWhpAg", - "aPjLWDTPQsoPHUTxKBNRzoebDALj3eTcfh", - "aKmXfS7nEZdqWBGRdAXcyMoEoKhZQDPBoq", - ]); - - await wallet.put('changeAddresses', [ - "a5V5r6We6mNZzWJwGwEeRML3mEYLjvK39w", - ]); - - expect(await firo.maxFee, 0); // ??? - - expect(await firo.balanceMinusMaxFee, Decimal.parse("0")); - }); - - test("wallet balance minus maxfee - wallet balance is not zero", () async { - TestWidgetsFlutterBinding.ensureInitialized(); - const MethodChannel('uk.spiralarm.flutter/devicelocale') - .setMockMethodCallHandler((methodCall) async => 'en_US'); - - final client = MockElectrumX(); - final cachedClient = MockCachedElectrumX(); - final priceAPI = MockPriceAPI(); - final secureStore = FakeSecureStorage(); - - // mock price calls - when(priceAPI.getPricesAnd24hChange(baseCurrency: "USD")).thenAnswer( - (_) async => {Coin.firo: Tuple2(Decimal.fromInt(10), 1.0)}); - - // mock history calls - when(client.getHistory(scripthash: SampleGetHistoryData.scripthash0)) - .thenAnswer((_) async => SampleGetHistoryData.data0); - when(client.getHistory(scripthash: SampleGetHistoryData.scripthash1)) - .thenAnswer((_) async => SampleGetHistoryData.data1); - when(client.getHistory(scripthash: SampleGetHistoryData.scripthash2)) - .thenAnswer((_) async => SampleGetHistoryData.data2); - when(client.getHistory(scripthash: SampleGetHistoryData.scripthash3)) - .thenAnswer((_) async => SampleGetHistoryData.data3); - - when(client.getBatchHistory(args: batchHistoryRequest0)) - .thenAnswer((realInvocation) async => batchHistoryResponse0); - - when(client.getBatchUTXOs(args: batchUtxoRequest)) - .thenAnswer((realInvocation) async => {}); - - // mock transaction calls - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash0, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData0); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash1, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData1); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash2, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData2); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash3, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData3); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash4, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData4); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash5, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData5); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash6, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData6); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash7, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData7); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash8, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData8); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash9, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData9); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash10, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData10); - - final firo = FiroWallet( - walletId: "${testWalletId}wallet balance minus maxfee", - walletName: "pendingBalance wallet name", - client: client, - coin: Coin.firo, - cachedClient: cachedClient, - secureStore: secureStore, - priceAPI: priceAPI, - tracker: MockTransactionNotificationTracker(), - ); - - await secureStore.write( - key: "${testWalletId}wallet balance minus maxfee_mnemonic", - value: TEST_MNEMONIC); - - final wallet = await Hive.openBox( - "${testWalletId}wallet balance minus maxfee"); - await wallet.put('_lelantus_coins', SampleLelantus.lelantusCoins); - await wallet.put('jindex', [2, 4, 6]); - await wallet.put('mintIndex', 8); - await wallet.put('receivingAddresses', [ - "a8VV7vMzJdTQj1eLEJNskhLEBUxfNWhpAg", - "aPjLWDTPQsoPHUTxKBNRzoebDALj3eTcfh", - "aKmXfS7nEZdqWBGRdAXcyMoEoKhZQDPBoq", - ]); - - await wallet.put('changeAddresses', [ - "a5V5r6We6mNZzWJwGwEeRML3mEYLjvK39w", - ]); - - expect(await firo.maxFee, 8914); - - expect(await firo.balanceMinusMaxFee, Decimal.parse("0.0001268")); - }); - - test("get transactionData", () async { - final client = MockElectrumX(); - final cachedClient = MockCachedElectrumX(); - final secureStore = FakeSecureStorage(); - final priceAPI = MockPriceAPI(); - - // set mnemonic - await secureStore.write( - key: "${testWalletId}transactionData_mnemonic", - value: RefreshTestParams.mnemonic); - - // mock electrumx client calls - when(client.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - - when(client.getLatestCoinId()).thenAnswer((_) async => 1); - // when(client.getCoinsForRecovery(setId: 1)) - // .thenAnswer((_) async => getCoinsForRecoveryResponse); - when(client.getUsedCoinSerials(startNumber: 0)) - .thenAnswer((_) async => GetUsedSerialsSampleData.serials); - - when(client.estimateFee(blocks: 1)) - .thenAnswer((_) async => Decimal.parse("0.00001000")); - when(client.estimateFee(blocks: 5)) - .thenAnswer((_) async => Decimal.parse("0.00001000")); - when(client.estimateFee(blocks: 20)) - .thenAnswer((_) async => Decimal.parse("0.00001000")); - - // mock history calls - when(client.getHistory(scripthash: SampleGetHistoryData.scripthash0)) - .thenAnswer((_) async => SampleGetHistoryData.data0); - when(client.getHistory(scripthash: SampleGetHistoryData.scripthash1)) - .thenAnswer((_) async => SampleGetHistoryData.data1); - when(client.getHistory(scripthash: SampleGetHistoryData.scripthash2)) - .thenAnswer((_) async => SampleGetHistoryData.data2); - when(client.getHistory(scripthash: SampleGetHistoryData.scripthash3)) - .thenAnswer((_) async => SampleGetHistoryData.data3); - - // mock transaction calls - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash0, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData0); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash1, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData1); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash2, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData2); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash3, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData3); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash4, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData4); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash5, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData5); - when(cachedClient.getTransaction( - txHash: SampleGetTransactionData.txHash6, - coin: Coin.firo, - )).thenAnswer((_) async => SampleGetTransactionData.txData6); - - // mock utxo calls - when(client.getUTXOs(scripthash: anyNamed("scripthash"))) - .thenAnswer((_) async => []); - - // mock price calls - when(priceAPI.getPricesAnd24hChange(baseCurrency: "USD")).thenAnswer( - (_) async => {Coin.firo: Tuple2(Decimal.fromInt(10), 1.0)}); - - final firo = FiroWallet( - walletName: testWalletName, - walletId: "${testWalletId}transactionData", - coin: Coin.firo, - client: client, - cachedClient: cachedClient, - secureStore: secureStore, - priceAPI: priceAPI, - tracker: MockTransactionNotificationTracker(), - ); - - final wallet = await Hive.openBox(testWalletId + "transactionData"); - await wallet.put( - 'receivingAddresses', RefreshTestParams.receivingAddresses); - await wallet.put('changeAddresses', RefreshTestParams.changeAddresses); - - final txData = await firo.transactionData; - - expect(txData, isA()); - - // kill timer and listener - await firo.exit(); - }); + // test("prepareSend fails due to insufficient balance", () async { + // TestWidgetsFlutterBinding.ensureInitialized(); + // const MethodChannel('uk.spiralarm.flutter/devicelocale') + // .setMockMethodCallHandler((methodCall) async => 'en_US'); + // + // final client = MockElectrumX(); + // final cachedClient = MockCachedElectrumX(); + // final secureStore = FakeSecureStorage(); + // + // when(client.getLatestCoinId()).thenAnswer((_) async => 1); + // when(client.getBlockHeadTip()).thenAnswer( + // (_) async => {"height": 459185, "hex": "... some block hex ..."}); + // + // when(client.broadcastTransaction(rawTx: anyNamed("rawTx"))) + // .thenAnswer((realInvocation) async { + // final rawTx = + // realInvocation.namedArguments[const Symbol("rawTx")] as String; + // final rawTxData = Format.stringToUint8List(rawTx); + // + // final hash = sha256 + // .convert(sha256.convert(rawTxData.toList(growable: false)).bytes); + // + // final reversedBytes = + // Uint8List.fromList(hash.bytes.reversed.toList(growable: false)); + // + // final txid = Format.uint8listToString(reversedBytes); + // return txid; + // }); + // when(client.getBatchHistory(args: batchHistoryRequest0)) + // .thenAnswer((realInvocation) async => batchHistoryResponse0); + // + // when(cachedClient.getAnonymitySet( + // groupId: "1", + // coin: Coin.firo, + // )).thenAnswer((_) async => GetAnonymitySetSampleData.data); + // + // // mock transaction calls + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash0, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData0); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash1, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData1); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash2, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData2); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash3, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData3); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash4, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData4); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash5, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData5); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash6, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData6); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash7, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData7); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash8, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData8); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash9, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData9); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash10, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData10); + // + // final firo = FiroWallet( + // walletId: "${testWalletId}send", + // coin: Coin.firo, + // walletName: testWalletName, + // client: client, + // cachedClient: cachedClient, + // secureStore: secureStore, + // tracker: MockTransactionNotificationTracker(), + // ); + // + // // set mnemonic + // await secureStore.write( + // key: "${testWalletId}send_mnemonic", value: TEST_MNEMONIC); + // + // // set timer to non null so a periodic timer isn't created + // firo.timer = Timer(const Duration(), () {}); + // + // // build sending wallet + // await firo.fillAddresses(TEST_MNEMONIC); + // final wallet = await Hive.openBox("${testWalletId}send"); + // + // final rcv = + // await secureStore.read(key: "${testWalletId}send_receiveDerivations"); + // final chg = + // await secureStore.read(key: "${testWalletId}send_changeDerivations"); + // final receiveDerivations = + // Map.from(jsonDecode(rcv as String) as Map); + // final changeDerivations = + // Map.from(jsonDecode(chg as String) as Map); + // + // for (int i = 0; i < receiveDerivations.length; i++) { + // final receiveHash = AddressUtils.convertToScriptHash( + // receiveDerivations["$i"]!["address"] as String, firoNetwork); + // final changeHash = AddressUtils.convertToScriptHash( + // changeDerivations["$i"]!["address"] as String, firoNetwork); + // List> data; + // switch (receiveHash) { + // case SampleGetHistoryData.scripthash0: + // data = SampleGetHistoryData.data0; + // break; + // case SampleGetHistoryData.scripthash1: + // data = SampleGetHistoryData.data1; + // break; + // case SampleGetHistoryData.scripthash2: + // data = SampleGetHistoryData.data2; + // break; + // case SampleGetHistoryData.scripthash3: + // data = SampleGetHistoryData.data3; + // break; + // default: + // data = []; + // } + // when(client.getHistory(scripthash: receiveHash)) + // .thenAnswer((_) async => data); + // + // switch (changeHash) { + // case SampleGetHistoryData.scripthash0: + // data = SampleGetHistoryData.data0; + // break; + // case SampleGetHistoryData.scripthash1: + // data = SampleGetHistoryData.data1; + // break; + // case SampleGetHistoryData.scripthash2: + // data = SampleGetHistoryData.data2; + // break; + // case SampleGetHistoryData.scripthash3: + // data = SampleGetHistoryData.data3; + // break; + // default: + // data = []; + // } + // + // when(client.getHistory(scripthash: changeHash)) + // .thenAnswer((_) async => data); + // } + // + // await wallet.put('_lelantus_coins', []); + // await wallet.put('jindex', []); + // await wallet.put('mintIndex', 0); + // await wallet.put('receivingAddresses', [ + // "a8VV7vMzJdTQj1eLEJNskhLEBUxfNWhpAg", + // "aPjLWDTPQsoPHUTxKBNRzoebDALj3eTcfh", + // "aKmXfS7nEZdqWBGRdAXcyMoEoKhZQDPBoq" + // ]); + // await wallet + // .put('changeAddresses', ["a5V5r6We6mNZzWJwGwEeRML3mEYLjvK39w"]); + // + // expect( + // () async => await firo.prepareSend( + // address: "aHZJsucDrhr4Uzzx6XXrKnaTgLxsEAokvV", + // satoshiAmount: 100), + // throwsA(isA())); + // }, timeout: const Timeout(Duration(minutes: 3))); + + // test("wallet balances", () async { + // TestWidgetsFlutterBinding.ensureInitialized(); + // const MethodChannel('uk.spiralarm.flutter/devicelocale') + // .setMockMethodCallHandler((methodCall) async => 'en_US'); + // + // final client = MockElectrumX(); + // final cachedClient = MockCachedElectrumX(); + // + // // mock history calls + // when(client.getHistory(scripthash: SampleGetHistoryData.scripthash0)) + // .thenAnswer((_) async => SampleGetHistoryData.data0); + // when(client.getHistory(scripthash: SampleGetHistoryData.scripthash1)) + // .thenAnswer((_) async => SampleGetHistoryData.data1); + // when(client.getHistory(scripthash: SampleGetHistoryData.scripthash2)) + // .thenAnswer((_) async => SampleGetHistoryData.data2); + // when(client.getHistory(scripthash: SampleGetHistoryData.scripthash3)) + // .thenAnswer((_) async => SampleGetHistoryData.data3); + // + // when(client.getBatchHistory(args: batchHistoryRequest0)) + // .thenAnswer((realInvocation) async => batchHistoryResponse0); + // + // when(client.getBatchUTXOs(args: batchUtxoRequest)) + // .thenAnswer((realInvocation) async => {}); + // + // // mock transaction calls + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash0, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData0); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash1, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData1); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash2, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData2); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash3, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData3); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash4, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData4); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash5, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData5); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash6, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData6); + // + // final firo = FiroWallet( + // walletId: "${testWalletId}wallet balances", + // walletName: "pendingBalance wallet name", + // coin: Coin.firo, + // client: client, + // cachedClient: cachedClient, + // secureStore: FakeSecureStorage(), + // tracker: MockTransactionNotificationTracker(), + // ); + // + // final wallet = + // await Hive.openBox("${testWalletId}wallet balances"); + // await wallet.put('_lelantus_coins', SampleLelantus.lelantusCoins); + // await wallet.put('jindex', [2, 4, 6]); + // await wallet.put('mintIndex', 8); + // await wallet.put('receivingAddresses', [ + // "a8VV7vMzJdTQj1eLEJNskhLEBUxfNWhpAg", + // "aPjLWDTPQsoPHUTxKBNRzoebDALj3eTcfh", + // "aKmXfS7nEZdqWBGRdAXcyMoEoKhZQDPBoq", + // ]); + // + // await wallet.put('changeAddresses', [ + // "a5V5r6We6mNZzWJwGwEeRML3mEYLjvK39w", + // ]); + // + // expect(firo.balance.getPending(), Decimal.zero); + // expect(firo.balance.getSpendable(), Decimal.parse("0.00021594")); + // expect(firo.balance.getTotal(), Decimal.parse("0.00021594")); + // }); + + // test("get transactions", () async { + // final client = MockElectrumX(); + // final cachedClient = MockCachedElectrumX(); + // final secureStore = FakeSecureStorage(); + // + // // set mnemonic + // await secureStore.write( + // key: "${testWalletId}transactionData_mnemonic", + // value: RefreshTestParams.mnemonic); + // + // // mock electrumx client calls + // when(client.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // + // when(client.getLatestCoinId()).thenAnswer((_) async => 1); + // // when(client.getCoinsForRecovery(setId: 1)) + // // .thenAnswer((_) async => getCoinsForRecoveryResponse); + // when(client.getUsedCoinSerials(startNumber: 0)) + // .thenAnswer((_) async => GetUsedSerialsSampleData.serials); + // + // when(client.estimateFee(blocks: 1)) + // .thenAnswer((_) async => Decimal.parse("0.00001000")); + // when(client.estimateFee(blocks: 5)) + // .thenAnswer((_) async => Decimal.parse("0.00001000")); + // when(client.estimateFee(blocks: 20)) + // .thenAnswer((_) async => Decimal.parse("0.00001000")); + // + // // mock history calls + // when(client.getHistory(scripthash: SampleGetHistoryData.scripthash0)) + // .thenAnswer((_) async => SampleGetHistoryData.data0); + // when(client.getHistory(scripthash: SampleGetHistoryData.scripthash1)) + // .thenAnswer((_) async => SampleGetHistoryData.data1); + // when(client.getHistory(scripthash: SampleGetHistoryData.scripthash2)) + // .thenAnswer((_) async => SampleGetHistoryData.data2); + // when(client.getHistory(scripthash: SampleGetHistoryData.scripthash3)) + // .thenAnswer((_) async => SampleGetHistoryData.data3); + // + // // mock transaction calls + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash0, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData0); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash1, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData1); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash2, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData2); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash3, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData3); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash4, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData4); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash5, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData5); + // when(cachedClient.getTransaction( + // txHash: SampleGetTransactionData.txHash6, + // coin: Coin.firo, + // )).thenAnswer((_) async => SampleGetTransactionData.txData6); + // + // // mock utxo calls + // when(client.getUTXOs(scripthash: anyNamed("scripthash"))) + // .thenAnswer((_) async => []); + // + // final firo = FiroWallet( + // walletName: testWalletName, + // walletId: "${testWalletId}transactionData", + // coin: Coin.firo, + // client: client, + // cachedClient: cachedClient, + // secureStore: secureStore, + // tracker: MockTransactionNotificationTracker(), + // ); + // + // final wallet = + // await Hive.openBox("${testWalletId}transactionData"); + // await wallet.put( + // 'receivingAddresses', RefreshTestParams.receivingAddresses); + // await wallet.put('changeAddresses', RefreshTestParams.changeAddresses); + // + // final txData = await firo.transactions; + // + // expect(txData, isA>()); + // + // // kill timer and listener + // await firo.exit(); + // }); // test("autoMint", () async { // TestWidgetsFlutterBinding.ensureInitialized(); @@ -3515,18 +2969,18 @@ void main() { // final client = MockElectrumX(); // final cachedClient = MockCachedElectrumX(); // final secureStore = FakeSecureStorage(); - // final priceAPI = MockPriceAPI(); + // // // // mock electrumx client calls // when(client.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_MAINNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // // when(client.getBlockHeadTip()).thenAnswer( @@ -3634,7 +3088,8 @@ void main() { // client: client, // cachedClient: cachedClient, // secureStore: secureStore, - // priceAPI: priceAPI, + // + // // tracker: MockTransactionNotificationTracker(), // ); // @@ -3734,7 +3189,6 @@ void main() { client: MockElectrumX(), cachedClient: MockCachedElectrumX(), secureStore: FakeSecureStorage(), - priceAPI: MockPriceAPI(), tracker: MockTransactionNotificationTracker(), ); @@ -3774,7 +3228,6 @@ void main() { client: client, cachedClient: MockCachedElectrumX(), secureStore: FakeSecureStorage(), - priceAPI: MockPriceAPI(), tracker: MockTransactionNotificationTracker(), ); @@ -3796,7 +3249,6 @@ void main() { client: client, cachedClient: MockCachedElectrumX(), secureStore: FakeSecureStorage(), - priceAPI: MockPriceAPI(), tracker: MockTransactionNotificationTracker(), ); @@ -3813,7 +3265,6 @@ void main() { client: MockElectrumX(), cachedClient: MockCachedElectrumX(), secureStore: FakeSecureStorage(), - priceAPI: MockPriceAPI(), tracker: MockTransactionNotificationTracker(), ); @@ -3828,7 +3279,6 @@ void main() { client: MockElectrumX(), cachedClient: MockCachedElectrumX(), secureStore: FakeSecureStorage(), - priceAPI: MockPriceAPI(), tracker: MockTransactionNotificationTracker(), ); @@ -3840,7 +3290,7 @@ void main() { test("fetch and convert properly stored mnemonic to list of words", () async { final store = FakeSecureStorage(); - store.write( + await store.write( key: "some id_mnemonic", value: "some test mnemonic string of words"); @@ -3851,7 +3301,6 @@ void main() { client: MockElectrumX(), cachedClient: MockCachedElectrumX(), secureStore: store, - priceAPI: MockPriceAPI(), tracker: MockTransactionNotificationTracker(), ); final List result = await firo.mnemonic; @@ -3869,7 +3318,7 @@ void main() { test("attempt fetch and convert non existent mnemonic to list of words", () async { final store = FakeSecureStorage(); - store.write( + await store.write( key: "some id_mnemonic", value: "some test mnemonic string of words"); @@ -3880,7 +3329,6 @@ void main() { client: MockElectrumX(), cachedClient: MockCachedElectrumX(), secureStore: store, - priceAPI: MockPriceAPI(), tracker: MockTransactionNotificationTracker(), ); final mnemonic = await firo.mnemonic; @@ -3896,7 +3344,6 @@ void main() { client: MockElectrumX(), cachedClient: MockCachedElectrumX(), secureStore: FakeSecureStorage(), - priceAPI: MockPriceAPI(), tracker: MockTransactionNotificationTracker(), ); @@ -3914,7 +3361,6 @@ void main() { client: MockElectrumX(), cachedClient: MockCachedElectrumX(), secureStore: FakeSecureStorage(), - priceAPI: MockPriceAPI(), tracker: MockTransactionNotificationTracker(), ); diff --git a/test/services/coins/firo/firo_wallet_test.mocks.dart b/test/services/coins/firo/firo_wallet_test.mocks.dart index 2d32cef48..bb6d13ca7 100644 --- a/test/services/coins/firo/firo_wallet_test.mocks.dart +++ b/test/services/coins/firo/firo_wallet_test.mocks.dart @@ -6,16 +6,19 @@ import 'dart:async' as _i6; import 'package:decimal/decimal.dart' as _i2; -import 'package:http/http.dart' as _i4; +import 'package:isar/isar.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/db/isar/main_db.dart' as _i10; import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i7; import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i5; -import 'package:stackwallet/services/price.dart' as _i9; +import 'package:stackwallet/models/isar/models/block_explorer.dart' as _i12; +import 'package:stackwallet/models/isar/models/contact_entry.dart' as _i11; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i13; import 'package:stackwallet/services/transaction_notification_tracker.dart' - as _i11; + as _i9; import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i8; import 'package:stackwallet/utilities/prefs.dart' as _i3; -import 'package:tuple/tuple.dart' as _i10; +import 'package:tuple/tuple.dart' as _i14; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -48,8 +51,19 @@ class _FakePrefs_1 extends _i1.SmartFake implements _i3.Prefs { ); } -class _FakeClient_2 extends _i1.SmartFake implements _i4.Client { - _FakeClient_2( +class _FakeIsar_2 extends _i1.SmartFake implements _i4.Isar { + _FakeIsar_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeQueryBuilder_3 extends _i1.SmartFake + implements _i4.QueryBuilder { + _FakeQueryBuilder_3( Object parent, Invocation parentInvocation, ) : super( @@ -500,7 +514,7 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { _i6.Future>.value({}), ) as _i6.Future>); @override - _i6.Future> getUsedCoinSerials({ + _i6.Future> getUsedCoinSerials({ required _i8.Coin? coin, int? startNumber = 0, }) => @@ -513,8 +527,8 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { #startNumber: startNumber, }, ), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i6.Future>.value([]), + ) as _i6.Future>); @override _i6.Future clearSharedTransactionCache({required _i8.Coin? coin}) => (super.noSuchMethod( @@ -528,51 +542,11 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { ) as _i6.Future); } -/// A class which mocks [PriceAPI]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockPriceAPI extends _i1.Mock implements _i9.PriceAPI { - MockPriceAPI() { - _i1.throwOnMissingStub(this); - } - - @override - _i4.Client get client => (super.noSuchMethod( - Invocation.getter(#client), - returnValue: _FakeClient_2( - this, - Invocation.getter(#client), - ), - ) as _i4.Client); - @override - void resetLastCalledToForceNextCallToUpdateCache() => super.noSuchMethod( - Invocation.method( - #resetLastCalledToForceNextCallToUpdateCache, - [], - ), - returnValueForMissingStub: null, - ); - @override - _i6.Future< - Map<_i8.Coin, _i10.Tuple2<_i2.Decimal, double>>> getPricesAnd24hChange( - {required String? baseCurrency}) => - (super.noSuchMethod( - Invocation.method( - #getPricesAnd24hChange, - [], - {#baseCurrency: baseCurrency}, - ), - returnValue: - _i6.Future>>.value( - <_i8.Coin, _i10.Tuple2<_i2.Decimal, double>>{}), - ) as _i6.Future>>); -} - /// A class which mocks [TransactionNotificationTracker]. /// /// See the documentation for Mockito's code generation for more information. class MockTransactionNotificationTracker extends _i1.Mock - implements _i11.TransactionNotificationTracker { + implements _i9.TransactionNotificationTracker { MockTransactionNotificationTracker() { _i1.throwOnMissingStub(this); } @@ -626,4 +600,574 @@ class MockTransactionNotificationTracker extends _i1.Mock returnValue: _i6.Future.value(), returnValueForMissingStub: _i6.Future.value(), ) as _i6.Future); + @override + _i6.Future deleteTransaction(String? txid) => (super.noSuchMethod( + Invocation.method( + #deleteTransaction, + [txid], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); +} + +/// A class which mocks [MainDB]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockMainDB extends _i1.Mock implements _i10.MainDB { + MockMainDB() { + _i1.throwOnMissingStub(this); + } + + @override + _i4.Isar get isar => (super.noSuchMethod( + Invocation.getter(#isar), + returnValue: _FakeIsar_2( + this, + Invocation.getter(#isar), + ), + ) as _i4.Isar); + @override + _i6.Future initMainDB({_i4.Isar? mock}) => (super.noSuchMethod( + Invocation.method( + #initMainDB, + [], + {#mock: mock}, + ), + returnValue: _i6.Future.value(false), + ) as _i6.Future); + @override + List<_i11.ContactEntry> getContactEntries() => (super.noSuchMethod( + Invocation.method( + #getContactEntries, + [], + ), + returnValue: <_i11.ContactEntry>[], + ) as List<_i11.ContactEntry>); + @override + _i6.Future deleteContactEntry({required String? id}) => + (super.noSuchMethod( + Invocation.method( + #deleteContactEntry, + [], + {#id: id}, + ), + returnValue: _i6.Future.value(false), + ) as _i6.Future); + @override + _i6.Future isContactEntryExists({required String? id}) => + (super.noSuchMethod( + Invocation.method( + #isContactEntryExists, + [], + {#id: id}, + ), + returnValue: _i6.Future.value(false), + ) as _i6.Future); + @override + _i11.ContactEntry? getContactEntry({required String? id}) => + (super.noSuchMethod(Invocation.method( + #getContactEntry, + [], + {#id: id}, + )) as _i11.ContactEntry?); + @override + _i6.Future putContactEntry( + {required _i11.ContactEntry? contactEntry}) => + (super.noSuchMethod( + Invocation.method( + #putContactEntry, + [], + {#contactEntry: contactEntry}, + ), + returnValue: _i6.Future.value(false), + ) as _i6.Future); + @override + _i12.TransactionBlockExplorer? getTransactionBlockExplorer( + {required _i8.Coin? coin}) => + (super.noSuchMethod(Invocation.method( + #getTransactionBlockExplorer, + [], + {#coin: coin}, + )) as _i12.TransactionBlockExplorer?); + @override + _i6.Future putTransactionBlockExplorer( + _i12.TransactionBlockExplorer? explorer) => + (super.noSuchMethod( + Invocation.method( + #putTransactionBlockExplorer, + [explorer], + ), + returnValue: _i6.Future.value(0), + ) as _i6.Future); + @override + _i4.QueryBuilder<_i13.Address, _i13.Address, _i4.QAfterWhereClause> + getAddresses(String? walletId) => (super.noSuchMethod( + Invocation.method( + #getAddresses, + [walletId], + ), + returnValue: _FakeQueryBuilder_3<_i13.Address, _i13.Address, + _i4.QAfterWhereClause>( + this, + Invocation.method( + #getAddresses, + [walletId], + ), + ), + ) as _i4 + .QueryBuilder<_i13.Address, _i13.Address, _i4.QAfterWhereClause>); + @override + _i6.Future putAddress(_i13.Address? address) => (super.noSuchMethod( + Invocation.method( + #putAddress, + [address], + ), + returnValue: _i6.Future.value(0), + ) as _i6.Future); + @override + _i6.Future> putAddresses(List<_i13.Address>? addresses) => + (super.noSuchMethod( + Invocation.method( + #putAddresses, + [addresses], + ), + returnValue: _i6.Future>.value([]), + ) as _i6.Future>); + @override + _i6.Future> updateOrPutAddresses(List<_i13.Address>? addresses) => + (super.noSuchMethod( + Invocation.method( + #updateOrPutAddresses, + [addresses], + ), + returnValue: _i6.Future>.value([]), + ) as _i6.Future>); + @override + _i6.Future<_i13.Address?> getAddress( + String? walletId, + String? address, + ) => + (super.noSuchMethod( + Invocation.method( + #getAddress, + [ + walletId, + address, + ], + ), + returnValue: _i6.Future<_i13.Address?>.value(), + ) as _i6.Future<_i13.Address?>); + @override + _i6.Future updateAddress( + _i13.Address? oldAddress, + _i13.Address? newAddress, + ) => + (super.noSuchMethod( + Invocation.method( + #updateAddress, + [ + oldAddress, + newAddress, + ], + ), + returnValue: _i6.Future.value(0), + ) as _i6.Future); + @override + _i4.QueryBuilder<_i13.Transaction, _i13.Transaction, _i4.QAfterWhereClause> + getTransactions(String? walletId) => (super.noSuchMethod( + Invocation.method( + #getTransactions, + [walletId], + ), + returnValue: _FakeQueryBuilder_3<_i13.Transaction, _i13.Transaction, + _i4.QAfterWhereClause>( + this, + Invocation.method( + #getTransactions, + [walletId], + ), + ), + ) as _i4.QueryBuilder<_i13.Transaction, _i13.Transaction, + _i4.QAfterWhereClause>); + @override + _i6.Future putTransaction(_i13.Transaction? transaction) => + (super.noSuchMethod( + Invocation.method( + #putTransaction, + [transaction], + ), + returnValue: _i6.Future.value(0), + ) as _i6.Future); + @override + _i6.Future> putTransactions(List<_i13.Transaction>? transactions) => + (super.noSuchMethod( + Invocation.method( + #putTransactions, + [transactions], + ), + returnValue: _i6.Future>.value([]), + ) as _i6.Future>); + @override + _i6.Future<_i13.Transaction?> getTransaction( + String? walletId, + String? txid, + ) => + (super.noSuchMethod( + Invocation.method( + #getTransaction, + [ + walletId, + txid, + ], + ), + returnValue: _i6.Future<_i13.Transaction?>.value(), + ) as _i6.Future<_i13.Transaction?>); + @override + _i6.Stream<_i13.Transaction?> watchTransaction({ + required int? id, + bool? fireImmediately = false, + }) => + (super.noSuchMethod( + Invocation.method( + #watchTransaction, + [], + { + #id: id, + #fireImmediately: fireImmediately, + }, + ), + returnValue: _i6.Stream<_i13.Transaction?>.empty(), + ) as _i6.Stream<_i13.Transaction?>); + @override + _i4.QueryBuilder<_i13.UTXO, _i13.UTXO, _i4.QAfterWhereClause> getUTXOs( + String? walletId) => + (super.noSuchMethod( + Invocation.method( + #getUTXOs, + [walletId], + ), + returnValue: + _FakeQueryBuilder_3<_i13.UTXO, _i13.UTXO, _i4.QAfterWhereClause>( + this, + Invocation.method( + #getUTXOs, + [walletId], + ), + ), + ) as _i4.QueryBuilder<_i13.UTXO, _i13.UTXO, _i4.QAfterWhereClause>); + @override + _i6.Future putUTXO(_i13.UTXO? utxo) => (super.noSuchMethod( + Invocation.method( + #putUTXO, + [utxo], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future putUTXOs(List<_i13.UTXO>? utxos) => (super.noSuchMethod( + Invocation.method( + #putUTXOs, + [utxos], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future updateUTXOs( + String? walletId, + List<_i13.UTXO>? utxos, + ) => + (super.noSuchMethod( + Invocation.method( + #updateUTXOs, + [ + walletId, + utxos, + ], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Stream<_i13.UTXO?> watchUTXO({ + required int? id, + bool? fireImmediately = false, + }) => + (super.noSuchMethod( + Invocation.method( + #watchUTXO, + [], + { + #id: id, + #fireImmediately: fireImmediately, + }, + ), + returnValue: _i6.Stream<_i13.UTXO?>.empty(), + ) as _i6.Stream<_i13.UTXO?>); + @override + _i4.QueryBuilder<_i13.TransactionNote, _i13.TransactionNote, + _i4.QAfterWhereClause> getTransactionNotes( + String? walletId) => + (super.noSuchMethod( + Invocation.method( + #getTransactionNotes, + [walletId], + ), + returnValue: _FakeQueryBuilder_3<_i13.TransactionNote, + _i13.TransactionNote, _i4.QAfterWhereClause>( + this, + Invocation.method( + #getTransactionNotes, + [walletId], + ), + ), + ) as _i4.QueryBuilder<_i13.TransactionNote, _i13.TransactionNote, + _i4.QAfterWhereClause>); + @override + _i6.Future putTransactionNote(_i13.TransactionNote? transactionNote) => + (super.noSuchMethod( + Invocation.method( + #putTransactionNote, + [transactionNote], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future putTransactionNotes( + List<_i13.TransactionNote>? transactionNotes) => + (super.noSuchMethod( + Invocation.method( + #putTransactionNotes, + [transactionNotes], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future<_i13.TransactionNote?> getTransactionNote( + String? walletId, + String? txid, + ) => + (super.noSuchMethod( + Invocation.method( + #getTransactionNote, + [ + walletId, + txid, + ], + ), + returnValue: _i6.Future<_i13.TransactionNote?>.value(), + ) as _i6.Future<_i13.TransactionNote?>); + @override + _i6.Stream<_i13.TransactionNote?> watchTransactionNote({ + required int? id, + bool? fireImmediately = false, + }) => + (super.noSuchMethod( + Invocation.method( + #watchTransactionNote, + [], + { + #id: id, + #fireImmediately: fireImmediately, + }, + ), + returnValue: _i6.Stream<_i13.TransactionNote?>.empty(), + ) as _i6.Stream<_i13.TransactionNote?>); + @override + _i4.QueryBuilder<_i13.AddressLabel, _i13.AddressLabel, _i4.QAfterWhereClause> + getAddressLabels(String? walletId) => (super.noSuchMethod( + Invocation.method( + #getAddressLabels, + [walletId], + ), + returnValue: _FakeQueryBuilder_3<_i13.AddressLabel, + _i13.AddressLabel, _i4.QAfterWhereClause>( + this, + Invocation.method( + #getAddressLabels, + [walletId], + ), + ), + ) as _i4.QueryBuilder<_i13.AddressLabel, _i13.AddressLabel, + _i4.QAfterWhereClause>); + @override + _i6.Future putAddressLabel(_i13.AddressLabel? addressLabel) => + (super.noSuchMethod( + Invocation.method( + #putAddressLabel, + [addressLabel], + ), + returnValue: _i6.Future.value(0), + ) as _i6.Future); + @override + int putAddressLabelSync(_i13.AddressLabel? addressLabel) => + (super.noSuchMethod( + Invocation.method( + #putAddressLabelSync, + [addressLabel], + ), + returnValue: 0, + ) as int); + @override + _i6.Future putAddressLabels(List<_i13.AddressLabel>? addressLabels) => + (super.noSuchMethod( + Invocation.method( + #putAddressLabels, + [addressLabels], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future<_i13.AddressLabel?> getAddressLabel( + String? walletId, + String? addressString, + ) => + (super.noSuchMethod( + Invocation.method( + #getAddressLabel, + [ + walletId, + addressString, + ], + ), + returnValue: _i6.Future<_i13.AddressLabel?>.value(), + ) as _i6.Future<_i13.AddressLabel?>); + @override + _i13.AddressLabel? getAddressLabelSync( + String? walletId, + String? addressString, + ) => + (super.noSuchMethod(Invocation.method( + #getAddressLabelSync, + [ + walletId, + addressString, + ], + )) as _i13.AddressLabel?); + @override + _i6.Stream<_i13.AddressLabel?> watchAddressLabel({ + required int? id, + bool? fireImmediately = false, + }) => + (super.noSuchMethod( + Invocation.method( + #watchAddressLabel, + [], + { + #id: id, + #fireImmediately: fireImmediately, + }, + ), + returnValue: _i6.Stream<_i13.AddressLabel?>.empty(), + ) as _i6.Stream<_i13.AddressLabel?>); + @override + _i6.Future updateAddressLabel(_i13.AddressLabel? addressLabel) => + (super.noSuchMethod( + Invocation.method( + #updateAddressLabel, + [addressLabel], + ), + returnValue: _i6.Future.value(0), + ) as _i6.Future); + @override + _i6.Future deleteWalletBlockchainData(String? walletId) => + (super.noSuchMethod( + Invocation.method( + #deleteWalletBlockchainData, + [walletId], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future deleteAddressLabels(String? walletId) => (super.noSuchMethod( + Invocation.method( + #deleteAddressLabels, + [walletId], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future deleteTransactionNotes(String? walletId) => + (super.noSuchMethod( + Invocation.method( + #deleteTransactionNotes, + [walletId], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i6.Future addNewTransactionData( + List<_i14.Tuple2<_i13.Transaction, _i13.Address?>>? transactionsData, + String? walletId, + ) => + (super.noSuchMethod( + Invocation.method( + #addNewTransactionData, + [ + transactionsData, + walletId, + ], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); + @override + _i4.QueryBuilder<_i13.EthContract, _i13.EthContract, _i4.QWhere> + getEthContracts() => (super.noSuchMethod( + Invocation.method( + #getEthContracts, + [], + ), + returnValue: _FakeQueryBuilder_3<_i13.EthContract, _i13.EthContract, + _i4.QWhere>( + this, + Invocation.method( + #getEthContracts, + [], + ), + ), + ) as _i4 + .QueryBuilder<_i13.EthContract, _i13.EthContract, _i4.QWhere>); + @override + _i6.Future<_i13.EthContract?> getEthContract(String? contractAddress) => + (super.noSuchMethod( + Invocation.method( + #getEthContract, + [contractAddress], + ), + returnValue: _i6.Future<_i13.EthContract?>.value(), + ) as _i6.Future<_i13.EthContract?>); + @override + _i13.EthContract? getEthContractSync(String? contractAddress) => + (super.noSuchMethod(Invocation.method( + #getEthContractSync, + [contractAddress], + )) as _i13.EthContract?); + @override + _i6.Future putEthContract(_i13.EthContract? contract) => + (super.noSuchMethod( + Invocation.method( + #putEthContract, + [contract], + ), + returnValue: _i6.Future.value(0), + ) as _i6.Future); + @override + _i6.Future putEthContracts(List<_i13.EthContract>? contracts) => + (super.noSuchMethod( + Invocation.method( + #putEthContracts, + [contracts], + ), + returnValue: _i6.Future.value(), + returnValueForMissingStub: _i6.Future.value(), + ) as _i6.Future); } diff --git a/test/services/coins/manager_test.dart b/test/services/coins/manager_test.dart index 60cab37c0..65a2de918 100644 --- a/test/services/coins/manager_test.dart +++ b/test/services/coins/manager_test.dart @@ -3,15 +3,24 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; -import 'package:stackwallet/models/models.dart'; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/isar/models/isar_models.dart'; +import 'package:stackwallet/models/paymint/fee_object_model.dart'; import 'package:stackwallet/services/coins/coin_service.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; import 'package:stackwallet/services/coins/manager.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'firo/sample_data/transaction_data_samples.dart'; import 'manager_test.mocks.dart'; +/// quick amount constructor wrapper. Using an int is bad practice but for +/// testing with small amounts this should be fine +Amount _a(int i) => Amount.fromDecimal( + Decimal.fromInt(i), + fractionDigits: 8, + ); + @GenerateMocks([FiroWallet, ElectrumX]) void main() { test("Manager should have a backgroundRefreshListener on initialization", () { @@ -28,30 +37,6 @@ void main() { expect(manager.coin, Coin.firo); }); - group("send", () { - test("successful send", () async { - final CoinServiceAPI wallet = MockFiroWallet(); - when(wallet.send(toAddress: "some address", amount: 1987634)) - .thenAnswer((_) async => "some txid"); - - final manager = Manager(wallet); - - expect(await manager.send(toAddress: "some address", amount: 1987634), - "some txid"); - }); - - test("failed send", () { - final CoinServiceAPI wallet = MockFiroWallet(); - when(wallet.send(toAddress: "some address", amount: 1987634)) - .thenThrow(Exception("Tx failed!")); - - final manager = Manager(wallet); - - expect(() => manager.send(toAddress: "some address", amount: 1987634), - throwsA(isA())); - }); - }); - test("fees", () async { final CoinServiceAPI wallet = MockFiroWallet(); when(wallet.fees).thenAnswer((_) async => FeeObject( @@ -98,68 +83,61 @@ void main() { group("get balances", () { test("balance", () async { final CoinServiceAPI wallet = MockFiroWallet(); - when(wallet.availableBalance).thenAnswer((_) async => Decimal.ten); + final balance = Balance( + total: _a(10), + spendable: _a(1), + blockedTotal: _a(0), + pendingSpendable: _a(9), + ); + + when(wallet.coin).thenAnswer((_) => Coin.firo); + when(wallet.balance).thenAnswer( + (_) => balance, + ); final manager = Manager(wallet); - expect(await manager.availableBalance, Decimal.ten); - }); - - test("pendingBalance", () async { - final CoinServiceAPI wallet = MockFiroWallet(); - when(wallet.pendingBalance).thenAnswer((_) async => Decimal.fromInt(23)); - - final manager = Manager(wallet); - - expect(await manager.pendingBalance, Decimal.fromInt(23)); - }); - - test("totalBalance", () async { - final wallet = MockFiroWallet(); - when(wallet.totalBalance).thenAnswer((_) async => Decimal.fromInt(2)); - - final manager = Manager(wallet); - - expect(await manager.totalBalance, Decimal.fromInt(2)); - }); - - test("balanceMinusMaxFee", () async { - final CoinServiceAPI wallet = MockFiroWallet(); - when(wallet.balanceMinusMaxFee).thenAnswer((_) async => Decimal.one); - - final manager = Manager(wallet); - - expect(await manager.balanceMinusMaxFee, Decimal.one); + expect(manager.balance, balance); }); }); - test("allOwnAddresses", () async { + test("transactions", () async { final CoinServiceAPI wallet = MockFiroWallet(); - when(wallet.allOwnAddresses) - .thenAnswer((_) async => ["address1", "address2", "address3"]); + + when(wallet.coin).thenAnswer((realInvocation) => Coin.firo); + + final tx = Transaction( + walletId: "walletId", + txid: "txid", + timestamp: 6, + type: TransactionType.incoming, + subType: TransactionSubType.mint, + amount: 123, + amountString: Amount( + rawValue: BigInt.from(123), + fractionDigits: wallet.coin.decimals, + ).toJsonString(), + fee: 3, + height: 123, + isCancelled: false, + isLelantus: true, + slateId: null, + otherData: null, + nonce: null, + inputs: [], + outputs: [], + ); + when(wallet.transactions).thenAnswer((_) async => [ + tx, + ]); final manager = Manager(wallet); - expect(await manager.allOwnAddresses, ["address1", "address2", "address3"]); - }); + final result = await manager.transactions; - test("transactionData", () async { - final CoinServiceAPI wallet = MockFiroWallet(); - when(wallet.transactionData) - .thenAnswer((_) async => TransactionData.fromJson(dateTimeChunksJson)); + expect(result.length, 1); - final manager = Manager(wallet); - - final expectedMap = - TransactionData.fromJson(dateTimeChunksJson).getAllTransactions(); - final result = (await manager.transactionData).getAllTransactions(); - - expect(result.length, expectedMap.length); - - for (int i = 0; i < expectedMap.length; i++) { - final resultTxid = result.keys.toList(growable: false)[i]; - expect(result[resultTxid].toString(), expectedMap[resultTxid].toString()); - } + expect(result.first, tx); }); test("refresh", () async { diff --git a/test/services/coins/manager_test.mocks.dart b/test/services/coins/manager_test.mocks.dart index 9a958702f..8a607f14f 100644 --- a/test/services/coins/manager_test.mocks.dart +++ b/test/services/coins/manager_test.mocks.dart @@ -3,18 +3,23 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i8; +import 'dart:async' as _i11; -import 'package:decimal/decimal.dart' as _i3; +import 'package:decimal/decimal.dart' as _i9; import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i6; -import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i5; -import 'package:stackwallet/models/lelantus_coin.dart' as _i10; -import 'package:stackwallet/models/models.dart' as _i4; -import 'package:stackwallet/services/coins/firo/firo_wallet.dart' as _i7; +import 'package:stackwallet/db/isar/main_db.dart' as _i7; +import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i5; +import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i4; +import 'package:stackwallet/models/balance.dart' as _i6; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i13; +import 'package:stackwallet/models/lelantus_coin.dart' as _i15; +import 'package:stackwallet/models/paymint/fee_object_model.dart' as _i3; +import 'package:stackwallet/models/signing_data.dart' as _i14; +import 'package:stackwallet/services/coins/firo/firo_wallet.dart' as _i10; import 'package:stackwallet/services/transaction_notification_tracker.dart' as _i2; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i9; +import 'package:stackwallet/utilities/amount/amount.dart' as _i8; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i12; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -38,8 +43,8 @@ class _FakeTransactionNotificationTracker_0 extends _i1.SmartFake ); } -class _FakeDecimal_1 extends _i1.SmartFake implements _i3.Decimal { - _FakeDecimal_1( +class _FakeFeeObject_1 extends _i1.SmartFake implements _i3.FeeObject { + _FakeFeeObject_1( Object parent, Invocation parentInvocation, ) : super( @@ -48,9 +53,8 @@ class _FakeDecimal_1 extends _i1.SmartFake implements _i3.Decimal { ); } -class _FakeTransactionData_2 extends _i1.SmartFake - implements _i4.TransactionData { - _FakeTransactionData_2( +class _FakeElectrumX_2 extends _i1.SmartFake implements _i4.ElectrumX { + _FakeElectrumX_2( Object parent, Invocation parentInvocation, ) : super( @@ -59,8 +63,9 @@ class _FakeTransactionData_2 extends _i1.SmartFake ); } -class _FakeUtxoData_3 extends _i1.SmartFake implements _i4.UtxoData { - _FakeUtxoData_3( +class _FakeCachedElectrumX_3 extends _i1.SmartFake + implements _i5.CachedElectrumX { + _FakeCachedElectrumX_3( Object parent, Invocation parentInvocation, ) : super( @@ -69,8 +74,8 @@ class _FakeUtxoData_3 extends _i1.SmartFake implements _i4.UtxoData { ); } -class _FakeFeeObject_4 extends _i1.SmartFake implements _i4.FeeObject { - _FakeFeeObject_4( +class _FakeBalance_4 extends _i1.SmartFake implements _i6.Balance { + _FakeBalance_4( Object parent, Invocation parentInvocation, ) : super( @@ -79,8 +84,8 @@ class _FakeFeeObject_4 extends _i1.SmartFake implements _i4.FeeObject { ); } -class _FakeElectrumX_5 extends _i1.SmartFake implements _i5.ElectrumX { - _FakeElectrumX_5( +class _FakeMainDB_5 extends _i1.SmartFake implements _i7.MainDB { + _FakeMainDB_5( Object parent, Invocation parentInvocation, ) : super( @@ -89,9 +94,18 @@ class _FakeElectrumX_5 extends _i1.SmartFake implements _i5.ElectrumX { ); } -class _FakeCachedElectrumX_6 extends _i1.SmartFake - implements _i6.CachedElectrumX { - _FakeCachedElectrumX_6( +class _FakeAmount_6 extends _i1.SmartFake implements _i8.Amount { + _FakeAmount_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeDecimal_7 extends _i1.SmartFake implements _i9.Decimal { + _FakeDecimal_7( Object parent, Invocation parentInvocation, ) : super( @@ -103,13 +117,13 @@ class _FakeCachedElectrumX_6 extends _i1.SmartFake /// A class which mocks [FiroWallet]. /// /// See the documentation for Mockito's code generation for more information. -class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { +class MockFiroWallet extends _i1.Mock implements _i10.FiroWallet { MockFiroWallet() { _i1.throwOnMissingStub(this); } @override - set timer(_i8.Timer? _timer) => super.noSuchMethod( + set timer(_i11.Timer? _timer) => super.noSuchMethod( Invocation.setter( #timer, _timer, @@ -117,14 +131,6 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { returnValueForMissingStub: null, ); @override - set cachedTxData(_i4.TransactionData? _cachedTxData) => super.noSuchMethod( - Invocation.setter( - #cachedTxData, - _cachedTxData, - ), - returnValueForMissingStub: null, - ); - @override _i2.TransactionNotificationTracker get txTracker => (super.noSuchMethod( Invocation.getter(#txTracker), returnValue: _FakeTransactionNotificationTracker_0( @@ -207,110 +213,48 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { returnValue: false, ) as bool); @override - _i9.Coin get coin => (super.noSuchMethod( + _i12.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i9.Coin.bitcoin, - ) as _i9.Coin); + returnValue: _i12.Coin.bitcoin, + ) as _i12.Coin); @override - _i8.Future> get mnemonic => (super.noSuchMethod( + _i11.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i8.Future>.value([]), - ) as _i8.Future>); + returnValue: _i11.Future>.value([]), + ) as _i11.Future>); @override - _i8.Future<_i3.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i8.Future<_i3.Decimal>.value(_FakeDecimal_1( - this, - Invocation.getter(#availableBalance), - )), - ) as _i8.Future<_i3.Decimal>); + _i11.Future get mnemonicString => (super.noSuchMethod( + Invocation.getter(#mnemonicString), + returnValue: _i11.Future.value(), + ) as _i11.Future); @override - _i8.Future<_i3.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i8.Future<_i3.Decimal>.value(_FakeDecimal_1( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i8.Future<_i3.Decimal>); + _i11.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i11.Future.value(), + ) as _i11.Future); @override - _i8.Future<_i3.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i8.Future<_i3.Decimal>.value(_FakeDecimal_1( - this, - Invocation.getter(#totalBalance), - )), - ) as _i8.Future<_i3.Decimal>); - @override - _i8.Future<_i3.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i8.Future<_i3.Decimal>.value(_FakeDecimal_1( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i8.Future<_i3.Decimal>); - @override - _i8.Future<_i4.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), - returnValue: - _i8.Future<_i4.TransactionData>.value(_FakeTransactionData_2( - this, - Invocation.getter(#transactionData), - )), - ) as _i8.Future<_i4.TransactionData>); - @override - _i8.Future<_i4.UtxoData> get utxoData => (super.noSuchMethod( - Invocation.getter(#utxoData), - returnValue: _i8.Future<_i4.UtxoData>.value(_FakeUtxoData_3( - this, - Invocation.getter(#utxoData), - )), - ) as _i8.Future<_i4.UtxoData>); - @override - _i8.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: _i8.Future>.value(<_i4.UtxoObject>[]), - ) as _i8.Future>); - @override - _i8.Future<_i4.TransactionData> get lelantusTransactionData => - (super.noSuchMethod( - Invocation.getter(#lelantusTransactionData), - returnValue: - _i8.Future<_i4.TransactionData>.value(_FakeTransactionData_2( - this, - Invocation.getter(#lelantusTransactionData), - )), - ) as _i8.Future<_i4.TransactionData>); - @override - _i8.Future get maxFee => (super.noSuchMethod( + _i11.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i8.Future.value(0), - ) as _i8.Future); + returnValue: _i11.Future.value(0), + ) as _i11.Future); @override - _i8.Future> get balances => (super.noSuchMethod( - Invocation.getter(#balances), - returnValue: _i8.Future>.value(<_i3.Decimal>[]), - ) as _i8.Future>); - @override - _i8.Future<_i3.Decimal> get firoPrice => (super.noSuchMethod( - Invocation.getter(#firoPrice), - returnValue: _i8.Future<_i3.Decimal>.value(_FakeDecimal_1( - this, - Invocation.getter(#firoPrice), - )), - ) as _i8.Future<_i3.Decimal>); - @override - _i8.Future<_i4.FeeObject> get fees => (super.noSuchMethod( + _i11.Future<_i3.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i8.Future<_i4.FeeObject>.value(_FakeFeeObject_4( + returnValue: _i11.Future<_i3.FeeObject>.value(_FakeFeeObject_1( this, Invocation.getter(#fees), )), - ) as _i8.Future<_i4.FeeObject>); + ) as _i11.Future<_i3.FeeObject>); @override - _i8.Future get currentReceivingAddress => (super.noSuchMethod( + _i11.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i8.Future.value(''), - ) as _i8.Future); + returnValue: _i11.Future.value(''), + ) as _i11.Future); + @override + _i11.Future get currentChangeAddress => (super.noSuchMethod( + Invocation.getter(#currentChangeAddress), + returnValue: _i11.Future.value(''), + ) as _i11.Future); @override String get walletName => (super.noSuchMethod( Invocation.getter(#walletName), @@ -330,31 +274,26 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { returnValue: '', ) as String); @override - _i8.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i8.Future>.value([]), - ) as _i8.Future>); - @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override - _i5.ElectrumX get electrumXClient => (super.noSuchMethod( + _i4.ElectrumX get electrumXClient => (super.noSuchMethod( Invocation.getter(#electrumXClient), - returnValue: _FakeElectrumX_5( + returnValue: _FakeElectrumX_2( this, Invocation.getter(#electrumXClient), ), - ) as _i5.ElectrumX); + ) as _i4.ElectrumX); @override - _i6.CachedElectrumX get cachedElectrumXClient => (super.noSuchMethod( + _i5.CachedElectrumX get cachedElectrumXClient => (super.noSuchMethod( Invocation.getter(#cachedElectrumXClient), - returnValue: _FakeCachedElectrumX_6( + returnValue: _FakeCachedElectrumX_3( this, Invocation.getter(#cachedElectrumXClient), ), - ) as _i6.CachedElectrumX); + ) as _i5.CachedElectrumX); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -366,6 +305,48 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { returnValue: false, ) as bool); @override + _i11.Future get chainHeight => (super.noSuchMethod( + Invocation.getter(#chainHeight), + returnValue: _i11.Future.value(0), + ) as _i11.Future); + @override + int get storedChainHeight => (super.noSuchMethod( + Invocation.getter(#storedChainHeight), + returnValue: 0, + ) as int); + @override + _i6.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_4( + this, + Invocation.getter(#balance), + ), + ) as _i6.Balance); + @override + _i6.Balance get balancePrivate => (super.noSuchMethod( + Invocation.getter(#balancePrivate), + returnValue: _FakeBalance_4( + this, + Invocation.getter(#balancePrivate), + ), + ) as _i6.Balance); + @override + _i11.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i11.Future>.value(<_i13.UTXO>[]), + ) as _i11.Future>); + @override + _i11.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), + returnValue: + _i11.Future>.value(<_i13.Transaction>[]), + ) as _i11.Future>); + @override + _i11.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i11.Future.value(''), + ) as _i11.Future); + @override set onIsActiveWalletChanged(void Function(bool)? _onIsActiveWalletChanged) => super.noSuchMethod( Invocation.setter( @@ -375,6 +356,14 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { returnValueForMissingStub: null, ); @override + _i7.MainDB get db => (super.noSuchMethod( + Invocation.getter(#db), + returnValue: _FakeMainDB_5( + this, + Invocation.getter(#db), + ), + ) as _i7.MainDB); + @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( #validateAddress, @@ -383,23 +372,23 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { returnValue: false, ) as bool); @override - _i8.Future updateSentCachedTxData(Map? txData) => + _i11.Future updateSentCachedTxData(Map? txData) => (super.noSuchMethod( Invocation.method( #updateSentCachedTxData, [txData], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i8.Future testNetworkConnection() => (super.noSuchMethod( + _i11.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i11.Future.value(false), + ) as _i11.Future); @override void startNetworkAlivePinging() => super.noSuchMethod( Invocation.method( @@ -417,9 +406,9 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { returnValueForMissingStub: null, ); @override - _i8.Future> prepareSendPublic({ + _i11.Future> prepareSendPublic({ required String? address, - required int? satoshiAmount, + required _i8.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -428,26 +417,27 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i8.Future>.value({}), - ) as _i8.Future>); + _i11.Future>.value({}), + ) as _i11.Future>); @override - _i8.Future confirmSendPublic({dynamic txData}) => (super.noSuchMethod( + _i11.Future confirmSendPublic({dynamic txData}) => + (super.noSuchMethod( Invocation.method( #confirmSendPublic, [], {#txData: txData}, ), - returnValue: _i8.Future.value(''), - ) as _i8.Future); + returnValue: _i11.Future.value(''), + ) as _i11.Future); @override - _i8.Future> prepareSend({ + _i11.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i8.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -456,41 +446,23 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i8.Future>.value({}), - ) as _i8.Future>); + _i11.Future>.value({}), + ) as _i11.Future>); @override - _i8.Future confirmSend({required Map? txData}) => + _i11.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i8.Future.value(''), - ) as _i8.Future); - @override - _i8.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i8.Future.value(''), - ) as _i8.Future); + returnValue: _i11.Future.value(''), + ) as _i11.Future); @override int estimateTxFee({ required int? vSize, @@ -514,7 +486,7 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { String? _recipientAddress, bool? isSendAll, { int? additionalOutputs = 0, - List<_i4.UtxoObject>? utxos, + List<_i13.UTXO>? utxos, }) => super.noSuchMethod(Invocation.method( #coinSelection, @@ -530,20 +502,19 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { }, )); @override - _i8.Future> fetchBuildTxData( - List<_i4.UtxoObject>? utxosToUse) => + _i11.Future> fetchBuildTxData( + List<_i13.UTXO>? utxosToUse) => (super.noSuchMethod( Invocation.method( #fetchBuildTxData, [utxosToUse], ), returnValue: - _i8.Future>.value({}), - ) as _i8.Future>); + _i11.Future>.value(<_i14.SigningData>[]), + ) as _i11.Future>); @override - _i8.Future> buildTransaction({ - required List<_i4.UtxoObject>? utxosToUse, - required Map? utxoSigningData, + _i11.Future> buildTransaction({ + required List<_i14.SigningData>? utxoSigningData, required List? recipients, required List? satoshiAmounts, }) => @@ -552,114 +523,106 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { #buildTransaction, [], { - #utxosToUse: utxosToUse, #utxoSigningData: utxoSigningData, #recipients: recipients, #satoshiAmounts: satoshiAmounts, }, ), returnValue: - _i8.Future>.value({}), - ) as _i8.Future>); + _i11.Future>.value({}), + ) as _i11.Future>); @override - _i8.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i11.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i8.Future initializeNew() => (super.noSuchMethod( + _i11.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i8.Future initializeExisting() => (super.noSuchMethod( + _i11.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i8.Future refreshIfThereIsNewData() => (super.noSuchMethod( + _i11.Future refreshIfThereIsNewData() => (super.noSuchMethod( Invocation.method( #refreshIfThereIsNewData, [], ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i11.Future.value(false), + ) as _i11.Future); @override - _i8.Future getAllTxsToWatch( - _i4.TransactionData? txData, - _i4.TransactionData? lTxData, - ) => - (super.noSuchMethod( + _i11.Future getAllTxsToWatch() => (super.noSuchMethod( Invocation.method( #getAllTxsToWatch, - [ - txData, - lTxData, - ], + [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i8.Future refresh() => (super.noSuchMethod( + _i11.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - List> getLelantusCoinMap() => + List> getLelantusCoinMap() => (super.noSuchMethod( Invocation.method( #getLelantusCoinMap, [], ), - returnValue: >[], - ) as List>); + returnValue: >[], + ) as List>); @override - _i8.Future anonymizeAllPublicFunds() => (super.noSuchMethod( + _i11.Future anonymizeAllPublicFunds() => (super.noSuchMethod( Invocation.method( #anonymizeAllPublicFunds, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i8.Future>> createMintsFromAmount(int? total) => + _i11.Future>> createMintsFromAmount(int? total) => (super.noSuchMethod( Invocation.method( #createMintsFromAmount, [total], ), - returnValue: _i8.Future>>.value( + returnValue: _i11.Future>>.value( >[]), - ) as _i8.Future>>); + ) as _i11.Future>>); @override - _i8.Future submitHexToNetwork(String? hex) => (super.noSuchMethod( + _i11.Future submitHexToNetwork(String? hex) => (super.noSuchMethod( Invocation.method( #submitHexToNetwork, [hex], ), - returnValue: _i8.Future.value(''), - ) as _i8.Future); + returnValue: _i11.Future.value(''), + ) as _i11.Future); @override - _i8.Future> buildMintTransaction( - List<_i4.UtxoObject>? utxosToUse, + _i11.Future> buildMintTransaction( + List<_i13.UTXO>? utxosToUse, int? satoshisPerRecipient, List>? mintsMap, ) => @@ -673,73 +636,29 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { ], ), returnValue: - _i8.Future>.value({}), - ) as _i8.Future>); + _i11.Future>.value({}), + ) as _i11.Future>); @override - _i8.Future checkReceivingAddressForTransactions() => + _i11.Future checkReceivingAddressForTransactions() => (super.noSuchMethod( Invocation.method( #checkReceivingAddressForTransactions, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i8.Future checkChangeAddressForTransactions() => (super.noSuchMethod( + _i11.Future checkChangeAddressForTransactions() => (super.noSuchMethod( Invocation.method( #checkChangeAddressForTransactions, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i8.Future fillAddresses( - String? suppliedMnemonic, { - int? perBatch = 50, - int? numberOfThreads = 4, - }) => - (super.noSuchMethod( - Invocation.method( - #fillAddresses, - [suppliedMnemonic], - { - #perBatch: perBatch, - #numberOfThreads: numberOfThreads, - }, - ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); - @override - _i8.Future incrementAddressIndexForChain(int? chain) => - (super.noSuchMethod( - Invocation.method( - #incrementAddressIndexForChain, - [chain], - ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); - @override - _i8.Future addToAddressesArrayForChain( - String? address, - int? chain, - ) => - (super.noSuchMethod( - Invocation.method( - #addToAddressesArrayForChain, - [ - address, - chain, - ], - ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); - @override - _i8.Future fullRescan( + _i11.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -751,12 +670,13 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { maxNumberOfIndexesToCheck, ], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i8.Future recoverFromMnemonic({ + _i11.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -767,106 +687,137 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i8.Future> getSetDataMap(int? latestSetId) => + _i11.Future> getSetDataMap(int? latestSetId) => (super.noSuchMethod( Invocation.method( #getSetDataMap, [latestSetId], ), - returnValue: _i8.Future>.value({}), - ) as _i8.Future>); + returnValue: _i11.Future>.value({}), + ) as _i11.Future>); @override - _i8.Future>> fetchAnonymitySets() => + _i11.Future getTransactionCacheEarly(List? allAddresses) => + (super.noSuchMethod( + Invocation.method( + #getTransactionCacheEarly, + [allAddresses], + ), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + @override + _i11.Future>> fetchAnonymitySets() => (super.noSuchMethod( Invocation.method( #fetchAnonymitySets, [], ), - returnValue: _i8.Future>>.value( + returnValue: _i11.Future>>.value( >[]), - ) as _i8.Future>>); + ) as _i11.Future>>); @override - _i8.Future getLatestSetId() => (super.noSuchMethod( + _i11.Future getLatestSetId() => (super.noSuchMethod( Invocation.method( #getLatestSetId, [], ), - returnValue: _i8.Future.value(0), - ) as _i8.Future); + returnValue: _i11.Future.value(0), + ) as _i11.Future); @override - _i8.Future> getUsedCoinSerials() => (super.noSuchMethod( + _i11.Future> getUsedCoinSerials() => (super.noSuchMethod( Invocation.method( #getUsedCoinSerials, [], ), - returnValue: _i8.Future>.value([]), - ) as _i8.Future>); + returnValue: _i11.Future>.value([]), + ) as _i11.Future>); @override - _i8.Future exit() => (super.noSuchMethod( + _i11.Future exit() => (super.noSuchMethod( Invocation.method( #exit, [], ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i8.Future getCoinsToJoinSplit(int? required) => (super.noSuchMethod( + _i11.Future getCoinsToJoinSplit(int? required) => + (super.noSuchMethod( Invocation.method( #getCoinsToJoinSplit, [required], ), - returnValue: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i11.Future.value(), + ) as _i11.Future); @override - _i8.Future estimateJoinSplitFee(int? spendAmount) => (super.noSuchMethod( + _i11.Future estimateJoinSplitFee(int? spendAmount) => + (super.noSuchMethod( Invocation.method( #estimateJoinSplitFee, [spendAmount], ), - returnValue: _i8.Future.value(0), - ) as _i8.Future); + returnValue: _i11.Future.value(0), + ) as _i11.Future); @override - _i8.Future estimateFeeFor( - int? satoshiAmount, + _i11.Future<_i8.Amount> estimateFeeFor( + _i8.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i8.Future.value(0), - ) as _i8.Future); + returnValue: _i11.Future<_i8.Amount>.value(_FakeAmount_6( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i11.Future<_i8.Amount>); @override - _i8.Future estimateFeeForPublic( - int? satoshiAmount, + _i11.Future<_i8.Amount> estimateFeeForPublic( + _i8.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeForPublic, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i8.Future.value(0), - ) as _i8.Future); + returnValue: _i11.Future<_i8.Amount>.value(_FakeAmount_6( + this, + Invocation.method( + #estimateFeeForPublic, + [ + amount, + feeRate, + ], + ), + )), + ) as _i11.Future<_i8.Amount>); @override - int roughFeeEstimate( + _i8.Amount roughFeeEstimate( int? inputCount, int? outputCount, int? feeRatePerKB, @@ -880,34 +831,48 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { feeRatePerKB, ], ), - returnValue: 0, - ) as int); + returnValue: _FakeAmount_6( + this, + Invocation.method( + #roughFeeEstimate, + [ + inputCount, + outputCount, + feeRatePerKB, + ], + ), + ), + ) as _i8.Amount); @override - int sweepAllEstimate(int? feeRate) => (super.noSuchMethod( + _i11.Future<_i8.Amount> sweepAllEstimate(int? feeRate) => (super.noSuchMethod( Invocation.method( #sweepAllEstimate, [feeRate], ), - returnValue: 0, - ) as int); + returnValue: _i11.Future<_i8.Amount>.value(_FakeAmount_6( + this, + Invocation.method( + #sweepAllEstimate, + [feeRate], + ), + )), + ) as _i11.Future<_i8.Amount>); @override - _i8.Future>> fastFetch(List? allTxHashes) => + _i11.Future>> fastFetch( + List? allTxHashes) => (super.noSuchMethod( Invocation.method( #fastFetch, [allTxHashes], ), - returnValue: _i8.Future>>.value( + returnValue: _i11.Future>>.value( >[]), - ) as _i8.Future>>); + ) as _i11.Future>>); @override - _i8.Future> getJMintTransactions( - _i6.CachedElectrumX? cachedClient, + _i11.Future> getJMintTransactions( + _i5.CachedElectrumX? cachedClient, List? transactions, - String? currency, - _i9.Coin? coin, - _i3.Decimal? currentPrice, - String? locale, + _i12.Coin? coin, ) => (super.noSuchMethod( Invocation.method( @@ -915,63 +880,232 @@ class MockFiroWallet extends _i1.Mock implements _i7.FiroWallet { [ cachedClient, transactions, - currency, coin, - currentPrice, - locale, ], ), - returnValue: - _i8.Future>.value(<_i4.Transaction>[]), - ) as _i8.Future>); + returnValue: _i11.Future>.value( + <_i13.Address, _i13.Transaction>{}), + ) as _i11.Future>); @override - _i8.Future generateNewAddress() => (super.noSuchMethod( + _i11.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i11.Future.value(false), + ) as _i11.Future); @override - _i8.Future<_i3.Decimal> availablePrivateBalance() => (super.noSuchMethod( + _i8.Amount availablePrivateBalance() => (super.noSuchMethod( Invocation.method( #availablePrivateBalance, [], ), - returnValue: _i8.Future<_i3.Decimal>.value(_FakeDecimal_1( + returnValue: _FakeAmount_6( this, Invocation.method( #availablePrivateBalance, [], ), - )), - ) as _i8.Future<_i3.Decimal>); + ), + ) as _i8.Amount); @override - _i8.Future<_i3.Decimal> availablePublicBalance() => (super.noSuchMethod( + _i8.Amount availablePublicBalance() => (super.noSuchMethod( Invocation.method( #availablePublicBalance, [], ), - returnValue: _i8.Future<_i3.Decimal>.value(_FakeDecimal_1( + returnValue: _FakeAmount_6( this, Invocation.method( #availablePublicBalance, [], ), - )), - ) as _i8.Future<_i3.Decimal>); + ), + ) as _i8.Amount); + @override + void initCache( + String? walletId, + _i12.Coin? coin, + ) => + super.noSuchMethod( + Invocation.method( + #initCache, + [ + walletId, + coin, + ], + ), + returnValueForMissingStub: null, + ); + @override + _i11.Future updateCachedId(String? id) => (super.noSuchMethod( + Invocation.method( + #updateCachedId, + [id], + ), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + @override + int getCachedChainHeight() => (super.noSuchMethod( + Invocation.method( + #getCachedChainHeight, + [], + ), + returnValue: 0, + ) as int); + @override + _i11.Future updateCachedChainHeight(int? height) => (super.noSuchMethod( + Invocation.method( + #updateCachedChainHeight, + [height], + ), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + @override + bool getCachedIsFavorite() => (super.noSuchMethod( + Invocation.method( + #getCachedIsFavorite, + [], + ), + returnValue: false, + ) as bool); + @override + _i11.Future updateCachedIsFavorite(bool? isFavorite) => + (super.noSuchMethod( + Invocation.method( + #updateCachedIsFavorite, + [isFavorite], + ), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + @override + _i6.Balance getCachedBalance() => (super.noSuchMethod( + Invocation.method( + #getCachedBalance, + [], + ), + returnValue: _FakeBalance_4( + this, + Invocation.method( + #getCachedBalance, + [], + ), + ), + ) as _i6.Balance); + @override + _i11.Future updateCachedBalance(_i6.Balance? balance) => + (super.noSuchMethod( + Invocation.method( + #updateCachedBalance, + [balance], + ), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + @override + _i6.Balance getCachedBalanceSecondary() => (super.noSuchMethod( + Invocation.method( + #getCachedBalanceSecondary, + [], + ), + returnValue: _FakeBalance_4( + this, + Invocation.method( + #getCachedBalanceSecondary, + [], + ), + ), + ) as _i6.Balance); + @override + _i11.Future updateCachedBalanceSecondary(_i6.Balance? balance) => + (super.noSuchMethod( + Invocation.method( + #updateCachedBalanceSecondary, + [balance], + ), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + @override + List getWalletTokenContractAddresses() => (super.noSuchMethod( + Invocation.method( + #getWalletTokenContractAddresses, + [], + ), + returnValue: [], + ) as List); + @override + _i11.Future updateWalletTokenContractAddresses( + List? contractAddresses) => + (super.noSuchMethod( + Invocation.method( + #updateWalletTokenContractAddresses, + [contractAddresses], + ), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + @override + void initWalletDB({_i7.MainDB? mockableOverride}) => super.noSuchMethod( + Invocation.method( + #initWalletDB, + [], + {#mockableOverride: mockableOverride}, + ), + returnValueForMissingStub: null, + ); + @override + void initFiroHive(String? walletId) => super.noSuchMethod( + Invocation.method( + #initFiroHive, + [walletId], + ), + returnValueForMissingStub: null, + ); + @override + _i11.Future firoUpdateJIndex(List? jIndex) => + (super.noSuchMethod( + Invocation.method( + #firoUpdateJIndex, + [jIndex], + ), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + @override + _i11.Future firoUpdateLelantusCoins(List? lelantusCoins) => + (super.noSuchMethod( + Invocation.method( + #firoUpdateLelantusCoins, + [lelantusCoins], + ), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + @override + _i11.Future firoUpdateMintIndex(int? mintIndex) => (super.noSuchMethod( + Invocation.method( + #firoUpdateMintIndex, + [mintIndex], + ), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); } /// A class which mocks [ElectrumX]. /// /// See the documentation for Mockito's code generation for more information. -class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { +class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { MockElectrumX() { _i1.throwOnMissingStub(this); } @override - set failovers(List<_i5.ElectrumXNode>? _failovers) => super.noSuchMethod( + set failovers(List<_i4.ElectrumXNode>? _failovers) => super.noSuchMethod( Invocation.setter( #failovers, _failovers, @@ -1007,7 +1141,7 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { returnValue: false, ) as bool); @override - _i8.Future request({ + _i11.Future request({ required String? command, List? args = const [], Duration? connectionTimeout = const Duration(seconds: 60), @@ -1026,10 +1160,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #retries: retries, }, ), - returnValue: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i11.Future.value(), + ) as _i11.Future); @override - _i8.Future>> batchRequest({ + _i11.Future>> batchRequest({ required String? command, required Map>? args, Duration? connectionTimeout = const Duration(seconds: 60), @@ -1046,11 +1180,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #retries: retries, }, ), - returnValue: _i8.Future>>.value( + returnValue: _i11.Future>>.value( >[]), - ) as _i8.Future>>); + ) as _i11.Future>>); @override - _i8.Future ping({ + _i11.Future ping({ String? requestID, int? retryCount = 1, }) => @@ -1063,10 +1197,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #retryCount: retryCount, }, ), - returnValue: _i8.Future.value(false), - ) as _i8.Future); + returnValue: _i11.Future.value(false), + ) as _i11.Future); @override - _i8.Future> getBlockHeadTip({String? requestID}) => + _i11.Future> getBlockHeadTip({String? requestID}) => (super.noSuchMethod( Invocation.method( #getBlockHeadTip, @@ -1074,10 +1208,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { {#requestID: requestID}, ), returnValue: - _i8.Future>.value({}), - ) as _i8.Future>); + _i11.Future>.value({}), + ) as _i11.Future>); @override - _i8.Future> getServerFeatures({String? requestID}) => + _i11.Future> getServerFeatures({String? requestID}) => (super.noSuchMethod( Invocation.method( #getServerFeatures, @@ -1085,10 +1219,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { {#requestID: requestID}, ), returnValue: - _i8.Future>.value({}), - ) as _i8.Future>); + _i11.Future>.value({}), + ) as _i11.Future>); @override - _i8.Future broadcastTransaction({ + _i11.Future broadcastTransaction({ required String? rawTx, String? requestID, }) => @@ -1101,10 +1235,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #requestID: requestID, }, ), - returnValue: _i8.Future.value(''), - ) as _i8.Future); + returnValue: _i11.Future.value(''), + ) as _i11.Future); @override - _i8.Future> getBalance({ + _i11.Future> getBalance({ required String? scripthash, String? requestID, }) => @@ -1118,10 +1252,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), returnValue: - _i8.Future>.value({}), - ) as _i8.Future>); + _i11.Future>.value({}), + ) as _i11.Future>); @override - _i8.Future>> getHistory({ + _i11.Future>> getHistory({ required String? scripthash, String? requestID, }) => @@ -1134,11 +1268,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #requestID: requestID, }, ), - returnValue: _i8.Future>>.value( + returnValue: _i11.Future>>.value( >[]), - ) as _i8.Future>>); + ) as _i11.Future>>); @override - _i8.Future>>> getBatchHistory( + _i11.Future>>> getBatchHistory( {required Map>? args}) => (super.noSuchMethod( Invocation.method( @@ -1146,11 +1280,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { [], {#args: args}, ), - returnValue: _i8.Future>>>.value( + returnValue: _i11.Future>>>.value( >>{}), - ) as _i8.Future>>>); + ) as _i11.Future>>>); @override - _i8.Future>> getUTXOs({ + _i11.Future>> getUTXOs({ required String? scripthash, String? requestID, }) => @@ -1163,11 +1297,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #requestID: requestID, }, ), - returnValue: _i8.Future>>.value( + returnValue: _i11.Future>>.value( >[]), - ) as _i8.Future>>); + ) as _i11.Future>>); @override - _i8.Future>>> getBatchUTXOs( + _i11.Future>>> getBatchUTXOs( {required Map>? args}) => (super.noSuchMethod( Invocation.method( @@ -1175,11 +1309,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { [], {#args: args}, ), - returnValue: _i8.Future>>>.value( + returnValue: _i11.Future>>>.value( >>{}), - ) as _i8.Future>>>); + ) as _i11.Future>>>); @override - _i8.Future> getTransaction({ + _i11.Future> getTransaction({ required String? txHash, bool? verbose = true, String? requestID, @@ -1195,10 +1329,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), returnValue: - _i8.Future>.value({}), - ) as _i8.Future>); + _i11.Future>.value({}), + ) as _i11.Future>); @override - _i8.Future> getAnonymitySet({ + _i11.Future> getAnonymitySet({ String? groupId = r'1', String? blockhash = r'', String? requestID, @@ -1214,10 +1348,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), returnValue: - _i8.Future>.value({}), - ) as _i8.Future>); + _i11.Future>.value({}), + ) as _i11.Future>); @override - _i8.Future getMintData({ + _i11.Future getMintData({ dynamic mints, String? requestID, }) => @@ -1230,10 +1364,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #requestID: requestID, }, ), - returnValue: _i8.Future.value(), - ) as _i8.Future); + returnValue: _i11.Future.value(), + ) as _i11.Future); @override - _i8.Future> getUsedCoinSerials({ + _i11.Future> getUsedCoinSerials({ String? requestID, required int? startNumber, }) => @@ -1247,19 +1381,19 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), returnValue: - _i8.Future>.value({}), - ) as _i8.Future>); + _i11.Future>.value({}), + ) as _i11.Future>); @override - _i8.Future getLatestCoinId({String? requestID}) => (super.noSuchMethod( + _i11.Future getLatestCoinId({String? requestID}) => (super.noSuchMethod( Invocation.method( #getLatestCoinId, [], {#requestID: requestID}, ), - returnValue: _i8.Future.value(0), - ) as _i8.Future); + returnValue: _i11.Future.value(0), + ) as _i11.Future); @override - _i8.Future> getFeeRate({String? requestID}) => + _i11.Future> getFeeRate({String? requestID}) => (super.noSuchMethod( Invocation.method( #getFeeRate, @@ -1267,10 +1401,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { {#requestID: requestID}, ), returnValue: - _i8.Future>.value({}), - ) as _i8.Future>); + _i11.Future>.value({}), + ) as _i11.Future>); @override - _i8.Future<_i3.Decimal> estimateFee({ + _i11.Future<_i9.Decimal> estimateFee({ String? requestID, required int? blocks, }) => @@ -1283,7 +1417,7 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #blocks: blocks, }, ), - returnValue: _i8.Future<_i3.Decimal>.value(_FakeDecimal_1( + returnValue: _i11.Future<_i9.Decimal>.value(_FakeDecimal_7( this, Invocation.method( #estimateFee, @@ -1294,15 +1428,15 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), )), - ) as _i8.Future<_i3.Decimal>); + ) as _i11.Future<_i9.Decimal>); @override - _i8.Future<_i3.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( + _i11.Future<_i9.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( Invocation.method( #relayFee, [], {#requestID: requestID}, ), - returnValue: _i8.Future<_i3.Decimal>.value(_FakeDecimal_1( + returnValue: _i11.Future<_i9.Decimal>.value(_FakeDecimal_7( this, Invocation.method( #relayFee, @@ -1310,5 +1444,5 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { {#requestID: requestID}, ), )), - ) as _i8.Future<_i3.Decimal>); + ) as _i11.Future<_i9.Decimal>); } diff --git a/test/services/coins/monero/monero_wallet_test.dart b/test/services/coins/monero/monero_wallet_test.dart index d6d600e36..d33c1b460 100644 --- a/test/services/coins/monero/monero_wallet_test.dart +++ b/test/services/coins/monero/monero_wallet_test.dart @@ -1,236 +1,231 @@ import 'dart:core'; -import 'dart:io'; -import 'dart:math'; +// import 'dart:io'; +// import 'dart:math'; +// +// TODO: move these tests to libmonero +// TODO: use temp dir for wallets testing and not production location +// +// import 'package:cw_core/node.dart'; +// import 'package:cw_core/unspent_coins_info.dart'; +// import 'package:cw_core/wallet_base.dart'; +// import 'package:cw_core/wallet_credentials.dart'; +// import 'package:cw_core/wallet_info.dart'; +// import 'package:cw_core/wallet_service.dart'; +// import 'package:cw_core/wallet_type.dart'; +// import 'package:cw_monero/monero_wallet.dart'; +// import 'package:flutter_libmonero/core/key_service.dart'; +// import 'package:flutter_libmonero/core/wallet_creation_service.dart'; +// import 'package:flutter_libmonero/monero/monero.dart'; +// import 'package:flutter_test/flutter_test.dart'; +// import 'package:hive/hive.dart'; +// import 'package:hive_test/hive_test.dart'; +// import 'package:path_provider/path_provider.dart'; +// import 'package:stackwallet/services/wallets.dart'; +// import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +// +// import 'monero_wallet_test_data.dart'; +// +// FakeSecureStorage? storage; +// WalletService? walletService; +// KeyService? keysStorage; +// MoneroWalletBase? walletBase; +// late WalletCreationService _walletCreationService; +// dynamic _walletInfoSource; +// Wallets? walletsService; +// +// String path = ''; +// +// String name = 'namee${Random().nextInt(10000000)}'; +// int nettype = 0; +// WalletType type = WalletType.monero; -import 'package:cw_core/node.dart'; -import 'package:cw_core/unspent_coins_info.dart'; -import 'package:cw_core/wallet_base.dart'; -import 'package:cw_core/wallet_credentials.dart'; -import 'package:cw_core/wallet_info.dart'; -import 'package:cw_core/wallet_service.dart'; -import 'package:cw_core/wallet_type.dart'; -import 'package:cw_monero/monero_wallet.dart'; -import 'package:flutter_libmonero/core/key_service.dart'; -import 'package:flutter_libmonero/core/wallet_creation_service.dart'; -import 'package:flutter_libmonero/monero/monero.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hive/hive.dart'; -import 'package:hive_test/hive_test.dart'; -import 'package:mockito/annotations.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:stackwallet/services/wallets.dart'; -import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; - -import 'monero_wallet_test_data.dart'; - -FakeSecureStorage? storage; -WalletService? walletService; -KeyService? keysStorage; -MoneroWalletBase? walletBase; -late WalletCreationService _walletCreationService; -dynamic _walletInfoSource; -Wallets? walletsService; - -String path = ''; - -String name = 'namee${Random().nextInt(10000000)}'; -int nettype = 0; -WalletType type = WalletType.monero; - -@GenerateMocks([]) void main() async { - storage = FakeSecureStorage(); - keysStorage = KeyService(storage!); - WalletInfo walletInfo = WalletInfo.external( - id: '', - name: '', - type: type, - isRecovery: false, - restoreHeight: 0, - date: DateTime.now(), - path: '', - address: '', - dirPath: ''); - late WalletCredentials credentials; - - monero.onStartup(); - - bool hiveAdaptersRegistered = false; - - group("Mainnet tests", () { - setUp(() async { - await setUpTestHive(); - if (!hiveAdaptersRegistered) { - hiveAdaptersRegistered = true; - - Hive.registerAdapter(NodeAdapter()); - Hive.registerAdapter(WalletInfoAdapter()); - Hive.registerAdapter(WalletTypeAdapter()); - Hive.registerAdapter(UnspentCoinsInfoAdapter()); - - final wallets = await Hive.openBox('wallets'); - await wallets.put('currentWalletName', name); - - _walletInfoSource = await Hive.openBox(WalletInfo.boxName); - walletService = monero - .createMoneroWalletService(_walletInfoSource as Box); - } - - try { - // if (name?.isEmpty ?? true) { - // name = await generateName(); - // } - final dirPath = await pathForWalletDir(name: name, type: type); - path = await pathForWallet(name: name, type: type); - credentials = - // // creating a new wallet - // monero.createMoneroNewWalletCredentials( - // name: name, language: "English"); - // restoring a previous wallet - monero.createMoneroRestoreWalletFromSeedCredentials( - name: name, height: 2580000, mnemonic: testMnemonic); - - walletInfo = WalletInfo.external( - id: WalletBase.idFor(name, type), - name: name, - type: type, - isRecovery: false, - restoreHeight: credentials.height ?? 0, - date: DateTime.now(), - path: path, - address: "", - dirPath: dirPath); - credentials.walletInfo = walletInfo; - - _walletCreationService = WalletCreationService( - secureStorage: storage, - walletService: walletService, - keyService: keysStorage, - ); - _walletCreationService.changeWalletType(); - } catch (e, s) { - print(e); - print(s); - } - }); - - test("Test mainnet address generation from seed", () async { - final wallet = await - // _walletCreationService.create(credentials); - _walletCreationService.restoreFromSeed(credentials); - walletInfo.address = wallet.walletAddresses.address; - //print(walletInfo.address); - - await _walletInfoSource.add(walletInfo); - walletBase?.close(); - walletBase = wallet as MoneroWalletBase; - //print("${walletBase?.seed}"); - - expect(await walletBase!.validateAddress(walletInfo.address ?? ''), true); - - // print(walletBase); - // loggerPrint(walletBase.toString()); - // loggerPrint("name: ${walletBase!.name} seed: ${walletBase!.seed} id: " - // "${walletBase!.id} walletinfo: ${toStringForinfo(walletBase!.walletInfo)} type: ${walletBase!.type} balance: " - // "${walletBase!.balance.entries.first.value.available} currency: ${walletBase!.currency}"); - - expect(walletInfo.address, mainnetTestData[0][0]); - expect( - await walletBase!.getTransactionAddress(0, 0), mainnetTestData[0][0]); - expect( - await walletBase!.getTransactionAddress(0, 1), mainnetTestData[0][1]); - expect( - await walletBase!.getTransactionAddress(0, 2), mainnetTestData[0][2]); - expect( - await walletBase!.getTransactionAddress(1, 0), mainnetTestData[1][0]); - expect( - await walletBase!.getTransactionAddress(1, 1), mainnetTestData[1][1]); - expect( - await walletBase!.getTransactionAddress(1, 2), mainnetTestData[1][2]); - - expect(await walletBase!.validateAddress(''), false); - expect( - await walletBase!.validateAddress( - '4AeRgkWZsMJhAWKMeCZ3h4ZSPnAcW5VBtRFyLd6gBEf6GgJU2FHXDA6i1DnQTd6h8R3VU5AkbGcWSNhtSwNNPgaD48gp4nn'), - true); - expect( - await walletBase!.validateAddress( - '4asdfkWZsMJhAWKMeCZ3h4ZSPnAcW5VBtRFyLd6gBEf6GgJU2FHXDA6i1DnQTd6h8R3VU5AkbGcWSNhtSwNNPgaD48gpjkl'), - false); - expect( - await walletBase!.validateAddress( - '8AeRgkWZsMJhAWKMeCZ3h4ZSPnAcW5VBtRFyLd6gBEf6GgJU2FHXDA6i1DnQTd6h8R3VU5AkbGcWSNhtSwNNPgaD48gp4nn'), - false); - expect( - await walletBase!.validateAddress( - '84kYPuZ1eaVKGQhf26QPNWbSLQG16BywXdLYYShVrPNMLAUAWce5vcpRc78FxwRphrG6Cda7faCKdUMr8fUCH3peHPenvHy'), - true); - expect( - await walletBase!.validateAddress( - '8asdfuZ1eaVKGQhf26QPNWbSLQG16BywXdLYYShVrPNMLAUAWce5vcpRc78FxwRphrG6Cda7faCKdUMr8fUCH3peHPenjkl'), - false); - expect( - await walletBase!.validateAddress( - '44kYPuZ1eaVKGQhf26QPNWbSLQG16BywXdLYYShVrPNMLAUAWce5vcpRc78FxwRphrG6Cda7faCKdUMr8fUCH3peHPenvHy'), - false); - }); - }); - /* - // Not needed; only folder created, wallet files not saved yet. TODO test saving and deleting wallet files and make sure to clean up leftover folder afterwards - group("Mainnet wallet deletion test", () { - test("Test mainnet wallet existence", () { - expect(monero_wallet_manager.isWalletExistSync(path: path), true); - }); - - test("Test mainnet wallet deletion", () { - // Remove wallet from wallet service - walletService?.remove(name); - walletsService?.removeWallet(walletId: name); - expect(monero_wallet_manager.isWalletExistSync(path: path), false); - }); - }); - - group("Mainnet node tests", () { - test("Test mainnet node connection", () async { - await walletBase?.connectToNode( - node: Node( - uri: "monero-stagenet.stackwallet.com:38081", - type: WalletType.moneroStageNet)); - await walletBase!.rescan( - height: - credentials.height); // Probably shouldn't be rescanning from 0... - await walletBase!.getNodeHeight(); - int height = await walletBase!.getNodeHeight(); - print('height: $height'); - bool connected = await walletBase!.isConnected(); - print('connected: $connected'); - - //expect... - }); - }); - */ - - // TODO test deletion of wallets ... and delete them + // storage = FakeSecureStorage(); + // keysStorage = KeyService(storage!); + // WalletInfo walletInfo = WalletInfo.external( + // id: '', + // name: '', + // type: type, + // isRecovery: false, + // restoreHeight: 0, + // date: DateTime.now(), + // path: '', + // address: '', + // dirPath: ''); + // late WalletCredentials credentials; + // + // monero.onStartup(); + // + // bool hiveAdaptersRegistered = false; + // + // group("Mainnet tests", () { + // setUp(() async { + // await setUpTestHive(); + // if (!hiveAdaptersRegistered) { + // hiveAdaptersRegistered = true; + // + // Hive.registerAdapter(NodeAdapter()); + // Hive.registerAdapter(WalletInfoAdapter()); + // Hive.registerAdapter(WalletTypeAdapter()); + // Hive.registerAdapter(UnspentCoinsInfoAdapter()); + // + // final wallets = await Hive.openBox('wallets'); + // await wallets.put('currentWalletName', name); + // + // _walletInfoSource = await Hive.openBox(WalletInfo.boxName); + // walletService = monero + // .createMoneroWalletService(_walletInfoSource as Box); + // } + // + // try { + // // if (name?.isEmpty ?? true) { + // // name = await generateName(); + // // } + // final dirPath = await pathForWalletDir(name: name, type: type); + // path = await pathForWallet(name: name, type: type); + // credentials = + // // // creating a new wallet + // // monero.createMoneroNewWalletCredentials( + // // name: name, language: "English"); + // // restoring a previous wallet + // monero.createMoneroRestoreWalletFromSeedCredentials( + // name: name, height: 2580000, mnemonic: testMnemonic); + // + // walletInfo = WalletInfo.external( + // id: WalletBase.idFor(name, type), + // name: name, + // type: type, + // isRecovery: false, + // restoreHeight: credentials.height ?? 0, + // date: DateTime.now(), + // path: path, + // address: "", + // dirPath: dirPath); + // credentials.walletInfo = walletInfo; + // + // _walletCreationService = WalletCreationService( + // secureStorage: storage, + // walletService: walletService, + // keyService: keysStorage, + // ); + // _walletCreationService.changeWalletType(); + // } catch (e, s) { + // print(e); + // print(s); + // } + // }); + // + // test("Test mainnet address generation from seed", () async { + // final wallet = await + // // _walletCreationService.create(credentials); + // _walletCreationService.restoreFromSeed(credentials); + // walletInfo.address = wallet.walletAddresses.address; + // //print(walletInfo.address); + // + // await _walletInfoSource.add(walletInfo); + // walletBase?.close(); + // walletBase = wallet as MoneroWalletBase; + // //print("${walletBase?.seed}"); + // + // expect(await walletBase!.validateAddress(walletInfo.address ?? ''), true); + // + // // print(walletBase); + // // loggerPrint(walletBase.toString()); + // // loggerPrint("name: ${walletBase!.name} seed: ${walletBase!.seed} id: " + // // "${walletBase!.id} walletinfo: ${toStringForinfo(walletBase!.walletInfo)} type: ${walletBase!.type} balance: " + // // "${walletBase!.balance.entries.first.value.available} currency: ${walletBase!.currency}"); + // + // expect(walletInfo.address, mainnetTestData[0][0]); + // expect(walletBase!.getTransactionAddress(0, 0), mainnetTestData[0][0]); + // expect(walletBase!.getTransactionAddress(0, 1), mainnetTestData[0][1]); + // expect(walletBase!.getTransactionAddress(0, 2), mainnetTestData[0][2]); + // expect(walletBase!.getTransactionAddress(1, 0), mainnetTestData[1][0]); + // expect(walletBase!.getTransactionAddress(1, 1), mainnetTestData[1][1]); + // expect(walletBase!.getTransactionAddress(1, 2), mainnetTestData[1][2]); + // + // expect(walletBase!.validateAddress(''), false); + // expect( + // walletBase!.validateAddress( + // '4AeRgkWZsMJhAWKMeCZ3h4ZSPnAcW5VBtRFyLd6gBEf6GgJU2FHXDA6i1DnQTd6h8R3VU5AkbGcWSNhtSwNNPgaD48gp4nn'), + // true); + // expect( + // walletBase!.validateAddress( + // '4asdfkWZsMJhAWKMeCZ3h4ZSPnAcW5VBtRFyLd6gBEf6GgJU2FHXDA6i1DnQTd6h8R3VU5AkbGcWSNhtSwNNPgaD48gpjkl'), + // false); + // expect( + // walletBase!.validateAddress( + // '8AeRgkWZsMJhAWKMeCZ3h4ZSPnAcW5VBtRFyLd6gBEf6GgJU2FHXDA6i1DnQTd6h8R3VU5AkbGcWSNhtSwNNPgaD48gp4nn'), + // false); + // expect( + // walletBase!.validateAddress( + // '84kYPuZ1eaVKGQhf26QPNWbSLQG16BywXdLYYShVrPNMLAUAWce5vcpRc78FxwRphrG6Cda7faCKdUMr8fUCH3peHPenvHy'), + // true); + // expect( + // walletBase!.validateAddress( + // '8asdfuZ1eaVKGQhf26QPNWbSLQG16BywXdLYYShVrPNMLAUAWce5vcpRc78FxwRphrG6Cda7faCKdUMr8fUCH3peHPenjkl'), + // false); + // expect( + // walletBase!.validateAddress( + // '44kYPuZ1eaVKGQhf26QPNWbSLQG16BywXdLYYShVrPNMLAUAWce5vcpRc78FxwRphrG6Cda7faCKdUMr8fUCH3peHPenvHy'), + // false); + // }); + // }); + // /* + // // Not needed; only folder created, wallet files not saved yet. TODO test saving and deleting wallet files and make sure to clean up leftover folder afterwards + // group("Mainnet wallet deletion test", () { + // test("Test mainnet wallet existence", () { + // expect(monero_wallet_manager.isWalletExistSync(path: path), true); + // }); + // + // test("Test mainnet wallet deletion", () { + // // Remove wallet from wallet service + // walletService?.remove(name); + // walletsService?.removeWallet(walletId: name); + // expect(monero_wallet_manager.isWalletExistSync(path: path), false); + // }); + // }); + // + // group("Mainnet node tests", () { + // test("Test mainnet node connection", () async { + // await walletBase?.connectToNode( + // node: Node( + // uri: "monero-stagenet.stackwallet.com:38081", + // type: WalletType.moneroStageNet)); + // await walletBase!.rescan( + // height: + // credentials.height); // Probably shouldn't be rescanning from 0... + // await walletBase!.getNodeHeight(); + // int height = await walletBase!.getNodeHeight(); + // print('height: $height'); + // bool connected = await walletBase!.isConnected(); + // print('connected: $connected'); + // + // //expect... + // }); + // }); + // */ + // + // // TODO test deletion of wallets ... and delete them } -Future pathForWalletDir( - {required String name, required WalletType type}) async { - Directory root = (await getApplicationDocumentsDirectory()); - if (Platform.isIOS) { - root = (await getLibraryDirectory()); - } - final prefix = walletTypeToString(type).toLowerCase(); - final walletsDir = Directory('${root.path}/wallets'); - final walletDire = Directory('${walletsDir.path}/$prefix/$name'); - - if (!walletDire.existsSync()) { - walletDire.createSync(recursive: true); - } - - return walletDire.path; -} - -Future pathForWallet( - {required String name, required WalletType type}) async => - await pathForWalletDir(name: name, type: type) - .then((path) => path + '/$name'); +// Future pathForWalletDir( +// {required String name, required WalletType type}) async { +// Directory root = (await getApplicationDocumentsDirectory()); +// if (Platform.isIOS) { +// root = (await getLibraryDirectory()); +// } +// final prefix = walletTypeToString(type).toLowerCase(); +// final walletsDir = Directory('${root.path}/wallets'); +// final walletDire = Directory('${walletsDir.path}/$prefix/$name'); +// +// if (!walletDire.existsSync()) { +// walletDire.createSync(recursive: true); +// } +// +// return walletDire.path; +// } +// +// Future pathForWallet( +// {required String name, required WalletType type}) async => +// await pathForWalletDir(name: name, type: type) +// .then((path) => '$path/$name'); diff --git a/test/services/coins/namecoin/namecoin_wallet_test.dart b/test/services/coins/namecoin/namecoin_wallet_test.dart index 1975f66e0..3fa93b505 100644 --- a/test/services/coins/namecoin/namecoin_wallet_test.dart +++ b/test/services/coins/namecoin/namecoin_wallet_test.dart @@ -6,32 +6,35 @@ import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; -import 'package:stackwallet/hive/db.dart'; import 'package:stackwallet/models/paymint/fee_object_model.dart'; -import 'package:stackwallet/models/paymint/transactions_model.dart'; -import 'package:stackwallet/models/paymint/utxo_model.dart'; import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart'; -import 'package:stackwallet/services/price.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; -import 'package:tuple/tuple.dart'; -import 'namecoin_history_sample_data.dart'; -import 'namecoin_transaction_data_samples.dart'; -import 'namecoin_utxo_sample_data.dart'; import 'namecoin_wallet_test.mocks.dart'; import 'namecoin_wallet_test_parameters.dart'; -@GenerateMocks( - [ElectrumX, CachedElectrumX, PriceAPI, TransactionNotificationTracker]) +@GenerateMocks([ + ElectrumX, + CachedElectrumX, + TransactionNotificationTracker, +]) void main() { group("namecoin constants", () { test("namecoin minimum confirmations", () async { expect(MINIMUM_CONFIRMATIONS, 2); }); test("namecoin dust limit", () async { - expect(DUST_LIMIT, 546); + expect( + DUST_LIMIT, + Amount( + rawValue: BigInt.from(546), + fractionDigits: 8, + ), + ); }); test("namecoin mainnet genesis block hash", () async { expect(GENESIS_HASH_MAINNET, @@ -43,66 +46,9 @@ void main() { }); }); - test("namecoin DerivePathType enum", () { - expect(DerivePathType.values.length, 3); - expect(DerivePathType.values.toString(), - "[DerivePathType.bip44, DerivePathType.bip49, DerivePathType.bip84]"); - }); - - group("bip32 node/root", () { - test("getBip32Root", () { - final root = getBip32Root(TEST_MNEMONIC, namecoin); - expect(root.toWIF(), ROOT_WIF); - }); - - // test("getBip32NodeFromRoot", () { - // final root = getBip32Root(TEST_MNEMONIC, namecoin); - // // two mainnet - // final node44 = getBip32NodeFromRoot(0, 0, root, DerivePathType.bip44); - // expect(node44.toWIF(), NODE_WIF_44); - // final node49 = getBip32NodeFromRoot(0, 0, root, DerivePathType.bip49); - // expect(node49.toWIF(), NODE_WIF_49); - // // and one on testnet - // final node84 = getBip32NodeFromRoot( - // 0, 0, getBip32Root(TEST_MNEMONIC, testnet), DerivePathType.bip84); - // expect(node84.toWIF(), NODE_WIF_84); - // // a bad derive path - // bool didThrow = false; - // try { - // getBip32NodeFromRoot(0, 0, root, null); - // } catch (_) { - // didThrow = true; - // } - // expect(didThrow, true); - // // finally an invalid network - // didThrow = false; - // final invalidNetwork = NetworkType( - // messagePrefix: '\x18hello world\n', - // bech32: 'gg', - // bip32: Bip32Type(public: 0x055521e, private: 0x055555), - // pubKeyHash: 0x55, - // scriptHash: 0x55, - // wif: 0x00); - // try { - // getBip32NodeFromRoot(0, 0, getBip32Root(TEST_MNEMONIC, invalidNetwork), - // DerivePathType.bip44); - // } catch (_) { - // didThrow = true; - // } - // expect(didThrow, true); - // }); - - // test("basic getBip32Node", () { - // final node = - // getBip32Node(0, 0, TEST_MNEMONIC, testnet, DerivePathType.bip84); - // expect(node.toWIF(), NODE_WIF_84); - // }); - }); - group("validate mainnet namecoin addresses", () { MockElectrumX? client; MockCachedElectrumX? cachedClient; - MockPriceAPI? priceAPI; late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; @@ -111,7 +57,6 @@ void main() { setUp(() { client = MockElectrumX(); cachedClient = MockCachedElectrumX(); - priceAPI = MockPriceAPI(); secureStore = FakeSecureStorage(); tracker = MockTransactionNotificationTracker(); @@ -122,7 +67,6 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, ); }); @@ -136,7 +80,6 @@ void main() { verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("valid mainnet bech32 p2wpkh address type", () { @@ -148,7 +91,6 @@ void main() { verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("invalid bech32 address type", () { @@ -160,7 +102,6 @@ void main() { verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("address has no matching script", () { @@ -172,14 +113,12 @@ void main() { verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); }); group("testNetworkConnection", () { MockElectrumX? client; MockCachedElectrumX? cachedClient; - MockPriceAPI? priceAPI; late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; @@ -188,7 +127,6 @@ void main() { setUp(() { client = MockElectrumX(); cachedClient = MockCachedElectrumX(); - priceAPI = MockPriceAPI(); secureStore = FakeSecureStorage(); tracker = MockTransactionNotificationTracker(); @@ -199,7 +137,6 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, ); }); @@ -212,7 +149,6 @@ void main() { verify(client?.ping()).called(1); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("attempted connection fails due to exception", () async { @@ -223,7 +159,6 @@ void main() { verify(client?.ping()).called(1); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("attempted connection test success", () async { @@ -234,17 +169,16 @@ void main() { verify(client?.ping()).called(1); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); }); group("basic getters, setters, and functions", () { - final testWalletId = "NMCtestWalletID"; - final testWalletName = "NMCWallet"; + const testWalletId = "NMCtestWalletID"; + const testWalletName = "NMCWallet"; MockElectrumX? client; MockCachedElectrumX? cachedClient; - MockPriceAPI? priceAPI; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; @@ -253,7 +187,7 @@ void main() { setUp(() async { client = MockElectrumX(); cachedClient = MockCachedElectrumX(); - priceAPI = MockPriceAPI(); + secureStore = FakeSecureStorage(); tracker = MockTransactionNotificationTracker(); @@ -264,7 +198,6 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, ); }); @@ -274,7 +207,6 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("get networkType test", () async { @@ -285,14 +217,12 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, ); expect(Coin.namecoin, Coin.namecoin); expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("get cryptoCurrency", () async { @@ -300,7 +230,6 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("get coinName", () async { @@ -308,7 +237,6 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("get coinTicker", () async { @@ -316,7 +244,6 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("get and set walletName", () async { @@ -326,7 +253,6 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("estimateTxFee", () async { @@ -341,20 +267,19 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("get fees succeeds", () async { when(client?.ping()).thenAnswer((_) async => true); when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_MAINNET, "hash_function": "sha256", - "services": [] + "services": [] }); when(client?.estimateFee(blocks: 1)) .thenAnswer((realInvocation) async => Decimal.zero); @@ -375,20 +300,19 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("get fees fails", () async { when(client?.ping()).thenAnswer((_) async => true); when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_MAINNET, "hash_function": "sha256", - "services": [] + "services": [] }); when(client?.estimateFee(blocks: 1)) .thenAnswer((realInvocation) async => Decimal.zero); @@ -412,20 +336,19 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); // test("get maxFee", () async { // when(client?.ping()).thenAnswer((_) async => true); // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_TESTNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // when(client?.estimateFee(blocks: 20)) // .thenAnswer((realInvocation) async => Decimal.zero); @@ -444,19 +367,19 @@ void main() { // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); // verifyNoMoreInteractions(tracker); - // verifyNoMoreInteractions(priceAPI); + // // }); }); group("Namecoin service class functions that depend on shared storage", () { - final testWalletId = "NMCtestWalletID"; - final testWalletName = "NMCWallet"; + const testWalletId = "NMCtestWalletID"; + const testWalletName = "NMCWallet"; bool hiveAdaptersRegistered = false; MockElectrumX? client; MockCachedElectrumX? cachedClient; - MockPriceAPI? priceAPI; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; @@ -467,25 +390,13 @@ void main() { if (!hiveAdaptersRegistered) { hiveAdaptersRegistered = true; - // Registering Transaction Model Adapters - Hive.registerAdapter(TransactionDataAdapter()); - Hive.registerAdapter(TransactionChunkAdapter()); - Hive.registerAdapter(TransactionAdapter()); - Hive.registerAdapter(InputAdapter()); - Hive.registerAdapter(OutputAdapter()); - - // Registering Utxo Model Adapters - Hive.registerAdapter(UtxoDataAdapter()); - Hive.registerAdapter(UtxoObjectAdapter()); - Hive.registerAdapter(StatusAdapter()); - - final wallets = await Hive.openBox('wallets'); + final wallets = await Hive.openBox('wallets'); await wallets.put('currentWalletName', testWalletName); } client = MockElectrumX(); cachedClient = MockCachedElectrumX(); - priceAPI = MockPriceAPI(); + secureStore = FakeSecureStorage(); tracker = MockTransactionNotificationTracker(); @@ -496,7 +407,6 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, ); }); @@ -508,70 +418,70 @@ void main() { // verify(client?.ping()).called(1); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // test("initializeWallet no network exception", () async { // when(client?.ping()).thenThrow(Exception("Network connection failed")); - // final wallets = await Hive.openBox(testWalletId); + // final wallets = await Hive.openBox(testWalletId); // expect(await nmc?.initializeExisting(), false); // expect(secureStore.interactions, 0); // verify(client?.ping()).called(1); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); test("initializeWallet mainnet throws bad network", () async { when(client?.ping()).thenAnswer((_) async => true); when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_MAINNET, "hash_function": "sha256", - "services": [] + "services": [] }); // await nmc?.initializeNew(); - final wallets = await Hive.openBox(testWalletId); + await Hive.openBox(testWalletId); - expectLater(() => nmc?.initializeExisting(), throwsA(isA())) + await expectLater( + () => nmc?.initializeExisting(), throwsA(isA())) .then((_) { expect(secureStore.interactions, 0); // verify(client?.ping()).called(1); // verify(client?.getServerFeatures()).called(1); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); }); test("initializeWallet throws mnemonic overwrite exception", () async { when(client?.ping()).thenAnswer((_) async => true); when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_MAINNET, "hash_function": "sha256", - "services": [] + "services": [] }); await secureStore.write( key: "${testWalletId}_mnemonic", value: "some mnemonic"); - final wallets = await Hive.openBox(testWalletId); - expectLater(() => nmc?.initializeExisting(), throwsA(isA())) + await Hive.openBox(testWalletId); + await expectLater( + () => nmc?.initializeExisting(), throwsA(isA())) .then((_) { expect(secureStore.interactions, 1); // verify(client?.ping()).called(1); // verify(client?.getServerFeatures()).called(1); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); }); @@ -579,14 +489,14 @@ void main() { // "recoverFromMnemonic using empty seed on mainnet fails due to bad genesis hash match", // () async { // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_TESTNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // // bool hasThrown = false; @@ -606,21 +516,21 @@ void main() { // expect(secureStore.interactions, 0); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); test( "recoverFromMnemonic using empty seed on mainnet fails due to attempted overwrite of mnemonic", () async { when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_MAINNET, "hash_function": "sha256", - "services": [] + "services": [] }); await secureStore.write( @@ -643,808 +553,801 @@ void main() { expect(secureStore.interactions, 2); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); - test("recoverFromMnemonic using empty seed on mainnet succeeds", () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs4)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs5)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - await DB.instance.init(); - final wallet = await Hive.openBox(testWalletId); - bool hasThrown = false; - try { - await nmc?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - } catch (_) { - hasThrown = true; - } - expect(hasThrown, false); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); - - expect(secureStore.interactions, 20); - expect(secureStore.writes, 7); - expect(secureStore.reads, 13); - expect(secureStore.deletes, 0); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); - - test("get mnemonic list", () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs4)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs5)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - - final wallet = await Hive.openBox(testWalletId); - - await nmc?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - expect(await nmc?.mnemonic, TEST_MNEMONIC.split(" ")); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); - - test("recoverFromMnemonic using non empty seed on mainnet succeeds", - () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs4)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs5)) - .thenAnswer((_) async => historyBatchResponse); - - List dynamicArgValues = []; - - when(client?.getBatchHistory(args: anyNamed("args"))) - .thenAnswer((realInvocation) async { - if (realInvocation.namedArguments.values.first.length == 1) { - dynamicArgValues.add(realInvocation.namedArguments.values.first); - } - - return historyBatchResponse; - }); - - await Hive.openBox(testWalletId); - - bool hasThrown = false; - try { - await nmc?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - } catch (_) { - hasThrown = true; - } - expect(hasThrown, false); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); - - for (final arg in dynamicArgValues) { - final map = Map>.from(arg as Map); - - verify(client?.getBatchHistory(args: map)).called(1); - expect(activeScriptHashes.contains(map.values.first.first as String), - true); - } - - expect(secureStore.interactions, 14); - expect(secureStore.writes, 7); - expect(secureStore.reads, 7); - expect(secureStore.deletes, 0); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); - - test("fullRescan succeeds", () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs4)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs5)) - .thenAnswer((_) async => historyBatchResponse); - when(cachedClient?.clearSharedTransactionCache(coin: Coin.namecoin)) - .thenAnswer((realInvocation) async {}); - - when(client?.getBatchHistory(args: { - "0": [ - "dd63fc12f5e6c1ada2cf3c941d1648e6d561ce4024747bb2117d72112d83287c" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - when(client?.getBatchHistory(args: { - "0": [ - "86906979fc9107d06d560275d7de8305b69d7189c3206ac9070ad76e6abff874" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - when(client?.getBatchHistory(args: { - "0": [ - "c068e7fa4aa0b8a63114f6d11c047ca4be6a8fa333eb0dac48506e8f150af73b" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - when(client?.getBatchHistory(args: { - "0": [ - "cd3dd4abe4f9efc7149ba334d2d6790020331805b0bd5c7ed89a3ac6a22f10b9" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - when(client?.getBatchHistory(args: { - "0": [ - "587943864cefed4f1643a5ee2ce2b3c13a0c6ad7c435373f0ac328e144a15c1e" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - when(client?.getBatchHistory(args: { - "0": [ - "42d6e40636f4740f9c7f95ef0bbc2a4c17f54da2bc98a32a622e2bf73eb675c3" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - final wallet = await Hive.openBox(testWalletId); - - // restore so we have something to rescan - await nmc?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - // fetch valid wallet data - final preReceivingAddressesP2PKH = - await wallet.get('receivingAddressesP2PKH'); - final preReceivingAddressesP2SH = - await wallet.get('receivingAddressesP2SH'); - final preReceivingAddressesP2WPKH = - await wallet.get('receivingAddressesP2WPKH'); - final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); - final preChangeAddressesP2SH = await wallet.get('changeAddressesP2SH'); - final preChangeAddressesP2WPKH = - await wallet.get('changeAddressesP2WPKH'); - final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); - final preReceivingIndexP2SH = await wallet.get('receivingIndexP2SH'); - final preReceivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); - final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); - final preChangeIndexP2SH = await wallet.get('changeIndexP2SH'); - final preChangeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); - final preUtxoData = await wallet.get('latest_utxo_model'); - final preReceiveDerivationsStringP2PKH = await secureStore.read( - key: "${testWalletId}_receiveDerivationsP2PKH"); - final preChangeDerivationsStringP2PKH = - await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); - final preReceiveDerivationsStringP2SH = - await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); - final preChangeDerivationsStringP2SH = - await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); - final preReceiveDerivationsStringP2WPKH = await secureStore.read( - key: "${testWalletId}_receiveDerivationsP2WPKH"); - final preChangeDerivationsStringP2WPKH = await secureStore.read( - key: "${testWalletId}_changeDerivationsP2WPKH"); - - // destroy the data that the rescan will fix - await wallet.put( - 'receivingAddressesP2PKH', ["some address", "some other address"]); - await wallet.put( - 'receivingAddressesP2SH', ["some address", "some other address"]); - await wallet.put( - 'receivingAddressesP2WPKH', ["some address", "some other address"]); - await wallet - .put('changeAddressesP2PKH', ["some address", "some other address"]); - await wallet - .put('changeAddressesP2SH', ["some address", "some other address"]); - await wallet - .put('changeAddressesP2WPKH', ["some address", "some other address"]); - await wallet.put('receivingIndexP2PKH', 123); - await wallet.put('receivingIndexP2SH', 123); - await wallet.put('receivingIndexP2WPKH', 123); - await wallet.put('changeIndexP2PKH', 123); - await wallet.put('changeIndexP2SH', 123); - await wallet.put('changeIndexP2WPKH', 123); - await secureStore.write( - key: "${testWalletId}_receiveDerivationsP2PKH", value: "{}"); - await secureStore.write( - key: "${testWalletId}_changeDerivationsP2PKH", value: "{}"); - await secureStore.write( - key: "${testWalletId}_receiveDerivationsP2SH", value: "{}"); - await secureStore.write( - key: "${testWalletId}_changeDerivationsP2SH", value: "{}"); - await secureStore.write( - key: "${testWalletId}_receiveDerivationsP2WPKH", value: "{}"); - await secureStore.write( - key: "${testWalletId}_changeDerivationsP2WPKH", value: "{}"); - - bool hasThrown = false; - try { - await nmc?.fullRescan(2, 1000); - } catch (_) { - hasThrown = true; - } - expect(hasThrown, false); - - // fetch wallet data again - final receivingAddressesP2PKH = - await wallet.get('receivingAddressesP2PKH'); - final receivingAddressesP2SH = await wallet.get('receivingAddressesP2SH'); - final receivingAddressesP2WPKH = - await wallet.get('receivingAddressesP2WPKH'); - final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); - final changeAddressesP2SH = await wallet.get('changeAddressesP2SH'); - final changeAddressesP2WPKH = await wallet.get('changeAddressesP2WPKH'); - final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); - final receivingIndexP2SH = await wallet.get('receivingIndexP2SH'); - final receivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); - final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); - final changeIndexP2SH = await wallet.get('changeIndexP2SH'); - final changeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); - final utxoData = await wallet.get('latest_utxo_model'); - final receiveDerivationsStringP2PKH = await secureStore.read( - key: "${testWalletId}_receiveDerivationsP2PKH"); - final changeDerivationsStringP2PKH = - await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); - final receiveDerivationsStringP2SH = - await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); - final changeDerivationsStringP2SH = - await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); - final receiveDerivationsStringP2WPKH = await secureStore.read( - key: "${testWalletId}_receiveDerivationsP2WPKH"); - final changeDerivationsStringP2WPKH = await secureStore.read( - key: "${testWalletId}_changeDerivationsP2WPKH"); - - expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); - expect(preReceivingAddressesP2SH, receivingAddressesP2SH); - expect(preReceivingAddressesP2WPKH, receivingAddressesP2WPKH); - expect(preChangeAddressesP2PKH, changeAddressesP2PKH); - expect(preChangeAddressesP2SH, changeAddressesP2SH); - expect(preChangeAddressesP2WPKH, changeAddressesP2WPKH); - expect(preReceivingIndexP2PKH, receivingIndexP2PKH); - expect(preReceivingIndexP2SH, receivingIndexP2SH); - expect(preReceivingIndexP2WPKH, receivingIndexP2WPKH); - expect(preChangeIndexP2PKH, changeIndexP2PKH); - expect(preChangeIndexP2SH, changeIndexP2SH); - expect(preChangeIndexP2WPKH, changeIndexP2WPKH); - expect(preUtxoData, utxoData); - expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); - expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); - expect(preReceiveDerivationsStringP2SH, receiveDerivationsStringP2SH); - expect(preChangeDerivationsStringP2SH, changeDerivationsStringP2SH); - expect(preReceiveDerivationsStringP2WPKH, receiveDerivationsStringP2WPKH); - expect(preChangeDerivationsStringP2WPKH, changeDerivationsStringP2WPKH); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs4)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs5)).called(2); - verify(client?.getBatchHistory(args: { - "0": [ - "dd63fc12f5e6c1ada2cf3c941d1648e6d561ce4024747bb2117d72112d83287c" - ] - })).called(2); - verify(client?.getBatchHistory(args: { - "0": [ - "86906979fc9107d06d560275d7de8305b69d7189c3206ac9070ad76e6abff874" - ] - })).called(2); - - verify(client?.getBatchHistory(args: { - "0": [ - "c068e7fa4aa0b8a63114f6d11c047ca4be6a8fa333eb0dac48506e8f150af73b" - ] - })).called(2); - - verify(client?.getBatchHistory(args: { - "0": [ - "cd3dd4abe4f9efc7149ba334d2d6790020331805b0bd5c7ed89a3ac6a22f10b9" - ] - })).called(2); - - verify(client?.getBatchHistory(args: { - "0": [ - "587943864cefed4f1643a5ee2ce2b3c13a0c6ad7c435373f0ac328e144a15c1e" - ] - })).called(2); - - verify(client?.getBatchHistory(args: { - "0": [ - "42d6e40636f4740f9c7f95ef0bbc2a4c17f54da2bc98a32a622e2bf73eb675c3" - ] - })).called(2); - verify(cachedClient?.clearSharedTransactionCache(coin: Coin.namecoin)) - .called(1); - - // for (final arg in dynamicArgValues) { - // final map = Map>.from(arg as Map); - // Map argCount = {}; - // - // // verify(client?.getBatchHistory(args: map)).called(1); - // // expect(activeScriptHashes.contains(map.values.first.first as String), - // // true); - // } - - // Map argCount = {}; - // - // for (final arg in dynamicArgValues) { - // final map = Map>.from(arg as Map); - // - // final str = jsonEncode(map); - // - // if (argCount[str] == null) { - // argCount[str] = 1; - // } else { - // argCount[str] = argCount[str]! + 1; - // } - // } - // - // argCount.forEach((key, value) => print("arg: $key\ncount: $value")); - - expect(secureStore.writes, 25); - expect(secureStore.reads, 32); - expect(secureStore.deletes, 6); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); - - test("fullRescan fails", () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs4)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs5)) - .thenAnswer((_) async => historyBatchResponse); - - when(client?.getBatchHistory(args: { - "0": [ - "dd63fc12f5e6c1ada2cf3c941d1648e6d561ce4024747bb2117d72112d83287c" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - when(client?.getBatchHistory(args: { - "0": [ - "cd3dd4abe4f9efc7149ba334d2d6790020331805b0bd5c7ed89a3ac6a22f10b9" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - when(client?.getBatchHistory(args: { - "0": [ - "42d6e40636f4740f9c7f95ef0bbc2a4c17f54da2bc98a32a622e2bf73eb675c3" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - when(client?.getBatchHistory(args: { - "0": [ - "587943864cefed4f1643a5ee2ce2b3c13a0c6ad7c435373f0ac328e144a15c1e" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - when(client?.getBatchHistory(args: { - "0": [ - "86906979fc9107d06d560275d7de8305b69d7189c3206ac9070ad76e6abff874" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - when(client?.getBatchHistory(args: { - "0": [ - "c068e7fa4aa0b8a63114f6d11c047ca4be6a8fa333eb0dac48506e8f150af73b" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - when(cachedClient?.clearSharedTransactionCache(coin: Coin.namecoin)) - .thenAnswer((realInvocation) async {}); - - final wallet = await Hive.openBox(testWalletId); - - // restore so we have something to rescan - await nmc?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - // fetch wallet data - final preReceivingAddressesP2PKH = - await wallet.get('receivingAddressesP2PKH'); - final preReceivingAddressesP2SH = - await wallet.get('receivingAddressesP2SH'); - final preReceivingAddressesP2WPKH = - await wallet.get('receivingAddressesP2WPKH'); - final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); - final preChangeAddressesP2SH = await wallet.get('changeAddressesP2SH'); - final preChangeAddressesP2WPKH = - await wallet.get('changeAddressesP2WPKH'); - final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); - final preReceivingIndexP2SH = await wallet.get('receivingIndexP2SH'); - final preReceivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); - final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); - final preChangeIndexP2SH = await wallet.get('changeIndexP2SH'); - final preChangeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); - final preUtxoData = await wallet.get('latest_utxo_model'); - final preReceiveDerivationsStringP2PKH = await secureStore.read( - key: "${testWalletId}_receiveDerivationsP2PKH"); - final preChangeDerivationsStringP2PKH = - await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); - final preReceiveDerivationsStringP2SH = - await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); - final preChangeDerivationsStringP2SH = - await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); - final preReceiveDerivationsStringP2WPKH = await secureStore.read( - key: "${testWalletId}_receiveDerivationsP2WPKH"); - final preChangeDerivationsStringP2WPKH = await secureStore.read( - key: "${testWalletId}_changeDerivationsP2WPKH"); - - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenThrow(Exception("fake exception")); - - bool hasThrown = false; - try { - await nmc?.fullRescan(2, 1000); - } catch (_) { - hasThrown = true; - } - expect(hasThrown, true); - - // fetch wallet data again - final receivingAddressesP2PKH = - await wallet.get('receivingAddressesP2PKH'); - final receivingAddressesP2SH = await wallet.get('receivingAddressesP2SH'); - final receivingAddressesP2WPKH = - await wallet.get('receivingAddressesP2WPKH'); - final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); - final changeAddressesP2SH = await wallet.get('changeAddressesP2SH'); - final changeAddressesP2WPKH = await wallet.get('changeAddressesP2WPKH'); - final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); - final receivingIndexP2SH = await wallet.get('receivingIndexP2SH'); - final receivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); - final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); - final changeIndexP2SH = await wallet.get('changeIndexP2SH'); - final changeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); - final utxoData = await wallet.get('latest_utxo_model'); - final receiveDerivationsStringP2PKH = await secureStore.read( - key: "${testWalletId}_receiveDerivationsP2PKH"); - final changeDerivationsStringP2PKH = - await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); - final receiveDerivationsStringP2SH = - await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); - final changeDerivationsStringP2SH = - await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); - final receiveDerivationsStringP2WPKH = await secureStore.read( - key: "${testWalletId}_receiveDerivationsP2WPKH"); - final changeDerivationsStringP2WPKH = await secureStore.read( - key: "${testWalletId}_changeDerivationsP2WPKH"); - - expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); - expect(preReceivingAddressesP2SH, receivingAddressesP2SH); - expect(preReceivingAddressesP2WPKH, receivingAddressesP2WPKH); - expect(preChangeAddressesP2PKH, changeAddressesP2PKH); - expect(preChangeAddressesP2SH, changeAddressesP2SH); - expect(preChangeAddressesP2WPKH, changeAddressesP2WPKH); - expect(preReceivingIndexP2PKH, receivingIndexP2PKH); - expect(preReceivingIndexP2SH, receivingIndexP2SH); - expect(preReceivingIndexP2WPKH, receivingIndexP2WPKH); - expect(preChangeIndexP2PKH, changeIndexP2PKH); - expect(preChangeIndexP2SH, changeIndexP2SH); - expect(preChangeIndexP2WPKH, changeIndexP2WPKH); - expect(preUtxoData, utxoData); - expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); - expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); - expect(preReceiveDerivationsStringP2SH, receiveDerivationsStringP2SH); - expect(preChangeDerivationsStringP2SH, changeDerivationsStringP2SH); - expect(preReceiveDerivationsStringP2WPKH, receiveDerivationsStringP2WPKH); - expect(preChangeDerivationsStringP2WPKH, changeDerivationsStringP2WPKH); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs4)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs5)).called(2); - - verify(client?.getBatchHistory(args: { - "0": [ - "dd63fc12f5e6c1ada2cf3c941d1648e6d561ce4024747bb2117d72112d83287c" - ] - })).called(2); - verify(client?.getBatchHistory(args: { - "0": [ - "cd3dd4abe4f9efc7149ba334d2d6790020331805b0bd5c7ed89a3ac6a22f10b9" - ] - })).called(1); - verify(client?.getBatchHistory(args: { - "0": [ - "42d6e40636f4740f9c7f95ef0bbc2a4c17f54da2bc98a32a622e2bf73eb675c3" - ] - })).called(2); - verify(client?.getBatchHistory(args: { - "0": [ - "587943864cefed4f1643a5ee2ce2b3c13a0c6ad7c435373f0ac328e144a15c1e" - ] - })).called(2); - verify(client?.getBatchHistory(args: { - "0": [ - "86906979fc9107d06d560275d7de8305b69d7189c3206ac9070ad76e6abff874" - ] - })).called(2); - verify(client?.getBatchHistory(args: { - "0": [ - "c068e7fa4aa0b8a63114f6d11c047ca4be6a8fa333eb0dac48506e8f150af73b" - ] - })).called(2); - verify(cachedClient?.clearSharedTransactionCache(coin: Coin.namecoin)) - .called(1); - - expect(secureStore.writes, 19); - expect(secureStore.reads, 32); - expect(secureStore.deletes, 12); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); - - test("prepareSend fails", () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs4)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs5)) - .thenAnswer((_) async => historyBatchResponse); - - List dynamicArgValues = []; - - when(client?.getBatchHistory(args: anyNamed("args"))) - .thenAnswer((realInvocation) async { - if (realInvocation.namedArguments.values.first.length == 1) { - dynamicArgValues.add(realInvocation.namedArguments.values.first); - } - - return historyBatchResponse; - }); - - await Hive.openBox(testWalletId); - - when(cachedClient?.getTransaction( - txHash: - "dffa9543852197f9fb90f8adafaab8a0b9b4925e9ada8c6bdcaf00bf2e9f60d7", - coin: Coin.namecoin)) - .thenAnswer((_) async => tx2Raw); - when(cachedClient?.getTransaction( - txHash: - "71b56532e9e7321bd8c30d0f8b14530743049d2f3edd5623065c46eee1dda04d", - coin: Coin.namecoin)) - .thenAnswer((_) async => tx3Raw); - when(cachedClient?.getTransaction( - txHash: - "c7e700f7e23a85bbdd9de86d502322a933607ee7ea7e16adaf02e477cdd849b9", - coin: Coin.namecoin, - )).thenAnswer((_) async => tx4Raw); - - // recover to fill data - await nmc?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - // modify addresses to properly mock data to build a tx - final rcv44 = await secureStore.read( - key: testWalletId + "_receiveDerivationsP2PKH"); - await secureStore.write( - key: testWalletId + "_receiveDerivationsP2PKH", - value: rcv44?.replaceFirst("1RMSPixoLPuaXuhR2v4HsUMcRjLncKDaw", - "16FuTPaeRSPVxxCnwQmdyx2PQWxX6HWzhQ")); - final rcv49 = - await secureStore.read(key: testWalletId + "_receiveDerivationsP2SH"); - await secureStore.write( - key: testWalletId + "_receiveDerivationsP2SH", - value: rcv49?.replaceFirst("3AV74rKfibWmvX34F99yEvUcG4LLQ9jZZk", - "36NvZTcMsMowbt78wPzJaHHWaNiyR73Y4g")); - final rcv84 = await secureStore.read( - key: testWalletId + "_receiveDerivationsP2WPKH"); - await secureStore.write( - key: testWalletId + "_receiveDerivationsP2WPKH", - value: rcv84?.replaceFirst( - "bc1qggtj4ka8jsaj44hhd5mpamx7mp34m2d3w7k0m0", - "bc1q42lja79elem0anu8q8s3h2n687re9jax556pcc")); - - nmc?.outputsList = utxoList; - - bool didThrow = false; - try { - await nmc?.prepareSend( - address: "nc1q6k4x8ye6865z3rc8zkt8gyu52na7njqt6hsk4v", - satoshiAmount: 15000); - } catch (_) { - didThrow = true; - } - - expect(didThrow, true); - - verify(client?.getServerFeatures()).called(1); - - /// verify transaction no matching calls - - // verify(cachedClient?.getTransaction( - // txHash: - // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", - // coin: Coin.namecoin, - // callOutSideMainIsolate: false)) - // .called(1); - // verify(cachedClient?.getTransaction( - // txHash: - // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", - // coin: Coin.namecoin, - // callOutSideMainIsolate: false)) - // .called(1); - // verify(cachedClient?.getTransaction( - // txHash: - // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", - // coin: Coin.namecoin, - // callOutSideMainIsolate: false)) - // .called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); - - for (final arg in dynamicArgValues) { - final map = Map>.from(arg as Map); - - verify(client?.getBatchHistory(args: map)).called(1); - expect(activeScriptHashes.contains(map.values.first.first as String), - true); - } - - expect(secureStore.interactions, 20); - expect(secureStore.writes, 10); - expect(secureStore.reads, 10); - expect(secureStore.deletes, 0); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); + // test("recoverFromMnemonic using empty seed on mainnet succeeds", () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs4)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs5)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // await DB.instance.init(); + // await Hive.openBox(testWalletId); + // bool hasThrown = false; + // try { + // await nmc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // } catch (_) { + // hasThrown = true; + // } + // expect(hasThrown, false); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); + // + // expect(secureStore.interactions, 20); + // expect(secureStore.writes, 7); + // expect(secureStore.reads, 13); + // expect(secureStore.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("get mnemonic list", () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs4)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs5)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // + // await Hive.openBox(testWalletId); + // + // await nmc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // expect(await nmc?.mnemonic, TEST_MNEMONIC.split(" ")); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + + // test("recoverFromMnemonic using non empty seed on mainnet succeeds", + // () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs4)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs5)) + // .thenAnswer((_) async => historyBatchResponse); + // + // List dynamicArgValues = []; + // + // when(client?.getBatchHistory(args: anyNamed("args"))) + // .thenAnswer((realInvocation) async { + // if (realInvocation.namedArguments.values.first.length == 1) { + // dynamicArgValues.add(realInvocation.namedArguments.values.first); + // } + // + // return historyBatchResponse; + // }); + // + // await Hive.openBox(testWalletId); + // + // bool hasThrown = false; + // try { + // await nmc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // } catch (_) { + // hasThrown = true; + // } + // expect(hasThrown, false); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); + // + // for (final arg in dynamicArgValues) { + // final map = Map>.from(arg as Map); + // + // verify(client?.getBatchHistory(args: map)).called(1); + // expect(activeScriptHashes.contains(map.values.first.first as String), + // true); + // } + // + // expect(secureStore.interactions, 14); + // expect(secureStore.writes, 7); + // expect(secureStore.reads, 7); + // expect(secureStore.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("fullRescan succeeds", () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs4)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs5)) + // .thenAnswer((_) async => historyBatchResponse); + // when(cachedClient?.clearSharedTransactionCache(coin: Coin.namecoin)) + // .thenAnswer((realInvocation) async {}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "dd63fc12f5e6c1ada2cf3c941d1648e6d561ce4024747bb2117d72112d83287c" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "86906979fc9107d06d560275d7de8305b69d7189c3206ac9070ad76e6abff874" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "c068e7fa4aa0b8a63114f6d11c047ca4be6a8fa333eb0dac48506e8f150af73b" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "cd3dd4abe4f9efc7149ba334d2d6790020331805b0bd5c7ed89a3ac6a22f10b9" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // when(client?.getBatchHistory(args: { + // "0": [ + // "587943864cefed4f1643a5ee2ce2b3c13a0c6ad7c435373f0ac328e144a15c1e" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // when(client?.getBatchHistory(args: { + // "0": [ + // "42d6e40636f4740f9c7f95ef0bbc2a4c17f54da2bc98a32a622e2bf73eb675c3" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // final wallet = await Hive.openBox(testWalletId); + // + // // restore so we have something to rescan + // await nmc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // // fetch valid wallet data + // final preReceivingAddressesP2PKH = + // await wallet.get('receivingAddressesP2PKH'); + // final preReceivingAddressesP2SH = + // await wallet.get('receivingAddressesP2SH'); + // final preReceivingAddressesP2WPKH = + // await wallet.get('receivingAddressesP2WPKH'); + // final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + // final preChangeAddressesP2SH = await wallet.get('changeAddressesP2SH'); + // final preChangeAddressesP2WPKH = + // await wallet.get('changeAddressesP2WPKH'); + // final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + // final preReceivingIndexP2SH = await wallet.get('receivingIndexP2SH'); + // final preReceivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); + // final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + // final preChangeIndexP2SH = await wallet.get('changeIndexP2SH'); + // final preChangeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); + // final preUtxoData = await wallet.get('latest_utxo_model'); + // final preReceiveDerivationsStringP2PKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2PKH"); + // final preChangeDerivationsStringP2PKH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + // final preReceiveDerivationsStringP2SH = + // await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); + // final preChangeDerivationsStringP2SH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); + // final preReceiveDerivationsStringP2WPKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2WPKH"); + // final preChangeDerivationsStringP2WPKH = await secureStore.read( + // key: "${testWalletId}_changeDerivationsP2WPKH"); + // + // // destroy the data that the rescan will fix + // await wallet.put( + // 'receivingAddressesP2PKH', ["some address", "some other address"]); + // await wallet.put( + // 'receivingAddressesP2SH', ["some address", "some other address"]); + // await wallet.put( + // 'receivingAddressesP2WPKH', ["some address", "some other address"]); + // await wallet + // .put('changeAddressesP2PKH', ["some address", "some other address"]); + // await wallet + // .put('changeAddressesP2SH', ["some address", "some other address"]); + // await wallet + // .put('changeAddressesP2WPKH', ["some address", "some other address"]); + // await wallet.put('receivingIndexP2PKH', 123); + // await wallet.put('receivingIndexP2SH', 123); + // await wallet.put('receivingIndexP2WPKH', 123); + // await wallet.put('changeIndexP2PKH', 123); + // await wallet.put('changeIndexP2SH', 123); + // await wallet.put('changeIndexP2WPKH', 123); + // await secureStore.write( + // key: "${testWalletId}_receiveDerivationsP2PKH", value: "{}"); + // await secureStore.write( + // key: "${testWalletId}_changeDerivationsP2PKH", value: "{}"); + // await secureStore.write( + // key: "${testWalletId}_receiveDerivationsP2SH", value: "{}"); + // await secureStore.write( + // key: "${testWalletId}_changeDerivationsP2SH", value: "{}"); + // await secureStore.write( + // key: "${testWalletId}_receiveDerivationsP2WPKH", value: "{}"); + // await secureStore.write( + // key: "${testWalletId}_changeDerivationsP2WPKH", value: "{}"); + // + // bool hasThrown = false; + // try { + // await nmc?.fullRescan(2, 1000); + // } catch (_) { + // hasThrown = true; + // } + // expect(hasThrown, false); + // + // // fetch wallet data again + // final receivingAddressesP2PKH = + // await wallet.get('receivingAddressesP2PKH'); + // final receivingAddressesP2SH = await wallet.get('receivingAddressesP2SH'); + // final receivingAddressesP2WPKH = + // await wallet.get('receivingAddressesP2WPKH'); + // final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + // final changeAddressesP2SH = await wallet.get('changeAddressesP2SH'); + // final changeAddressesP2WPKH = await wallet.get('changeAddressesP2WPKH'); + // final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + // final receivingIndexP2SH = await wallet.get('receivingIndexP2SH'); + // final receivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); + // final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + // final changeIndexP2SH = await wallet.get('changeIndexP2SH'); + // final changeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); + // final utxoData = await wallet.get('latest_utxo_model'); + // final receiveDerivationsStringP2PKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2PKH"); + // final changeDerivationsStringP2PKH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + // final receiveDerivationsStringP2SH = + // await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); + // final changeDerivationsStringP2SH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); + // final receiveDerivationsStringP2WPKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2WPKH"); + // final changeDerivationsStringP2WPKH = await secureStore.read( + // key: "${testWalletId}_changeDerivationsP2WPKH"); + // + // expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); + // expect(preReceivingAddressesP2SH, receivingAddressesP2SH); + // expect(preReceivingAddressesP2WPKH, receivingAddressesP2WPKH); + // expect(preChangeAddressesP2PKH, changeAddressesP2PKH); + // expect(preChangeAddressesP2SH, changeAddressesP2SH); + // expect(preChangeAddressesP2WPKH, changeAddressesP2WPKH); + // expect(preReceivingIndexP2PKH, receivingIndexP2PKH); + // expect(preReceivingIndexP2SH, receivingIndexP2SH); + // expect(preReceivingIndexP2WPKH, receivingIndexP2WPKH); + // expect(preChangeIndexP2PKH, changeIndexP2PKH); + // expect(preChangeIndexP2SH, changeIndexP2SH); + // expect(preChangeIndexP2WPKH, changeIndexP2WPKH); + // expect(preUtxoData, utxoData); + // expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); + // expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); + // expect(preReceiveDerivationsStringP2SH, receiveDerivationsStringP2SH); + // expect(preChangeDerivationsStringP2SH, changeDerivationsStringP2SH); + // expect(preReceiveDerivationsStringP2WPKH, receiveDerivationsStringP2WPKH); + // expect(preChangeDerivationsStringP2WPKH, changeDerivationsStringP2WPKH); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(2); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "dd63fc12f5e6c1ada2cf3c941d1648e6d561ce4024747bb2117d72112d83287c" + // ] + // })).called(2); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "86906979fc9107d06d560275d7de8305b69d7189c3206ac9070ad76e6abff874" + // ] + // })).called(2); + // + // verify(client?.getBatchHistory(args: { + // "0": [ + // "c068e7fa4aa0b8a63114f6d11c047ca4be6a8fa333eb0dac48506e8f150af73b" + // ] + // })).called(2); + // + // verify(client?.getBatchHistory(args: { + // "0": [ + // "cd3dd4abe4f9efc7149ba334d2d6790020331805b0bd5c7ed89a3ac6a22f10b9" + // ] + // })).called(2); + // + // verify(client?.getBatchHistory(args: { + // "0": [ + // "587943864cefed4f1643a5ee2ce2b3c13a0c6ad7c435373f0ac328e144a15c1e" + // ] + // })).called(2); + // + // verify(client?.getBatchHistory(args: { + // "0": [ + // "42d6e40636f4740f9c7f95ef0bbc2a4c17f54da2bc98a32a622e2bf73eb675c3" + // ] + // })).called(2); + // verify(cachedClient?.clearSharedTransactionCache(coin: Coin.namecoin)) + // .called(1); + // + // // for (final arg in dynamicArgValues) { + // // final map = Map>.from(arg as Map); + // // Map argCount = {}; + // // + // // // verify(client?.getBatchHistory(args: map)).called(1); + // // // expect(activeScriptHashes.contains(map.values.first.first as String), + // // // true); + // // } + // + // // Map argCount = {}; + // // + // // for (final arg in dynamicArgValues) { + // // final map = Map>.from(arg as Map); + // // + // // final str = jsonEncode(map); + // // + // // if (argCount[str] == null) { + // // argCount[str] = 1; + // // } else { + // // argCount[str] = argCount[str]! + 1; + // // } + // // } + // // + // // argCount.forEach((key, value) => print("arg: $key\ncount: $value")); + // + // expect(secureStore.writes, 25); + // expect(secureStore.reads, 32); + // expect(secureStore.deletes, 6); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("fullRescan fails", () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs4)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs5)) + // .thenAnswer((_) async => historyBatchResponse); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "dd63fc12f5e6c1ada2cf3c941d1648e6d561ce4024747bb2117d72112d83287c" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "cd3dd4abe4f9efc7149ba334d2d6790020331805b0bd5c7ed89a3ac6a22f10b9" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "42d6e40636f4740f9c7f95ef0bbc2a4c17f54da2bc98a32a622e2bf73eb675c3" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "587943864cefed4f1643a5ee2ce2b3c13a0c6ad7c435373f0ac328e144a15c1e" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "86906979fc9107d06d560275d7de8305b69d7189c3206ac9070ad76e6abff874" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "c068e7fa4aa0b8a63114f6d11c047ca4be6a8fa333eb0dac48506e8f150af73b" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // when(cachedClient?.clearSharedTransactionCache(coin: Coin.namecoin)) + // .thenAnswer((realInvocation) async {}); + // + // final wallet = await Hive.openBox(testWalletId); + // + // // restore so we have something to rescan + // await nmc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // // fetch wallet data + // final preReceivingAddressesP2PKH = + // await wallet.get('receivingAddressesP2PKH'); + // final preReceivingAddressesP2SH = + // await wallet.get('receivingAddressesP2SH'); + // final preReceivingAddressesP2WPKH = + // await wallet.get('receivingAddressesP2WPKH'); + // final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + // final preChangeAddressesP2SH = await wallet.get('changeAddressesP2SH'); + // final preChangeAddressesP2WPKH = + // await wallet.get('changeAddressesP2WPKH'); + // final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + // final preReceivingIndexP2SH = await wallet.get('receivingIndexP2SH'); + // final preReceivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); + // final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + // final preChangeIndexP2SH = await wallet.get('changeIndexP2SH'); + // final preChangeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); + // final preUtxoData = await wallet.get('latest_utxo_model'); + // final preReceiveDerivationsStringP2PKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2PKH"); + // final preChangeDerivationsStringP2PKH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + // final preReceiveDerivationsStringP2SH = + // await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); + // final preChangeDerivationsStringP2SH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); + // final preReceiveDerivationsStringP2WPKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2WPKH"); + // final preChangeDerivationsStringP2WPKH = await secureStore.read( + // key: "${testWalletId}_changeDerivationsP2WPKH"); + // + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenThrow(Exception("fake exception")); + // + // bool hasThrown = false; + // try { + // await nmc?.fullRescan(2, 1000); + // } catch (_) { + // hasThrown = true; + // } + // expect(hasThrown, true); + // + // // fetch wallet data again + // final receivingAddressesP2PKH = + // await wallet.get('receivingAddressesP2PKH'); + // final receivingAddressesP2SH = await wallet.get('receivingAddressesP2SH'); + // final receivingAddressesP2WPKH = + // await wallet.get('receivingAddressesP2WPKH'); + // final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + // final changeAddressesP2SH = await wallet.get('changeAddressesP2SH'); + // final changeAddressesP2WPKH = await wallet.get('changeAddressesP2WPKH'); + // final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + // final receivingIndexP2SH = await wallet.get('receivingIndexP2SH'); + // final receivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); + // final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + // final changeIndexP2SH = await wallet.get('changeIndexP2SH'); + // final changeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); + // final utxoData = await wallet.get('latest_utxo_model'); + // final receiveDerivationsStringP2PKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2PKH"); + // final changeDerivationsStringP2PKH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + // final receiveDerivationsStringP2SH = + // await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); + // final changeDerivationsStringP2SH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); + // final receiveDerivationsStringP2WPKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2WPKH"); + // final changeDerivationsStringP2WPKH = await secureStore.read( + // key: "${testWalletId}_changeDerivationsP2WPKH"); + // + // expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); + // expect(preReceivingAddressesP2SH, receivingAddressesP2SH); + // expect(preReceivingAddressesP2WPKH, receivingAddressesP2WPKH); + // expect(preChangeAddressesP2PKH, changeAddressesP2PKH); + // expect(preChangeAddressesP2SH, changeAddressesP2SH); + // expect(preChangeAddressesP2WPKH, changeAddressesP2WPKH); + // expect(preReceivingIndexP2PKH, receivingIndexP2PKH); + // expect(preReceivingIndexP2SH, receivingIndexP2SH); + // expect(preReceivingIndexP2WPKH, receivingIndexP2WPKH); + // expect(preChangeIndexP2PKH, changeIndexP2PKH); + // expect(preChangeIndexP2SH, changeIndexP2SH); + // expect(preChangeIndexP2WPKH, changeIndexP2WPKH); + // expect(preUtxoData, utxoData); + // expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); + // expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); + // expect(preReceiveDerivationsStringP2SH, receiveDerivationsStringP2SH); + // expect(preChangeDerivationsStringP2SH, changeDerivationsStringP2SH); + // expect(preReceiveDerivationsStringP2WPKH, receiveDerivationsStringP2WPKH); + // expect(preChangeDerivationsStringP2WPKH, changeDerivationsStringP2WPKH); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(2); + // + // verify(client?.getBatchHistory(args: { + // "0": [ + // "dd63fc12f5e6c1ada2cf3c941d1648e6d561ce4024747bb2117d72112d83287c" + // ] + // })).called(2); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "cd3dd4abe4f9efc7149ba334d2d6790020331805b0bd5c7ed89a3ac6a22f10b9" + // ] + // })).called(1); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "42d6e40636f4740f9c7f95ef0bbc2a4c17f54da2bc98a32a622e2bf73eb675c3" + // ] + // })).called(2); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "587943864cefed4f1643a5ee2ce2b3c13a0c6ad7c435373f0ac328e144a15c1e" + // ] + // })).called(2); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "86906979fc9107d06d560275d7de8305b69d7189c3206ac9070ad76e6abff874" + // ] + // })).called(2); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "c068e7fa4aa0b8a63114f6d11c047ca4be6a8fa333eb0dac48506e8f150af73b" + // ] + // })).called(2); + // verify(cachedClient?.clearSharedTransactionCache(coin: Coin.namecoin)) + // .called(1); + // + // expect(secureStore.writes, 19); + // expect(secureStore.reads, 32); + // expect(secureStore.deletes, 12); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("prepareSend fails", () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs4)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs5)) + // .thenAnswer((_) async => historyBatchResponse); + // + // List dynamicArgValues = []; + // + // when(client?.getBatchHistory(args: anyNamed("args"))) + // .thenAnswer((realInvocation) async { + // if (realInvocation.namedArguments.values.first.length == 1) { + // dynamicArgValues.add(realInvocation.namedArguments.values.first); + // } + // + // return historyBatchResponse; + // }); + // + // await Hive.openBox(testWalletId); + // + // when(cachedClient?.getTransaction( + // txHash: + // "dffa9543852197f9fb90f8adafaab8a0b9b4925e9ada8c6bdcaf00bf2e9f60d7", + // coin: Coin.namecoin)) + // .thenAnswer((_) async => tx2Raw); + // when(cachedClient?.getTransaction( + // txHash: + // "71b56532e9e7321bd8c30d0f8b14530743049d2f3edd5623065c46eee1dda04d", + // coin: Coin.namecoin)) + // .thenAnswer((_) async => tx3Raw); + // when(cachedClient?.getTransaction( + // txHash: + // "c7e700f7e23a85bbdd9de86d502322a933607ee7ea7e16adaf02e477cdd849b9", + // coin: Coin.namecoin, + // )).thenAnswer((_) async => tx4Raw); + // + // // recover to fill data + // await nmc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // // modify addresses to properly mock data to build a tx + // final rcv44 = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2PKH"); + // await secureStore.write( + // key: "${testWalletId}_receiveDerivationsP2PKH", + // value: rcv44?.replaceFirst("1RMSPixoLPuaXuhR2v4HsUMcRjLncKDaw", + // "16FuTPaeRSPVxxCnwQmdyx2PQWxX6HWzhQ")); + // final rcv49 = + // await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); + // await secureStore.write( + // key: "${testWalletId}_receiveDerivationsP2SH", + // value: rcv49?.replaceFirst("3AV74rKfibWmvX34F99yEvUcG4LLQ9jZZk", + // "36NvZTcMsMowbt78wPzJaHHWaNiyR73Y4g")); + // final rcv84 = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2WPKH"); + // await secureStore.write( + // key: "${testWalletId}_receiveDerivationsP2WPKH", + // value: rcv84?.replaceFirst( + // "bc1qggtj4ka8jsaj44hhd5mpamx7mp34m2d3w7k0m0", + // "bc1q42lja79elem0anu8q8s3h2n687re9jax556pcc")); + // + // // nmc?.outputsList = utxoList; + // + // bool didThrow = false; + // try { + // await nmc?.prepareSend( + // address: "nc1q6k4x8ye6865z3rc8zkt8gyu52na7njqt6hsk4v", + // satoshiAmount: 15000); + // } catch (_) { + // didThrow = true; + // } + // + // expect(didThrow, true); + // + // verify(client?.getServerFeatures()).called(1); + // + // /// verify transaction no matching calls + // + // // verify(cachedClient?.getTransaction( + // // txHash: + // // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // // coin: Coin.namecoin, + // // callOutSideMainIsolate: false)) + // // .called(1); + // // verify(cachedClient?.getTransaction( + // // txHash: + // // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // // coin: Coin.namecoin, + // // callOutSideMainIsolate: false)) + // // .called(1); + // // verify(cachedClient?.getTransaction( + // // txHash: + // // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // // coin: Coin.namecoin, + // // callOutSideMainIsolate: false)) + // // .called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); + // + // for (final arg in dynamicArgValues) { + // final map = Map>.from(arg as Map); + // + // verify(client?.getBatchHistory(args: map)).called(1); + // expect(activeScriptHashes.contains(map.values.first.first as String), + // true); + // } + // + // expect(secureStore.interactions, 20); + // expect(secureStore.writes, 10); + // expect(secureStore.reads, 10); + // expect(secureStore.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); test("confirmSend no hex", () async { bool didThrow = false; @@ -1459,7 +1362,6 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("confirmSend hex is not string", () async { @@ -1475,7 +1377,6 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("confirmSend hex is string but missing other data", () async { @@ -1495,7 +1396,6 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("confirmSend fails due to vSize being greater than fee", () async { @@ -1516,7 +1416,6 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("confirmSend fails when broadcast transactions throws", () async { @@ -1542,7 +1441,6 @@ void main() { verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); // // // this test will create a non mocked electrumx client that will try to connect @@ -1555,12 +1453,13 @@ void main() { // // networkType: BasicNetworkType.test, // // client: client, // // cachedClient: cachedClient, - // // priceAPI: priceAPI, + // // // // secureStore: secureStore, + // // // ); // // // // // set node - // // final wallet = await Hive.openBox(testWalletId); + // // final wallet = await Hive.openBox(testWalletId); // // await wallet.put("nodes", { // // "default": { // // "id": "some nodeID", @@ -1591,150 +1490,144 @@ void main() { // // expect(secureStore.interactions, 0); // // verifyNoMoreInteractions(client); // // verifyNoMoreInteractions(cachedClient); - // // verifyNoMoreInteractions(priceAPI); + // // // // }); - test("refresh wallet mutex locked", () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs4)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs5)) - .thenAnswer((_) async => historyBatchResponse); - - List dynamicArgValues = []; - - when(client?.getBatchHistory(args: anyNamed("args"))) - .thenAnswer((realInvocation) async { - if (realInvocation.namedArguments.values.first.length == 1) { - dynamicArgValues.add(realInvocation.namedArguments.values.first); - } - - return historyBatchResponse; - }); - - await Hive.openBox(testWalletId); - - // recover to fill data - await nmc?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - nmc?.refreshMutex = true; - - await nmc?.refresh(); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); - - for (final arg in dynamicArgValues) { - final map = Map>.from(arg as Map); - - verify(client?.getBatchHistory(args: map)).called(1); - expect(activeScriptHashes.contains(map.values.first.first as String), - true); - } - - expect(secureStore.interactions, 14); - expect(secureStore.writes, 7); - expect(secureStore.reads, 7); - expect(secureStore.deletes, 0); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); - }); - - test("refresh wallet normally", () async { - when(client?.getBlockHeadTip()).thenAnswer((realInvocation) async => - {"height": 520481, "hex": "some block hex"}); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getHistory(scripthash: anyNamed("scripthash"))) - .thenAnswer((_) async => []); - when(client?.estimateFee(blocks: anyNamed("blocks"))) - .thenAnswer((_) async => Decimal.one); - - when(priceAPI?.getPricesAnd24hChange(baseCurrency: "USD")) - .thenAnswer((_) async => {Coin.namecoin: Tuple2(Decimal.one, 0.3)}); - - final List dynamicArgValues = []; - - when(client?.getBatchHistory(args: anyNamed("args"))) - .thenAnswer((realInvocation) async { - dynamicArgValues.add(realInvocation.namedArguments.values.first); - return historyBatchResponse; - }); - - await Hive.openBox(testWalletId); - - // recover to fill data - await nmc?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - when(client?.getBatchHistory(args: anyNamed("args"))) - .thenAnswer((_) async => {}); - when(client?.getBatchUTXOs(args: anyNamed("args"))) - .thenAnswer((_) async => emptyHistoryBatchResponse); - - await nmc?.refresh(); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(4); - verify(client?.estimateFee(blocks: anyNamed("blocks"))).called(3); - verify(client?.getBlockHeadTip()).called(1); - verify(priceAPI?.getPricesAnd24hChange(baseCurrency: "USD")).called(2); - - for (final arg in dynamicArgValues) { - final map = Map>.from(arg as Map); - - verify(client?.getBatchHistory(args: map)).called(1); - } - - expect(secureStore.interactions, 14); - expect(secureStore.writes, 7); - expect(secureStore.reads, 7); - expect(secureStore.deletes, 0); - - // verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); + // test("refresh wallet mutex locked", () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs4)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs5)) + // .thenAnswer((_) async => historyBatchResponse); + // + // List dynamicArgValues = []; + // + // when(client?.getBatchHistory(args: anyNamed("args"))) + // .thenAnswer((realInvocation) async { + // if (realInvocation.namedArguments.values.first.length == 1) { + // dynamicArgValues.add(realInvocation.namedArguments.values.first); + // } + // + // return historyBatchResponse; + // }); + // + // await Hive.openBox(testWalletId); + // + // // recover to fill data + // await nmc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // nmc?.refreshMutex = true; + // + // await nmc?.refresh(); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs4)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs5)).called(1); + // + // for (final arg in dynamicArgValues) { + // final map = Map>.from(arg as Map); + // + // verify(client?.getBatchHistory(args: map)).called(1); + // expect(activeScriptHashes.contains(map.values.first.first as String), + // true); + // } + // + // expect(secureStore.interactions, 14); + // expect(secureStore.writes, 7); + // expect(secureStore.reads, 7); + // expect(secureStore.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // }); + // + // test("refresh wallet normally", () async { + // when(client?.getBlockHeadTip()).thenAnswer((realInvocation) async => + // {"height": 520481, "hex": "some block hex"}); + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getHistory(scripthash: anyNamed("scripthash"))) + // .thenAnswer((_) async => []); + // when(client?.estimateFee(blocks: anyNamed("blocks"))) + // .thenAnswer((_) async => Decimal.one); + // + // final List dynamicArgValues = []; + // + // when(client?.getBatchHistory(args: anyNamed("args"))) + // .thenAnswer((realInvocation) async { + // dynamicArgValues.add(realInvocation.namedArguments.values.first); + // return historyBatchResponse; + // }); + // + // await Hive.openBox(testWalletId); + // + // // recover to fill data + // await nmc?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // when(client?.getBatchHistory(args: anyNamed("args"))) + // .thenAnswer((_) async => {}); + // when(client?.getBatchUTXOs(args: anyNamed("args"))) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // + // await nmc?.refresh(); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(4); + // verify(client?.estimateFee(blocks: anyNamed("blocks"))).called(3); + // verify(client?.getBlockHeadTip()).called(1); + // + // for (final arg in dynamicArgValues) { + // final map = Map>.from(arg as Map); + // + // verify(client?.getBatchHistory(args: map)).called(1); + // } + // + // expect(secureStore.interactions, 14); + // expect(secureStore.writes, 7); + // expect(secureStore.reads, 7); + // expect(secureStore.deletes, 0); + // + // // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); tearDown(() async { await tearDownTestHive(); diff --git a/test/services/coins/namecoin/namecoin_wallet_test.mocks.dart b/test/services/coins/namecoin/namecoin_wallet_test.mocks.dart index 91c3e5bfa..3e9fd4fde 100644 --- a/test/services/coins/namecoin/namecoin_wallet_test.mocks.dart +++ b/test/services/coins/namecoin/namecoin_wallet_test.mocks.dart @@ -3,19 +3,16 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i6; +import 'dart:async' as _i5; import 'package:decimal/decimal.dart' as _i2; -import 'package:http/http.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i7; -import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i5; -import 'package:stackwallet/services/price.dart' as _i9; +import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i6; +import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i4; import 'package:stackwallet/services/transaction_notification_tracker.dart' - as _i11; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i8; + as _i8; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i7; import 'package:stackwallet/utilities/prefs.dart' as _i3; -import 'package:tuple/tuple.dart' as _i10; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -48,26 +45,16 @@ class _FakePrefs_1 extends _i1.SmartFake implements _i3.Prefs { ); } -class _FakeClient_2 extends _i1.SmartFake implements _i4.Client { - _FakeClient_2( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - /// A class which mocks [ElectrumX]. /// /// See the documentation for Mockito's code generation for more information. -class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { +class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { MockElectrumX() { _i1.throwOnMissingStub(this); } @override - set failovers(List<_i5.ElectrumXNode>? _failovers) => super.noSuchMethod( + set failovers(List<_i4.ElectrumXNode>? _failovers) => super.noSuchMethod( Invocation.setter( #failovers, _failovers, @@ -103,7 +90,7 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { returnValue: false, ) as bool); @override - _i6.Future request({ + _i5.Future request({ required String? command, List? args = const [], Duration? connectionTimeout = const Duration(seconds: 60), @@ -122,10 +109,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #retries: retries, }, ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i5.Future.value(), + ) as _i5.Future); @override - _i6.Future>> batchRequest({ + _i5.Future>> batchRequest({ required String? command, required Map>? args, Duration? connectionTimeout = const Duration(seconds: 60), @@ -142,11 +129,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #retries: retries, }, ), - returnValue: _i6.Future>>.value( + returnValue: _i5.Future>>.value( >[]), - ) as _i6.Future>>); + ) as _i5.Future>>); @override - _i6.Future ping({ + _i5.Future ping({ String? requestID, int? retryCount = 1, }) => @@ -159,10 +146,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #retryCount: retryCount, }, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i5.Future.value(false), + ) as _i5.Future); @override - _i6.Future> getBlockHeadTip({String? requestID}) => + _i5.Future> getBlockHeadTip({String? requestID}) => (super.noSuchMethod( Invocation.method( #getBlockHeadTip, @@ -170,10 +157,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future> getServerFeatures({String? requestID}) => + _i5.Future> getServerFeatures({String? requestID}) => (super.noSuchMethod( Invocation.method( #getServerFeatures, @@ -181,10 +168,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future broadcastTransaction({ + _i5.Future broadcastTransaction({ required String? rawTx, String? requestID, }) => @@ -197,10 +184,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #requestID: requestID, }, ), - returnValue: _i6.Future.value(''), - ) as _i6.Future); + returnValue: _i5.Future.value(''), + ) as _i5.Future); @override - _i6.Future> getBalance({ + _i5.Future> getBalance({ required String? scripthash, String? requestID, }) => @@ -214,10 +201,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future>> getHistory({ + _i5.Future>> getHistory({ required String? scripthash, String? requestID, }) => @@ -230,11 +217,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #requestID: requestID, }, ), - returnValue: _i6.Future>>.value( + returnValue: _i5.Future>>.value( >[]), - ) as _i6.Future>>); + ) as _i5.Future>>); @override - _i6.Future>>> getBatchHistory( + _i5.Future>>> getBatchHistory( {required Map>? args}) => (super.noSuchMethod( Invocation.method( @@ -242,11 +229,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { [], {#args: args}, ), - returnValue: _i6.Future>>>.value( + returnValue: _i5.Future>>>.value( >>{}), - ) as _i6.Future>>>); + ) as _i5.Future>>>); @override - _i6.Future>> getUTXOs({ + _i5.Future>> getUTXOs({ required String? scripthash, String? requestID, }) => @@ -259,11 +246,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #requestID: requestID, }, ), - returnValue: _i6.Future>>.value( + returnValue: _i5.Future>>.value( >[]), - ) as _i6.Future>>); + ) as _i5.Future>>); @override - _i6.Future>>> getBatchUTXOs( + _i5.Future>>> getBatchUTXOs( {required Map>? args}) => (super.noSuchMethod( Invocation.method( @@ -271,11 +258,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { [], {#args: args}, ), - returnValue: _i6.Future>>>.value( + returnValue: _i5.Future>>>.value( >>{}), - ) as _i6.Future>>>); + ) as _i5.Future>>>); @override - _i6.Future> getTransaction({ + _i5.Future> getTransaction({ required String? txHash, bool? verbose = true, String? requestID, @@ -291,10 +278,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future> getAnonymitySet({ + _i5.Future> getAnonymitySet({ String? groupId = r'1', String? blockhash = r'', String? requestID, @@ -310,10 +297,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future getMintData({ + _i5.Future getMintData({ dynamic mints, String? requestID, }) => @@ -326,10 +313,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #requestID: requestID, }, ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i5.Future.value(), + ) as _i5.Future); @override - _i6.Future> getUsedCoinSerials({ + _i5.Future> getUsedCoinSerials({ String? requestID, required int? startNumber, }) => @@ -343,19 +330,19 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future getLatestCoinId({String? requestID}) => (super.noSuchMethod( + _i5.Future getLatestCoinId({String? requestID}) => (super.noSuchMethod( Invocation.method( #getLatestCoinId, [], {#requestID: requestID}, ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i5.Future.value(0), + ) as _i5.Future); @override - _i6.Future> getFeeRate({String? requestID}) => + _i5.Future> getFeeRate({String? requestID}) => (super.noSuchMethod( Invocation.method( #getFeeRate, @@ -363,10 +350,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future<_i2.Decimal> estimateFee({ + _i5.Future<_i2.Decimal> estimateFee({ String? requestID, required int? blocks, }) => @@ -379,7 +366,7 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #blocks: blocks, }, ), - returnValue: _i6.Future<_i2.Decimal>.value(_FakeDecimal_0( + returnValue: _i5.Future<_i2.Decimal>.value(_FakeDecimal_0( this, Invocation.method( #estimateFee, @@ -390,15 +377,15 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), )), - ) as _i6.Future<_i2.Decimal>); + ) as _i5.Future<_i2.Decimal>); @override - _i6.Future<_i2.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( + _i5.Future<_i2.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( Invocation.method( #relayFee, [], {#requestID: requestID}, ), - returnValue: _i6.Future<_i2.Decimal>.value(_FakeDecimal_0( + returnValue: _i5.Future<_i2.Decimal>.value(_FakeDecimal_0( this, Invocation.method( #relayFee, @@ -406,13 +393,13 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { {#requestID: requestID}, ), )), - ) as _i6.Future<_i2.Decimal>); + ) as _i5.Future<_i2.Decimal>); } /// A class which mocks [CachedElectrumX]. /// /// See the documentation for Mockito's code generation for more information. -class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { +class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { MockCachedElectrumX() { _i1.throwOnMissingStub(this); } @@ -441,15 +428,15 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { ), ) as _i3.Prefs); @override - List<_i5.ElectrumXNode> get failovers => (super.noSuchMethod( + List<_i4.ElectrumXNode> get failovers => (super.noSuchMethod( Invocation.getter(#failovers), - returnValue: <_i5.ElectrumXNode>[], - ) as List<_i5.ElectrumXNode>); + returnValue: <_i4.ElectrumXNode>[], + ) as List<_i4.ElectrumXNode>); @override - _i6.Future> getAnonymitySet({ + _i5.Future> getAnonymitySet({ required String? groupId, String? blockhash = r'', - required _i8.Coin? coin, + required _i7.Coin? coin, }) => (super.noSuchMethod( Invocation.method( @@ -462,8 +449,8 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override String base64ToHex(String? source) => (super.noSuchMethod( Invocation.method( @@ -481,9 +468,9 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { returnValue: '', ) as String); @override - _i6.Future> getTransaction({ + _i5.Future> getTransaction({ required String? txHash, - required _i8.Coin? coin, + required _i7.Coin? coin, bool? verbose = true, }) => (super.noSuchMethod( @@ -497,11 +484,11 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future> getUsedCoinSerials({ - required _i8.Coin? coin, + _i5.Future> getUsedCoinSerials({ + required _i7.Coin? coin, int? startNumber = 0, }) => (super.noSuchMethod( @@ -513,66 +500,26 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { #startNumber: startNumber, }, ), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i5.Future>.value([]), + ) as _i5.Future>); @override - _i6.Future clearSharedTransactionCache({required _i8.Coin? coin}) => + _i5.Future clearSharedTransactionCache({required _i7.Coin? coin}) => (super.noSuchMethod( Invocation.method( #clearSharedTransactionCache, [], {#coin: coin}, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); -} - -/// A class which mocks [PriceAPI]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockPriceAPI extends _i1.Mock implements _i9.PriceAPI { - MockPriceAPI() { - _i1.throwOnMissingStub(this); - } - - @override - _i4.Client get client => (super.noSuchMethod( - Invocation.getter(#client), - returnValue: _FakeClient_2( - this, - Invocation.getter(#client), - ), - ) as _i4.Client); - @override - void resetLastCalledToForceNextCallToUpdateCache() => super.noSuchMethod( - Invocation.method( - #resetLastCalledToForceNextCallToUpdateCache, - [], - ), - returnValueForMissingStub: null, - ); - @override - _i6.Future< - Map<_i8.Coin, _i10.Tuple2<_i2.Decimal, double>>> getPricesAnd24hChange( - {required String? baseCurrency}) => - (super.noSuchMethod( - Invocation.method( - #getPricesAnd24hChange, - [], - {#baseCurrency: baseCurrency}, - ), - returnValue: - _i6.Future>>.value( - <_i8.Coin, _i10.Tuple2<_i2.Decimal, double>>{}), - ) as _i6.Future>>); + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); } /// A class which mocks [TransactionNotificationTracker]. /// /// See the documentation for Mockito's code generation for more information. class MockTransactionNotificationTracker extends _i1.Mock - implements _i11.TransactionNotificationTracker { + implements _i8.TransactionNotificationTracker { MockTransactionNotificationTracker() { _i1.throwOnMissingStub(this); } @@ -601,14 +548,14 @@ class MockTransactionNotificationTracker extends _i1.Mock returnValue: false, ) as bool); @override - _i6.Future addNotifiedPending(String? txid) => (super.noSuchMethod( + _i5.Future addNotifiedPending(String? txid) => (super.noSuchMethod( Invocation.method( #addNotifiedPending, [txid], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); @override bool wasNotifiedConfirmed(String? txid) => (super.noSuchMethod( Invocation.method( @@ -618,12 +565,21 @@ class MockTransactionNotificationTracker extends _i1.Mock returnValue: false, ) as bool); @override - _i6.Future addNotifiedConfirmed(String? txid) => (super.noSuchMethod( + _i5.Future addNotifiedConfirmed(String? txid) => (super.noSuchMethod( Invocation.method( #addNotifiedConfirmed, [txid], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future deleteTransaction(String? txid) => (super.noSuchMethod( + Invocation.method( + #deleteTransaction, + [txid], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); } diff --git a/test/services/coins/particl/particl_wallet_test.dart b/test/services/coins/particl/particl_wallet_test.dart index bbe34a5ec..41eadff70 100644 --- a/test/services/coins/particl/particl_wallet_test.dart +++ b/test/services/coins/particl/particl_wallet_test.dart @@ -6,22 +6,22 @@ import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart'; import 'package:stackwallet/electrumx_rpc/electrumx.dart'; -import 'package:stackwallet/models/models.dart'; +import 'package:stackwallet/models/paymint/fee_object_model.dart'; import 'package:stackwallet/services/coins/particl/particl_wallet.dart'; -import 'package:stackwallet/services/price.dart'; import 'package:stackwallet/services/transaction_notification_tracker.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; +import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; -import 'package:tuple/tuple.dart'; -import 'particl_history_sample_data.dart'; -import 'particl_transaction_data_samples.dart'; -import 'particl_utxo_sample_data.dart'; import 'particl_wallet_test.mocks.dart'; import 'particl_wallet_test_parameters.dart'; -@GenerateMocks( - [ElectrumX, CachedElectrumX, PriceAPI, TransactionNotificationTracker]) +@GenerateMocks([ + ElectrumX, + CachedElectrumX, + TransactionNotificationTracker, +]) void main() { group("particl constants", () { test("particl minimum confirmations", () async { @@ -29,7 +29,13 @@ void main() { 1); // TODO confirm particl minimum confirmations }); test("particl dust limit", () async { - expect(DUST_LIMIT, 294); // TODO confirm particl dust limit + expect( + DUST_LIMIT, + Amount( + rawValue: BigInt.from(294), + fractionDigits: 8, + ), + ); // TODO confirm particl dust limit }); test("particl mainnet genesis block hash", () async { expect(GENESIS_HASH_MAINNET, @@ -41,66 +47,10 @@ void main() { }); }); - test("particl DerivePathType enum", () { - expect(DerivePathType.values.length, 2); - expect(DerivePathType.values.toString(), - "[DerivePathType.bip44, DerivePathType.bip84]"); - }); - - group("bip32 node/root", () { - test("getBip32Root", () { - final root = getBip32Root(TEST_MNEMONIC, particl); - expect(root.toWIF(), ROOT_WIF); - }); - - // test("getBip32NodeFromRoot", () { - // final root = getBip32Root(TEST_MNEMONIC, particl); - // // two mainnet - // final node44 = getBip32NodeFromRoot(0, 0, root, DerivePathType.bip44); - // expect(node44.toWIF(), NODE_WIF_44); - // final node49 = getBip32NodeFromRoot(0, 0, root, DerivePathType.bip49); - // expect(node49.toWIF(), NODE_WIF_49); - // // and one on testnet - // final node84 = getBip32NodeFromRoot( - // 0, 0, getBip32Root(TEST_MNEMONIC, testnet), DerivePathType.bip84); - // expect(node84.toWIF(), NODE_WIF_84); - // // a bad derive path - // bool didThrow = false; - // try { - // getBip32NodeFromRoot(0, 0, root, null); - // } catch (_) { - // didThrow = true; - // } - // expect(didThrow, true); - // // finally an invalid network - // didThrow = false; - // final invalidNetwork = NetworkType( - // messagePrefix: '\x18hello world\n', - // bech32: 'gg', - // bip32: Bip32Type(public: 0x055521e, private: 0x055555), - // pubKeyHash: 0x55, - // scriptHash: 0x55, - // wif: 0x00); - // try { - // getBip32NodeFromRoot(0, 0, getBip32Root(TEST_MNEMONIC, invalidNetwork), - // DerivePathType.bip44); - // } catch (_) { - // didThrow = true; - // } - // expect(didThrow, true); - // }); - //TODO Testnet not setup - // test("basic getBip32Node", () { - // final node = - // getBip32Node(0, 0, TEST_MNEMONIC, testnet, DerivePathType.bip84); - // expect(node.toWIF(), NODE_WIF_84); - // }); - }); - group("validate mainnet particl addresses", () { MockElectrumX? client; MockCachedElectrumX? cachedClient; - MockPriceAPI? priceAPI; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; @@ -110,7 +60,7 @@ void main() { setUp(() { client = MockElectrumX(); cachedClient = MockCachedElectrumX(); - priceAPI = MockPriceAPI(); + secureStore = FakeSecureStorage(); tracker = MockTransactionNotificationTracker(); @@ -121,7 +71,6 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, ); }); @@ -133,7 +82,6 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("valid mainnet particl legacy/p2pkh address type", () { @@ -145,7 +93,6 @@ void main() { verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("valid mainnet particl p2wpkh address", () { @@ -160,7 +107,6 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("valid mainnet particl legacy/p2pkh address", () { @@ -170,7 +116,6 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("invalid mainnet particl legacy/p2pkh address", () { @@ -183,7 +128,6 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("invalid mainnet particl p2wpkh address", () { @@ -195,7 +139,6 @@ void main() { verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("invalid bech32 address type", () { @@ -207,7 +150,6 @@ void main() { verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); test("address has no matching script", () { @@ -219,14 +161,13 @@ void main() { verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); }); group("testNetworkConnection", () { MockElectrumX? client; MockCachedElectrumX? cachedClient; - MockPriceAPI? priceAPI; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; @@ -235,7 +176,7 @@ void main() { setUp(() { client = MockElectrumX(); cachedClient = MockCachedElectrumX(); - priceAPI = MockPriceAPI(); + secureStore = FakeSecureStorage(); tracker = MockTransactionNotificationTracker(); @@ -246,7 +187,6 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, ); }); @@ -259,7 +199,6 @@ void main() { verify(client?.ping()).called(1); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("attempted connection fails due to exception", () async { @@ -270,7 +209,6 @@ void main() { verify(client?.ping()).called(1); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("attempted connection test success", () async { @@ -281,17 +219,16 @@ void main() { verify(client?.ping()).called(1); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); }); group("basic getters, setters, and functions", () { - final testWalletId = "ParticltestWalletID"; - final testWalletName = "ParticlWallet"; + const testWalletId = "ParticltestWalletID"; + const testWalletName = "ParticlWallet"; MockElectrumX? client; MockCachedElectrumX? cachedClient; - MockPriceAPI? priceAPI; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; @@ -300,7 +237,7 @@ void main() { setUp(() async { client = MockElectrumX(); cachedClient = MockCachedElectrumX(); - priceAPI = MockPriceAPI(); + secureStore = FakeSecureStorage(); tracker = MockTransactionNotificationTracker(); @@ -311,7 +248,6 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, ); }); @@ -321,7 +257,6 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("get networkType test", () async { @@ -332,14 +267,12 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, ); expect(Coin.particl, Coin.particl); expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("get cryptoCurrency", () async { @@ -347,7 +280,6 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("get coinName", () async { @@ -355,7 +287,6 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("get coinTicker", () async { @@ -363,7 +294,6 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("get and set walletName", () async { @@ -373,7 +303,6 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("estimateTxFee", () async { @@ -388,20 +317,19 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("get fees succeeds", () async { when(client?.ping()).thenAnswer((_) async => true); when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_MAINNET, "hash_function": "sha256", - "services": [] + "services": [] }); when(client?.estimateFee(blocks: 1)) .thenAnswer((realInvocation) async => Decimal.zero); @@ -422,20 +350,19 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("get fees fails", () async { when(client?.ping()).thenAnswer((_) async => true); when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_MAINNET, "hash_function": "sha256", - "services": [] + "services": [] }); when(client?.estimateFee(blocks: 1)) .thenAnswer((realInvocation) async => Decimal.zero); @@ -459,20 +386,19 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); // test("get maxFee", () async { // when(client?.ping()).thenAnswer((_) async => true); // when(client?.getServerFeatures()).thenAnswer((_) async => { - // "hosts": {}, + // "hosts": {}, // "pruning": null, // "server_version": "Unit tests", // "protocol_min": "1.4", // "protocol_max": "1.4.2", // "genesis_hash": GENESIS_HASH_TESTNET, // "hash_function": "sha256", - // "services": [] + // "services": [] // }); // when(client?.estimateFee(blocks: 20)) // .thenAnswer((realInvocation) async => Decimal.zero); @@ -491,7 +417,7 @@ void main() { // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); // verifyNoMoreInteractions(tracker); - // verifyNoMoreInteractions(priceAPI); + // // }); }); @@ -503,7 +429,7 @@ void main() { MockElectrumX? client; MockCachedElectrumX? cachedClient; - MockPriceAPI? priceAPI; + late FakeSecureStorage secureStore; MockTransactionNotificationTracker? tracker; @@ -514,25 +440,13 @@ void main() { if (!hiveAdaptersRegistered) { hiveAdaptersRegistered = true; - // Registering Transaction Model Adapters - Hive.registerAdapter(TransactionDataAdapter()); - Hive.registerAdapter(TransactionChunkAdapter()); - Hive.registerAdapter(TransactionAdapter()); - Hive.registerAdapter(InputAdapter()); - Hive.registerAdapter(OutputAdapter()); - - // Registering Utxo Model Adapters - Hive.registerAdapter(UtxoDataAdapter()); - Hive.registerAdapter(UtxoObjectAdapter()); - Hive.registerAdapter(StatusAdapter()); - - final wallets = await Hive.openBox('wallets'); + final wallets = await Hive.openBox('wallets'); await wallets.put('currentWalletName', testWalletName); } client = MockElectrumX(); cachedClient = MockCachedElectrumX(); - priceAPI = MockPriceAPI(); + secureStore = FakeSecureStorage(); tracker = MockTransactionNotificationTracker(); @@ -543,7 +457,6 @@ void main() { client: client!, cachedClient: cachedClient!, tracker: tracker!, - priceAPI: priceAPI, secureStore: secureStore, ); }); @@ -555,33 +468,33 @@ void main() { // verify(client?.ping()).called(1); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); // test("initializeWallet no network exception", () async { // when(client?.ping()).thenThrow(Exception("Network connection failed")); - // final wallets = await Hive.openBox(testWalletId); + // final wallets = await Hive.openBox (testWalletId); // expect(await nmc?.initializeExisting(), false); // expect(secureStore.interactions, 0); // verify(client?.ping()).called(1); // verifyNoMoreInteractions(client); // verifyNoMoreInteractions(cachedClient); - // verifyNoMoreInteractions(priceAPI); + // // }); test("initializeWallet mainnet throws bad network", () async { when(client?.ping()).thenAnswer((_) async => true); when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_MAINNET, "hash_function": "sha256", - "services": [] + "services": [] }); - await Hive.openBox(testWalletId); + await Hive.openBox(testWalletId); await expectLater( () => part?.initializeExisting(), throwsA(isA())) @@ -589,33 +502,31 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); }); test("initializeWallet throws mnemonic overwrite exception", () async { when(client?.ping()).thenAnswer((_) async => true); when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_MAINNET, "hash_function": "sha256", - "services": [] + "services": [] }); await secureStore.write( key: "${testWalletId}_mnemonic", value: "some mnemonic"); - await Hive.openBox(testWalletId); + await Hive.openBox(testWalletId); await expectLater( () => part?.initializeExisting(), throwsA(isA())) .then((_) { expect(secureStore.interactions, 1); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); }); @@ -623,14 +534,14 @@ void main() { "recoverFromMnemonic using empty seed on mainnet fails due to bad genesis hash match", () async { when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_TESTNET, "hash_function": "sha256", - "services": [] + "services": [] }); bool hasThrown = false; @@ -650,21 +561,20 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test( "recoverFromMnemonic using empty seed on mainnet fails due to attempted overwrite of mnemonic", () async { when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, + "hosts": {}, "pruning": null, "server_version": "Unit tests", "protocol_min": "1.4", "protocol_max": "1.4.2", "genesis_hash": GENESIS_HASH_MAINNET, "hash_function": "sha256", - "services": [] + "services": [] }); await secureStore.write( @@ -687,693 +597,686 @@ void main() { expect(secureStore.interactions, 2); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); - test("recoverFromMnemonic using empty seed on mainnet succeeds", () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - - // await DB.instance.init(); - final wallet = await Hive.openBox(testWalletId); - bool hasThrown = false; - try { - await part?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - } catch (_) { - hasThrown = true; - } - expect(hasThrown, false); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); - - expect(secureStore.interactions, 14); - expect(secureStore.writes, 5); - expect(secureStore.reads, 9); - expect(secureStore.deletes, 0); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); - - test("get mnemonic list", () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => emptyHistoryBatchResponse); - - await Hive.openBox(testWalletId); - - await part?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - expect(await part?.mnemonic, TEST_MNEMONIC.split(" ")); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); - - test("recoverFromMnemonic using non empty seed on mainnet succeeds", - () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => historyBatchResponse); - - List dynamicArgValues = []; - - when(client?.getBatchHistory(args: anyNamed("args"))) - .thenAnswer((realInvocation) async { - if (realInvocation.namedArguments.values.first.length == 1) { - dynamicArgValues.add(realInvocation.namedArguments.values.first); - } - - return historyBatchResponse; - }); - - await Hive.openBox(testWalletId); - - bool hasThrown = false; - try { - await part?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - } catch (_) { - hasThrown = true; - } - expect(hasThrown, false); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); - - for (final arg in dynamicArgValues) { - final map = Map>.from(arg as Map); - verify(client?.getBatchHistory(args: map)).called(1); - expect(activeScriptHashes.contains(map.values.first.first as String), - true); - } - - expect(secureStore.interactions, 10); - expect(secureStore.writes, 5); - expect(secureStore.reads, 5); - expect(secureStore.deletes, 0); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); - - test("fullRescan succeeds", () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => historyBatchResponse); - when(cachedClient?.clearSharedTransactionCache(coin: Coin.particl)) - .thenAnswer((realInvocation) async {}); - - when(client?.getBatchHistory(args: { - "0": [ - "8ba03c2c46ed4980fa1e4c84cbceeb2d5e1371a7ccbaf5f3d69c5114161a2247" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - when(client?.getBatchHistory(args: { - "0": [ - "3fedd8a2d5fc355727afe353413dc1a0ef861ba768744d5b8193c33cbc829339" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - when(client?.getBatchHistory(args: { - "0": [ - "b6fce6c41154ccf70676c5c91acd9b6899ef0195e34b4c05c4920daa827c19a3" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - when(client?.getBatchHistory(args: { - "0": [ - "0e8b6756b404db5a381fd71ad79cb595a6c36c938cf9913c5a0494b667c2151a" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - when(client?.getBatchHistory(args: { - "0": [ - "9b56ab30c7bef0e1eaa10a632c8e2dcdd11b2158d7a917c03d62936afd0015fc" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - when(client?.getBatchHistory(args: { - "0": [ - "c4b1d9cd4edb7c13eae863b1e4f8fd5acff29f1fe153c4f859906cbea26a3f2f" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - final wallet = await Hive.openBox(testWalletId); - - // restore so we have something to rescan - await part?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - // fetch valid wallet data - final preReceivingAddressesP2PKH = - await wallet.get('receivingAddressesP2PKH'); - final preReceivingAddressesP2WPKH = - await wallet.get('receivingAddressesP2WPKH'); - final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); - final preChangeAddressesP2WPKH = - await wallet.get('changeAddressesP2WPKH'); - final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); - final preReceivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); - final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); - final preChangeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); - final preUtxoData = await wallet.get('latest_utxo_model'); - final preReceiveDerivationsStringP2PKH = await secureStore.read( - key: "${testWalletId}_receiveDerivationsP2PKH"); - final preChangeDerivationsStringP2PKH = - await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); - final preReceiveDerivationsStringP2WPKH = await secureStore.read( - key: "${testWalletId}_receiveDerivationsP2WPKH"); - final preChangeDerivationsStringP2WPKH = await secureStore.read( - key: "${testWalletId}_changeDerivationsP2WPKH"); - - // destroy the data that the rescan will fix - await wallet.put( - 'receivingAddressesP2PKH', ["some address", "some other address"]); - await wallet.put( - 'receivingAddressesP2WPKH', ["some address", "some other address"]); - await wallet - .put('changeAddressesP2PKH', ["some address", "some other address"]); - await wallet - .put('changeAddressesP2WPKH', ["some address", "some other address"]); - await wallet.put('receivingIndexP2PKH', 123); - await wallet.put('receivingIndexP2WPKH', 123); - await wallet.put('changeIndexP2PKH', 123); - await wallet.put('changeIndexP2WPKH', 123); - await secureStore.write( - key: "${testWalletId}_receiveDerivationsP2PKH", value: "{}"); - await secureStore.write( - key: "${testWalletId}_changeDerivationsP2PKH", value: "{}"); - await secureStore.write( - key: "${testWalletId}_receiveDerivationsP2WPKH", value: "{}"); - await secureStore.write( - key: "${testWalletId}_changeDerivationsP2WPKH", value: "{}"); - - bool hasThrown = false; - try { - await part?.fullRescan(2, 1000); - } catch (_) { - hasThrown = true; - } - expect(hasThrown, false); - - // fetch wallet data again - final receivingAddressesP2PKH = - await wallet.get('receivingAddressesP2PKH'); - final receivingAddressesP2WPKH = - await wallet.get('receivingAddressesP2WPKH'); - final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); - final changeAddressesP2WPKH = await wallet.get('changeAddressesP2WPKH'); - final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); - final receivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); - final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); - final changeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); - final utxoData = await wallet.get('latest_utxo_model'); - final receiveDerivationsStringP2PKH = await secureStore.read( - key: "${testWalletId}_receiveDerivationsP2PKH"); - final changeDerivationsStringP2PKH = - await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); - final receiveDerivationsStringP2WPKH = await secureStore.read( - key: "${testWalletId}_receiveDerivationsP2WPKH"); - final changeDerivationsStringP2WPKH = await secureStore.read( - key: "${testWalletId}_changeDerivationsP2WPKH"); - - expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); - expect(preReceivingAddressesP2WPKH, receivingAddressesP2WPKH); - expect(preChangeAddressesP2PKH, changeAddressesP2PKH); - expect(preChangeAddressesP2WPKH, changeAddressesP2WPKH); - expect(preReceivingIndexP2PKH, receivingIndexP2PKH); - expect(preReceivingIndexP2WPKH, receivingIndexP2WPKH); - expect(preChangeIndexP2PKH, changeIndexP2PKH); - expect(preChangeIndexP2WPKH, changeIndexP2WPKH); - expect(preUtxoData, utxoData); - expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); - expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); - expect(preReceiveDerivationsStringP2WPKH, receiveDerivationsStringP2WPKH); - expect(preChangeDerivationsStringP2WPKH, changeDerivationsStringP2WPKH); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(2); - - verify(client?.getBatchHistory(args: { - "0": [ - "3fedd8a2d5fc355727afe353413dc1a0ef861ba768744d5b8193c33cbc829339" - ] - })).called(2); - - verify(client?.getBatchHistory(args: { - "0": [ - "b6fce6c41154ccf70676c5c91acd9b6899ef0195e34b4c05c4920daa827c19a3" - ] - })).called(2); - - verify(client?.getBatchHistory(args: { - "0": [ - "0e8b6756b404db5a381fd71ad79cb595a6c36c938cf9913c5a0494b667c2151a" - ] - })).called(2); - - verify(client?.getBatchHistory(args: { - "0": [ - "c4b1d9cd4edb7c13eae863b1e4f8fd5acff29f1fe153c4f859906cbea26a3f2f" - ] - })).called(2); - verify(cachedClient?.clearSharedTransactionCache(coin: Coin.particl)) - .called(1); - - expect(secureStore.writes, 17); - expect(secureStore.reads, 22); - expect(secureStore.deletes, 4); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); - - test("fullRescan fails", () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => historyBatchResponse); - - when(client?.getBatchHistory(args: { - "0": [ - "8ba03c2c46ed4980fa1e4c84cbceeb2d5e1371a7ccbaf5f3d69c5114161a2247" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - when(client?.getBatchHistory(args: { - "0": [ - "3fedd8a2d5fc355727afe353413dc1a0ef861ba768744d5b8193c33cbc829339" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - when(client?.getBatchHistory(args: { - "0": [ - "b6fce6c41154ccf70676c5c91acd9b6899ef0195e34b4c05c4920daa827c19a3" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - when(client?.getBatchHistory(args: { - "0": [ - "0e8b6756b404db5a381fd71ad79cb595a6c36c938cf9913c5a0494b667c2151a" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - when(client?.getBatchHistory(args: { - "0": [ - "9b56ab30c7bef0e1eaa10a632c8e2dcdd11b2158d7a917c03d62936afd0015fc" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - when(client?.getBatchHistory(args: { - "0": [ - "c4b1d9cd4edb7c13eae863b1e4f8fd5acff29f1fe153c4f859906cbea26a3f2f" - ] - })).thenAnswer((realInvocation) async => {"0": []}); - - when(cachedClient?.clearSharedTransactionCache(coin: Coin.particl)) - .thenAnswer((realInvocation) async {}); - - final wallet = await Hive.openBox(testWalletId); - - // restore so we have something to rescan - await part?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - // fetch wallet data - final preReceivingAddressesP2PKH = - await wallet.get('receivingAddressesP2PKH'); - final preReceivingAddressesP2SH = - await wallet.get('receivingAddressesP2SH'); - final preReceivingAddressesP2WPKH = - await wallet.get('receivingAddressesP2WPKH'); - final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); - final preChangeAddressesP2SH = await wallet.get('changeAddressesP2SH'); - final preChangeAddressesP2WPKH = - await wallet.get('changeAddressesP2WPKH'); - final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); - final preReceivingIndexP2SH = await wallet.get('receivingIndexP2SH'); - final preReceivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); - final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); - final preChangeIndexP2SH = await wallet.get('changeIndexP2SH'); - final preChangeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); - final preUtxoData = await wallet.get('latest_utxo_model'); - final preReceiveDerivationsStringP2PKH = await secureStore.read( - key: "${testWalletId}_receiveDerivationsP2PKH"); - final preChangeDerivationsStringP2PKH = - await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); - final preReceiveDerivationsStringP2SH = - await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); - final preChangeDerivationsStringP2SH = - await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); - final preReceiveDerivationsStringP2WPKH = await secureStore.read( - key: "${testWalletId}_receiveDerivationsP2WPKH"); - final preChangeDerivationsStringP2WPKH = await secureStore.read( - key: "${testWalletId}_changeDerivationsP2WPKH"); - - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenThrow(Exception("fake exception")); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenThrow(Exception("fake exception")); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenThrow(Exception("fake exception")); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenThrow(Exception("fake exception")); - - bool hasThrown = false; - try { - await part?.fullRescan(2, 1000); - } catch (_) { - hasThrown = true; - } - expect(hasThrown, true); - - // fetch wallet data again - final receivingAddressesP2PKH = - await wallet.get('receivingAddressesP2PKH'); - final receivingAddressesP2SH = await wallet.get('receivingAddressesP2SH'); - final receivingAddressesP2WPKH = - await wallet.get('receivingAddressesP2WPKH'); - final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); - final changeAddressesP2SH = await wallet.get('changeAddressesP2SH'); - final changeAddressesP2WPKH = await wallet.get('changeAddressesP2WPKH'); - final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); - final receivingIndexP2SH = await wallet.get('receivingIndexP2SH'); - final receivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); - final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); - final changeIndexP2SH = await wallet.get('changeIndexP2SH'); - final changeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); - final utxoData = await wallet.get('latest_utxo_model'); - final receiveDerivationsStringP2PKH = await secureStore.read( - key: "${testWalletId}_receiveDerivationsP2PKH"); - final changeDerivationsStringP2PKH = - await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); - final receiveDerivationsStringP2SH = - await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); - final changeDerivationsStringP2SH = - await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); - final receiveDerivationsStringP2WPKH = await secureStore.read( - key: "${testWalletId}_receiveDerivationsP2WPKH"); - final changeDerivationsStringP2WPKH = await secureStore.read( - key: "${testWalletId}_changeDerivationsP2WPKH"); - - expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); - expect(preReceivingAddressesP2SH, receivingAddressesP2SH); - expect(preReceivingAddressesP2WPKH, receivingAddressesP2WPKH); - expect(preChangeAddressesP2PKH, changeAddressesP2PKH); - expect(preChangeAddressesP2SH, changeAddressesP2SH); - expect(preChangeAddressesP2WPKH, changeAddressesP2WPKH); - expect(preReceivingIndexP2PKH, receivingIndexP2PKH); - expect(preReceivingIndexP2SH, receivingIndexP2SH); - expect(preReceivingIndexP2WPKH, receivingIndexP2WPKH); - expect(preChangeIndexP2PKH, changeIndexP2PKH); - expect(preChangeIndexP2SH, changeIndexP2SH); - expect(preChangeIndexP2WPKH, changeIndexP2WPKH); - expect(preUtxoData, utxoData); - expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); - expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); - expect(preReceiveDerivationsStringP2SH, receiveDerivationsStringP2SH); - expect(preChangeDerivationsStringP2SH, changeDerivationsStringP2SH); - expect(preReceiveDerivationsStringP2WPKH, receiveDerivationsStringP2WPKH); - expect(preChangeDerivationsStringP2WPKH, changeDerivationsStringP2WPKH); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(2); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(2); - verify(client?.getBatchHistory(args: { - "0": [ - "3fedd8a2d5fc355727afe353413dc1a0ef861ba768744d5b8193c33cbc829339" - ] - })).called(1); - verify(client?.getBatchHistory(args: { - "0": [ - "b6fce6c41154ccf70676c5c91acd9b6899ef0195e34b4c05c4920daa827c19a3" - ] - })).called(1); - verify(client?.getBatchHistory(args: { - "0": [ - "0e8b6756b404db5a381fd71ad79cb595a6c36c938cf9913c5a0494b667c2151a" - ] - })).called(1); - verify(client?.getBatchHistory(args: { - "0": [ - "c4b1d9cd4edb7c13eae863b1e4f8fd5acff29f1fe153c4f859906cbea26a3f2f" - ] - })).called(1); - verify(cachedClient?.clearSharedTransactionCache(coin: Coin.particl)) - .called(1); - - expect(secureStore.writes, 13); - expect(secureStore.reads, 26); - expect(secureStore.deletes, 8); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); - - test("prepareSend fails", () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => historyBatchResponse); - - List dynamicArgValues = []; - - when(client?.getBatchHistory(args: anyNamed("args"))) - .thenAnswer((realInvocation) async { - if (realInvocation.namedArguments.values.first.length == 1) { - dynamicArgValues.add(realInvocation.namedArguments.values.first); - } - - return historyBatchResponse; - }); - - await Hive.openBox(testWalletId); - - when(cachedClient?.getTransaction( - txHash: - "85130125ec9e37a48670fb5eb0a2780b94ea958cd700a1237ff75775d8a0edb0", - coin: Coin.particl)) - .thenAnswer((_) async => tx2Raw); - when(cachedClient?.getTransaction( - txHash: - "bb25567e1ffb2fd6ec9aa3925a7a8dd3055a29521f7811b2b2bc01ce7d8a216e", - coin: Coin.particl)) - .thenAnswer((_) async => tx3Raw); - when(cachedClient?.getTransaction( - txHash: - "bb25567e1ffb2fd6ec9aa3925a7a8dd3055a29521f7811b2b2bc01ce7d8a216e", - coin: Coin.particl, - )).thenAnswer((_) async => tx4Raw); - - // recover to fill data - await part?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - // modify addresses to properly mock data to build a tx - final rcv44 = await secureStore.read( - key: testWalletId + "_receiveDerivationsP2PKH"); - await secureStore.write( - key: testWalletId + "_receiveDerivationsP2PKH", - value: rcv44?.replaceFirst("1RMSPixoLPuaXuhR2v4HsUMcRjLncKDaw", - "16FuTPaeRSPVxxCnwQmdyx2PQWxX6HWzhQ")); - final rcv84 = await secureStore.read( - key: testWalletId + "_receiveDerivationsP2WPKH"); - await secureStore.write( - key: testWalletId + "_receiveDerivationsP2WPKH", - value: rcv84?.replaceFirst( - "pw1qvr6ehcm44vvqe96mxy9zw9aa5sa5yezvr2r94s", - "pw1q66xtkhqzcue808nlg8tp48uq7fshmaddljtkpy")); - - part?.outputsList = utxoList; - - bool didThrow = false; - try { - await part?.prepareSend( - address: "pw1q66xtkhqzcue808nlg8tp48uq7fshmaddljtkpy", - satoshiAmount: 15000); - } catch (_) { - didThrow = true; - } - - expect(didThrow, true); - - verify(client?.getServerFeatures()).called(1); - - /// verify transaction no matching calls - - // verify(cachedClient?.getTransaction( - // txHash: - // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", - // coin: Coin.particl, - // callOutSideMainIsolate: false)) - // .called(1); - // verify(cachedClient?.getTransaction( - // txHash: - // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", - // coin: Coin.particl, - // callOutSideMainIsolate: false)) - // .called(1); - // verify(cachedClient?.getTransaction( - // txHash: - // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", - // coin: Coin.particl, - // callOutSideMainIsolate: false)) - // .called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); - - for (final arg in dynamicArgValues) { - final map = Map>.from(arg as Map); - - verify(client?.getBatchHistory(args: map)).called(1); - expect(activeScriptHashes.contains(map.values.first.first as String), - true); - } - - expect(secureStore.interactions, 14); - expect(secureStore.writes, 7); - expect(secureStore.reads, 7); - expect(secureStore.deletes, 0); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); + // test("recoverFromMnemonic using empty seed on mainnet succeeds", () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // + // // await DB.instance.init(); + // await Hive.openBox(testWalletId); + // bool hasThrown = false; + // try { + // await part?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // } catch (_) { + // hasThrown = true; + // } + // expect(hasThrown, false); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); + // + // expect(secureStore.interactions, 14); + // expect(secureStore.writes, 5); + // expect(secureStore.reads, 9); + // expect(secureStore.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("get mnemonic list", () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // + // await Hive.openBox(testWalletId); + // + // await part?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // expect(await part?.mnemonic, TEST_MNEMONIC.split(" ")); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("recoverFromMnemonic using non empty seed on mainnet succeeds", + // () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => historyBatchResponse); + // + // List dynamicArgValues = []; + // + // when(client?.getBatchHistory(args: anyNamed("args"))) + // .thenAnswer((realInvocation) async { + // if (realInvocation.namedArguments.values.first.length == 1) { + // dynamicArgValues.add(realInvocation.namedArguments.values.first); + // } + // + // return historyBatchResponse; + // }); + // + // await Hive.openBox(testWalletId); + // + // bool hasThrown = false; + // try { + // await part?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // } catch (_) { + // hasThrown = true; + // } + // expect(hasThrown, false); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); + // + // for (final arg in dynamicArgValues) { + // final map = Map>.from(arg as Map); + // verify(client?.getBatchHistory(args: map)).called(1); + // expect(activeScriptHashes.contains(map.values.first.first as String), + // true); + // } + // + // expect(secureStore.interactions, 10); + // expect(secureStore.writes, 5); + // expect(secureStore.reads, 5); + // expect(secureStore.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("fullRescan succeeds", () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => historyBatchResponse); + // when(cachedClient?.clearSharedTransactionCache(coin: Coin.particl)) + // .thenAnswer((realInvocation) async {}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "8ba03c2c46ed4980fa1e4c84cbceeb2d5e1371a7ccbaf5f3d69c5114161a2247" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "3fedd8a2d5fc355727afe353413dc1a0ef861ba768744d5b8193c33cbc829339" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "b6fce6c41154ccf70676c5c91acd9b6899ef0195e34b4c05c4920daa827c19a3" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "0e8b6756b404db5a381fd71ad79cb595a6c36c938cf9913c5a0494b667c2151a" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // when(client?.getBatchHistory(args: { + // "0": [ + // "9b56ab30c7bef0e1eaa10a632c8e2dcdd11b2158d7a917c03d62936afd0015fc" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // when(client?.getBatchHistory(args: { + // "0": [ + // "c4b1d9cd4edb7c13eae863b1e4f8fd5acff29f1fe153c4f859906cbea26a3f2f" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // final wallet = await Hive.openBox(testWalletId); + // + // // restore so we have something to rescan + // await part?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // // fetch valid wallet data + // final preReceivingAddressesP2PKH = + // await wallet.get('receivingAddressesP2PKH'); + // final preReceivingAddressesP2WPKH = + // await wallet.get('receivingAddressesP2WPKH'); + // final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + // final preChangeAddressesP2WPKH = + // await wallet.get('changeAddressesP2WPKH'); + // final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + // final preReceivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); + // final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + // final preChangeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); + // final preUtxoData = await wallet.get('latest_utxo_model'); + // final preReceiveDerivationsStringP2PKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2PKH"); + // final preChangeDerivationsStringP2PKH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + // final preReceiveDerivationsStringP2WPKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2WPKH"); + // final preChangeDerivationsStringP2WPKH = await secureStore.read( + // key: "${testWalletId}_changeDerivationsP2WPKH"); + // + // // destroy the data that the rescan will fix + // await wallet.put( + // 'receivingAddressesP2PKH', ["some address", "some other address"]); + // await wallet.put( + // 'receivingAddressesP2WPKH', ["some address", "some other address"]); + // await wallet + // .put('changeAddressesP2PKH', ["some address", "some other address"]); + // await wallet + // .put('changeAddressesP2WPKH', ["some address", "some other address"]); + // await wallet.put('receivingIndexP2PKH', 123); + // await wallet.put('receivingIndexP2WPKH', 123); + // await wallet.put('changeIndexP2PKH', 123); + // await wallet.put('changeIndexP2WPKH', 123); + // await secureStore.write( + // key: "${testWalletId}_receiveDerivationsP2PKH", value: "{}"); + // await secureStore.write( + // key: "${testWalletId}_changeDerivationsP2PKH", value: "{}"); + // await secureStore.write( + // key: "${testWalletId}_receiveDerivationsP2WPKH", value: "{}"); + // await secureStore.write( + // key: "${testWalletId}_changeDerivationsP2WPKH", value: "{}"); + // + // bool hasThrown = false; + // try { + // await part?.fullRescan(2, 1000); + // } catch (_) { + // hasThrown = true; + // } + // expect(hasThrown, false); + // + // // fetch wallet data again + // final receivingAddressesP2PKH = + // await wallet.get('receivingAddressesP2PKH'); + // final receivingAddressesP2WPKH = + // await wallet.get('receivingAddressesP2WPKH'); + // final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + // final changeAddressesP2WPKH = await wallet.get('changeAddressesP2WPKH'); + // final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + // final receivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); + // final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + // final changeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); + // final utxoData = await wallet.get('latest_utxo_model'); + // final receiveDerivationsStringP2PKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2PKH"); + // final changeDerivationsStringP2PKH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + // final receiveDerivationsStringP2WPKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2WPKH"); + // final changeDerivationsStringP2WPKH = await secureStore.read( + // key: "${testWalletId}_changeDerivationsP2WPKH"); + // + // expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); + // expect(preReceivingAddressesP2WPKH, receivingAddressesP2WPKH); + // expect(preChangeAddressesP2PKH, changeAddressesP2PKH); + // expect(preChangeAddressesP2WPKH, changeAddressesP2WPKH); + // expect(preReceivingIndexP2PKH, receivingIndexP2PKH); + // expect(preReceivingIndexP2WPKH, receivingIndexP2WPKH); + // expect(preChangeIndexP2PKH, changeIndexP2PKH); + // expect(preChangeIndexP2WPKH, changeIndexP2WPKH); + // expect(preUtxoData, utxoData); + // expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); + // expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); + // expect(preReceiveDerivationsStringP2WPKH, receiveDerivationsStringP2WPKH); + // expect(preChangeDerivationsStringP2WPKH, changeDerivationsStringP2WPKH); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(2); + // + // verify(client?.getBatchHistory(args: { + // "0": [ + // "3fedd8a2d5fc355727afe353413dc1a0ef861ba768744d5b8193c33cbc829339" + // ] + // })).called(2); + // + // verify(client?.getBatchHistory(args: { + // "0": [ + // "b6fce6c41154ccf70676c5c91acd9b6899ef0195e34b4c05c4920daa827c19a3" + // ] + // })).called(2); + // + // verify(client?.getBatchHistory(args: { + // "0": [ + // "0e8b6756b404db5a381fd71ad79cb595a6c36c938cf9913c5a0494b667c2151a" + // ] + // })).called(2); + // + // verify(client?.getBatchHistory(args: { + // "0": [ + // "c4b1d9cd4edb7c13eae863b1e4f8fd5acff29f1fe153c4f859906cbea26a3f2f" + // ] + // })).called(2); + // verify(cachedClient?.clearSharedTransactionCache(coin: Coin.particl)) + // .called(1); + // + // expect(secureStore.writes, 17); + // expect(secureStore.reads, 22); + // expect(secureStore.deletes, 4); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("fullRescan fails", () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => historyBatchResponse); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "8ba03c2c46ed4980fa1e4c84cbceeb2d5e1371a7ccbaf5f3d69c5114161a2247" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "3fedd8a2d5fc355727afe353413dc1a0ef861ba768744d5b8193c33cbc829339" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "b6fce6c41154ccf70676c5c91acd9b6899ef0195e34b4c05c4920daa827c19a3" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "0e8b6756b404db5a381fd71ad79cb595a6c36c938cf9913c5a0494b667c2151a" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "9b56ab30c7bef0e1eaa10a632c8e2dcdd11b2158d7a917c03d62936afd0015fc" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // when(client?.getBatchHistory(args: { + // "0": [ + // "c4b1d9cd4edb7c13eae863b1e4f8fd5acff29f1fe153c4f859906cbea26a3f2f" + // ] + // })).thenAnswer((realInvocation) async => {"0": []}); + // + // when(cachedClient?.clearSharedTransactionCache(coin: Coin.particl)) + // .thenAnswer((realInvocation) async {}); + // + // final wallet = await Hive.openBox(testWalletId); + // + // // restore so we have something to rescan + // await part?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // // fetch wallet data + // final preReceivingAddressesP2PKH = + // await wallet.get('receivingAddressesP2PKH'); + // final preReceivingAddressesP2SH = + // await wallet.get('receivingAddressesP2SH'); + // final preReceivingAddressesP2WPKH = + // await wallet.get('receivingAddressesP2WPKH'); + // final preChangeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + // final preChangeAddressesP2SH = await wallet.get('changeAddressesP2SH'); + // final preChangeAddressesP2WPKH = + // await wallet.get('changeAddressesP2WPKH'); + // final preReceivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + // final preReceivingIndexP2SH = await wallet.get('receivingIndexP2SH'); + // final preReceivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); + // final preChangeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + // final preChangeIndexP2SH = await wallet.get('changeIndexP2SH'); + // final preChangeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); + // final preUtxoData = await wallet.get('latest_utxo_model'); + // final preReceiveDerivationsStringP2PKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2PKH"); + // final preChangeDerivationsStringP2PKH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + // final preReceiveDerivationsStringP2SH = + // await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); + // final preChangeDerivationsStringP2SH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); + // final preReceiveDerivationsStringP2WPKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2WPKH"); + // final preChangeDerivationsStringP2WPKH = await secureStore.read( + // key: "${testWalletId}_changeDerivationsP2WPKH"); + // + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenThrow(Exception("fake exception")); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenThrow(Exception("fake exception")); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenThrow(Exception("fake exception")); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenThrow(Exception("fake exception")); + // + // bool hasThrown = false; + // try { + // await part?.fullRescan(2, 1000); + // } catch (_) { + // hasThrown = true; + // } + // expect(hasThrown, true); + // + // // fetch wallet data again + // final receivingAddressesP2PKH = + // await wallet.get('receivingAddressesP2PKH'); + // final receivingAddressesP2SH = await wallet.get('receivingAddressesP2SH'); + // final receivingAddressesP2WPKH = + // await wallet.get('receivingAddressesP2WPKH'); + // final changeAddressesP2PKH = await wallet.get('changeAddressesP2PKH'); + // final changeAddressesP2SH = await wallet.get('changeAddressesP2SH'); + // final changeAddressesP2WPKH = await wallet.get('changeAddressesP2WPKH'); + // final receivingIndexP2PKH = await wallet.get('receivingIndexP2PKH'); + // final receivingIndexP2SH = await wallet.get('receivingIndexP2SH'); + // final receivingIndexP2WPKH = await wallet.get('receivingIndexP2WPKH'); + // final changeIndexP2PKH = await wallet.get('changeIndexP2PKH'); + // final changeIndexP2SH = await wallet.get('changeIndexP2SH'); + // final changeIndexP2WPKH = await wallet.get('changeIndexP2WPKH'); + // final utxoData = await wallet.get('latest_utxo_model'); + // final receiveDerivationsStringP2PKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2PKH"); + // final changeDerivationsStringP2PKH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2PKH"); + // final receiveDerivationsStringP2SH = + // await secureStore.read(key: "${testWalletId}_receiveDerivationsP2SH"); + // final changeDerivationsStringP2SH = + // await secureStore.read(key: "${testWalletId}_changeDerivationsP2SH"); + // final receiveDerivationsStringP2WPKH = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2WPKH"); + // final changeDerivationsStringP2WPKH = await secureStore.read( + // key: "${testWalletId}_changeDerivationsP2WPKH"); + // + // expect(preReceivingAddressesP2PKH, receivingAddressesP2PKH); + // expect(preReceivingAddressesP2SH, receivingAddressesP2SH); + // expect(preReceivingAddressesP2WPKH, receivingAddressesP2WPKH); + // expect(preChangeAddressesP2PKH, changeAddressesP2PKH); + // expect(preChangeAddressesP2SH, changeAddressesP2SH); + // expect(preChangeAddressesP2WPKH, changeAddressesP2WPKH); + // expect(preReceivingIndexP2PKH, receivingIndexP2PKH); + // expect(preReceivingIndexP2SH, receivingIndexP2SH); + // expect(preReceivingIndexP2WPKH, receivingIndexP2WPKH); + // expect(preChangeIndexP2PKH, changeIndexP2PKH); + // expect(preChangeIndexP2SH, changeIndexP2SH); + // expect(preChangeIndexP2WPKH, changeIndexP2WPKH); + // expect(preUtxoData, utxoData); + // expect(preReceiveDerivationsStringP2PKH, receiveDerivationsStringP2PKH); + // expect(preChangeDerivationsStringP2PKH, changeDerivationsStringP2PKH); + // expect(preReceiveDerivationsStringP2SH, receiveDerivationsStringP2SH); + // expect(preChangeDerivationsStringP2SH, changeDerivationsStringP2SH); + // expect(preReceiveDerivationsStringP2WPKH, receiveDerivationsStringP2WPKH); + // expect(preChangeDerivationsStringP2WPKH, changeDerivationsStringP2WPKH); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(2); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(2); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "3fedd8a2d5fc355727afe353413dc1a0ef861ba768744d5b8193c33cbc829339" + // ] + // })).called(1); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "b6fce6c41154ccf70676c5c91acd9b6899ef0195e34b4c05c4920daa827c19a3" + // ] + // })).called(1); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "0e8b6756b404db5a381fd71ad79cb595a6c36c938cf9913c5a0494b667c2151a" + // ] + // })).called(1); + // verify(client?.getBatchHistory(args: { + // "0": [ + // "c4b1d9cd4edb7c13eae863b1e4f8fd5acff29f1fe153c4f859906cbea26a3f2f" + // ] + // })).called(1); + // verify(cachedClient?.clearSharedTransactionCache(coin: Coin.particl)) + // .called(1); + // + // expect(secureStore.writes, 13); + // expect(secureStore.reads, 26); + // expect(secureStore.deletes, 8); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); + // + // test("prepareSend fails", () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => historyBatchResponse); + // + // List dynamicArgValues = []; + // + // when(client?.getBatchHistory(args: anyNamed("args"))) + // .thenAnswer((realInvocation) async { + // if (realInvocation.namedArguments.values.first.length == 1) { + // dynamicArgValues.add(realInvocation.namedArguments.values.first); + // } + // + // return historyBatchResponse; + // }); + // + // await Hive.openBox(testWalletId); + // + // when(cachedClient?.getTransaction( + // txHash: + // "85130125ec9e37a48670fb5eb0a2780b94ea958cd700a1237ff75775d8a0edb0", + // coin: Coin.particl)) + // .thenAnswer((_) async => tx2Raw); + // when(cachedClient?.getTransaction( + // txHash: + // "bb25567e1ffb2fd6ec9aa3925a7a8dd3055a29521f7811b2b2bc01ce7d8a216e", + // coin: Coin.particl)) + // .thenAnswer((_) async => tx3Raw); + // when(cachedClient?.getTransaction( + // txHash: + // "bb25567e1ffb2fd6ec9aa3925a7a8dd3055a29521f7811b2b2bc01ce7d8a216e", + // coin: Coin.particl, + // )).thenAnswer((_) async => tx4Raw); + // + // // recover to fill data + // await part?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // // modify addresses to properly mock data to build a tx + // final rcv44 = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2PKH"); + // await secureStore.write( + // key: "${testWalletId}_receiveDerivationsP2PKH", + // value: rcv44?.replaceFirst("1RMSPixoLPuaXuhR2v4HsUMcRjLncKDaw", + // "16FuTPaeRSPVxxCnwQmdyx2PQWxX6HWzhQ")); + // final rcv84 = await secureStore.read( + // key: "${testWalletId}_receiveDerivationsP2WPKH"); + // await secureStore.write( + // key: "${testWalletId}_receiveDerivationsP2WPKH", + // value: rcv84?.replaceFirst( + // "pw1qvr6ehcm44vvqe96mxy9zw9aa5sa5yezvr2r94s", + // "pw1q66xtkhqzcue808nlg8tp48uq7fshmaddljtkpy")); + // + // // part?.outputsList = utxoList; + // + // bool didThrow = false; + // try { + // await part?.prepareSend( + // address: "pw1q66xtkhqzcue808nlg8tp48uq7fshmaddljtkpy", + // satoshiAmount: 15000); + // } catch (_) { + // didThrow = true; + // } + // + // expect(didThrow, true); + // + // verify(client?.getServerFeatures()).called(1); + // + // /// verify transaction no matching calls + // + // // verify(cachedClient?.getTransaction( + // // txHash: + // // "2087ce09bc316877c9f10971526a2bffa3078d52ea31752639305cdcd8230703", + // // coin: Coin.particl, + // // callOutSideMainIsolate: false)) + // // .called(1); + // // verify(cachedClient?.getTransaction( + // // txHash: + // // "ed32c967a0e86d51669ac21c2bb9bc9c50f0f55fbacdd8db21d0a986fba93bd7", + // // coin: Coin.particl, + // // callOutSideMainIsolate: false)) + // // .called(1); + // // verify(cachedClient?.getTransaction( + // // txHash: + // // "3f0032f89ac44b281b50314cff3874c969c922839dddab77ced54e86a21c3fd4", + // // coin: Coin.particl, + // // callOutSideMainIsolate: false)) + // // .called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); + // + // for (final arg in dynamicArgValues) { + // final map = Map>.from(arg as Map); + // + // verify(client?.getBatchHistory(args: map)).called(1); + // expect(activeScriptHashes.contains(map.values.first.first as String), + // true); + // } + // + // expect(secureStore.interactions, 14); + // expect(secureStore.writes, 7); + // expect(secureStore.reads, 7); + // expect(secureStore.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // }); test("confirmSend no hex", () async { bool didThrow = false; @@ -1388,7 +1291,6 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("confirmSend hex is not string", () async { @@ -1404,7 +1306,6 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("confirmSend hex is string but missing other data", () async { @@ -1424,7 +1325,6 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("confirmSend fails due to vSize being greater than fee", () async { @@ -1445,7 +1345,6 @@ void main() { expect(secureStore.interactions, 0); verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); }); test("confirmSend fails when broadcast transactions throws", () async { @@ -1471,7 +1370,6 @@ void main() { verifyNoMoreInteractions(client); verifyNoMoreInteractions(cachedClient); verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); }); // // // this test will create a non mocked electrumx client that will try to connect @@ -1484,12 +1382,13 @@ void main() { // // networkType: BasicNetworkType.test, // // client: client, // // cachedClient: cachedClient, - // // priceAPI: priceAPI, + // // // // secureStore: secureStore, + // // // ); // // // // // set node - // // final wallet = await Hive.openBox(testWalletId); + // // final wallet = await Hive.openBox (testWalletId); // // await wallet.put("nodes", { // // "default": { // // "id": "some nodeID", @@ -1520,142 +1419,136 @@ void main() { // // expect(secureStore.interactions, 0); // // verifyNoMoreInteractions(client); // // verifyNoMoreInteractions(cachedClient); - // // verifyNoMoreInteractions(priceAPI); + // // // // }); - test("refresh wallet mutex locked", () async { - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getBatchHistory(args: historyBatchArgs0)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs1)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs2)) - .thenAnswer((_) async => historyBatchResponse); - when(client?.getBatchHistory(args: historyBatchArgs3)) - .thenAnswer((_) async => historyBatchResponse); - List dynamicArgValues = []; - - when(client?.getBatchHistory(args: anyNamed("args"))) - .thenAnswer((realInvocation) async { - if (realInvocation.namedArguments.values.first.length == 1) { - dynamicArgValues.add(realInvocation.namedArguments.values.first); - } - - return historyBatchResponse; - }); - - await Hive.openBox(testWalletId); - - // recover to fill data - await part?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - part?.refreshMutex = true; - - await part?.refresh(); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); - verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); - - for (final arg in dynamicArgValues) { - final map = Map>.from(arg as Map); - - verify(client?.getBatchHistory(args: map)).called(1); - expect(activeScriptHashes.contains(map.values.first.first as String), - true); - } - - expect(secureStore.interactions, 10); - expect(secureStore.writes, 5); - expect(secureStore.reads, 5); - expect(secureStore.deletes, 0); - - verifyNoMoreInteractions(client); - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(tracker); - verifyNoMoreInteractions(priceAPI); - }); - - test("refresh wallet normally", () async { - when(client?.getBlockHeadTip()).thenAnswer((realInvocation) async => - {"height": 520481, "hex": "some block hex"}); - when(client?.getServerFeatures()).thenAnswer((_) async => { - "hosts": {}, - "pruning": null, - "server_version": "Unit tests", - "protocol_min": "1.4", - "protocol_max": "1.4.2", - "genesis_hash": GENESIS_HASH_MAINNET, - "hash_function": "sha256", - "services": [] - }); - when(client?.getHistory(scripthash: anyNamed("scripthash"))) - .thenAnswer((_) async => []); - when(client?.estimateFee(blocks: anyNamed("blocks"))) - .thenAnswer((_) async => Decimal.one); - - when(priceAPI?.getPricesAnd24hChange(baseCurrency: "USD")) - .thenAnswer((_) async => {Coin.particl: Tuple2(Decimal.one, 0.3)}); - - final List dynamicArgValues = []; - - when(client?.getBatchHistory(args: anyNamed("args"))) - .thenAnswer((realInvocation) async { - dynamicArgValues.add(realInvocation.namedArguments.values.first); - return historyBatchResponse; - }); - - await Hive.openBox(testWalletId); - - // recover to fill data - await part?.recoverFromMnemonic( - mnemonic: TEST_MNEMONIC, - maxUnusedAddressGap: 2, - maxNumberOfIndexesToCheck: 1000, - height: 4000); - - when(client?.getBatchHistory(args: anyNamed("args"))) - .thenAnswer((_) async => {}); - when(client?.getBatchUTXOs(args: anyNamed("args"))) - .thenAnswer((_) async => emptyHistoryBatchResponse); - - await part?.refresh(); - - verify(client?.getServerFeatures()).called(1); - verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(3); - verify(client?.estimateFee(blocks: anyNamed("blocks"))).called(3); - verify(client?.getBlockHeadTip()).called(1); - verify(priceAPI?.getPricesAnd24hChange(baseCurrency: "USD")).called(2); - - for (final arg in dynamicArgValues) { - final map = Map>.from(arg as Map); - - verify(client?.getBatchHistory(args: map)).called(1); - } - - expect(secureStore.interactions, 10); - expect(secureStore.writes, 5); - expect(secureStore.reads, 5); - expect(secureStore.deletes, 0); - - verifyNoMoreInteractions(cachedClient); - verifyNoMoreInteractions(priceAPI); - }); + // test("refresh wallet mutex locked", () async { + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getBatchHistory(args: historyBatchArgs0)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs1)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs2)) + // .thenAnswer((_) async => historyBatchResponse); + // when(client?.getBatchHistory(args: historyBatchArgs3)) + // .thenAnswer((_) async => historyBatchResponse); + // List dynamicArgValues = []; + // + // when(client?.getBatchHistory(args: anyNamed("args"))) + // .thenAnswer((realInvocation) async { + // if (realInvocation.namedArguments.values.first.length == 1) { + // dynamicArgValues.add(realInvocation.namedArguments.values.first); + // } + // + // return historyBatchResponse; + // }); + // + // await Hive.openBox(testWalletId); + // + // // recover to fill data + // await part?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // part?.refreshMutex = true; + // + // await part?.refresh(); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs0)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs1)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs2)).called(1); + // verify(client?.getBatchHistory(args: historyBatchArgs3)).called(1); + // + // for (final arg in dynamicArgValues) { + // final map = Map>.from(arg as Map); + // + // verify(client?.getBatchHistory(args: map)).called(1); + // expect(activeScriptHashes.contains(map.values.first.first as String), + // true); + // } + // + // expect(secureStore.interactions, 10); + // expect(secureStore.writes, 5); + // expect(secureStore.reads, 5); + // expect(secureStore.deletes, 0); + // + // verifyNoMoreInteractions(client); + // verifyNoMoreInteractions(cachedClient); + // verifyNoMoreInteractions(tracker); + // }); + // + // test("refresh wallet normally", () async { + // when(client?.getBlockHeadTip()).thenAnswer((realInvocation) async => + // {"height": 520481, "hex": "some block hex"}); + // when(client?.getServerFeatures()).thenAnswer((_) async => { + // "hosts": {}, + // "pruning": null, + // "server_version": "Unit tests", + // "protocol_min": "1.4", + // "protocol_max": "1.4.2", + // "genesis_hash": GENESIS_HASH_MAINNET, + // "hash_function": "sha256", + // "services": [] + // }); + // when(client?.getHistory(scripthash: anyNamed("scripthash"))) + // .thenAnswer((_) async => []); + // when(client?.estimateFee(blocks: anyNamed("blocks"))) + // .thenAnswer((_) async => Decimal.one); + // + // final List dynamicArgValues = []; + // + // when(client?.getBatchHistory(args: anyNamed("args"))) + // .thenAnswer((realInvocation) async { + // dynamicArgValues.add(realInvocation.namedArguments.values.first); + // return historyBatchResponse; + // }); + // + // await Hive.openBox(testWalletId); + // + // // recover to fill data + // await part?.recoverFromMnemonic( + // mnemonic: TEST_MNEMONIC, + // maxUnusedAddressGap: 2, + // maxNumberOfIndexesToCheck: 1000, + // height: 4000); + // + // when(client?.getBatchHistory(args: anyNamed("args"))) + // .thenAnswer((_) async => {}); + // when(client?.getBatchUTXOs(args: anyNamed("args"))) + // .thenAnswer((_) async => emptyHistoryBatchResponse); + // + // await part?.refresh(); + // + // verify(client?.getServerFeatures()).called(1); + // verify(client?.getHistory(scripthash: anyNamed("scripthash"))).called(3); + // verify(client?.estimateFee(blocks: anyNamed("blocks"))).called(3); + // verify(client?.getBlockHeadTip()).called(1); + // + // for (final arg in dynamicArgValues) { + // final map = Map>.from(arg as Map); + // + // verify(client?.getBatchHistory(args: map)).called(1); + // } + // + // expect(secureStore.interactions, 10); + // expect(secureStore.writes, 5); + // expect(secureStore.reads, 5); + // expect(secureStore.deletes, 0); + // + // verifyNoMoreInteractions(cachedClient); + // }); tearDown(() async { await tearDownTestHive(); diff --git a/test/services/coins/particl/particl_wallet_test.mocks.dart b/test/services/coins/particl/particl_wallet_test.mocks.dart index fb0f50d79..11a8de944 100644 --- a/test/services/coins/particl/particl_wallet_test.mocks.dart +++ b/test/services/coins/particl/particl_wallet_test.mocks.dart @@ -3,19 +3,16 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i6; +import 'dart:async' as _i5; import 'package:decimal/decimal.dart' as _i2; -import 'package:http/http.dart' as _i4; import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i7; -import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i5; -import 'package:stackwallet/services/price.dart' as _i9; +import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i6; +import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i4; import 'package:stackwallet/services/transaction_notification_tracker.dart' - as _i11; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i8; + as _i8; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i7; import 'package:stackwallet/utilities/prefs.dart' as _i3; -import 'package:tuple/tuple.dart' as _i10; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -48,26 +45,16 @@ class _FakePrefs_1 extends _i1.SmartFake implements _i3.Prefs { ); } -class _FakeClient_2 extends _i1.SmartFake implements _i4.Client { - _FakeClient_2( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - /// A class which mocks [ElectrumX]. /// /// See the documentation for Mockito's code generation for more information. -class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { +class MockElectrumX extends _i1.Mock implements _i4.ElectrumX { MockElectrumX() { _i1.throwOnMissingStub(this); } @override - set failovers(List<_i5.ElectrumXNode>? _failovers) => super.noSuchMethod( + set failovers(List<_i4.ElectrumXNode>? _failovers) => super.noSuchMethod( Invocation.setter( #failovers, _failovers, @@ -103,7 +90,7 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { returnValue: false, ) as bool); @override - _i6.Future request({ + _i5.Future request({ required String? command, List? args = const [], Duration? connectionTimeout = const Duration(seconds: 60), @@ -122,10 +109,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #retries: retries, }, ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i5.Future.value(), + ) as _i5.Future); @override - _i6.Future>> batchRequest({ + _i5.Future>> batchRequest({ required String? command, required Map>? args, Duration? connectionTimeout = const Duration(seconds: 60), @@ -142,11 +129,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #retries: retries, }, ), - returnValue: _i6.Future>>.value( + returnValue: _i5.Future>>.value( >[]), - ) as _i6.Future>>); + ) as _i5.Future>>); @override - _i6.Future ping({ + _i5.Future ping({ String? requestID, int? retryCount = 1, }) => @@ -159,10 +146,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #retryCount: retryCount, }, ), - returnValue: _i6.Future.value(false), - ) as _i6.Future); + returnValue: _i5.Future.value(false), + ) as _i5.Future); @override - _i6.Future> getBlockHeadTip({String? requestID}) => + _i5.Future> getBlockHeadTip({String? requestID}) => (super.noSuchMethod( Invocation.method( #getBlockHeadTip, @@ -170,10 +157,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future> getServerFeatures({String? requestID}) => + _i5.Future> getServerFeatures({String? requestID}) => (super.noSuchMethod( Invocation.method( #getServerFeatures, @@ -181,10 +168,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future broadcastTransaction({ + _i5.Future broadcastTransaction({ required String? rawTx, String? requestID, }) => @@ -197,10 +184,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #requestID: requestID, }, ), - returnValue: _i6.Future.value(''), - ) as _i6.Future); + returnValue: _i5.Future.value(''), + ) as _i5.Future); @override - _i6.Future> getBalance({ + _i5.Future> getBalance({ required String? scripthash, String? requestID, }) => @@ -214,10 +201,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future>> getHistory({ + _i5.Future>> getHistory({ required String? scripthash, String? requestID, }) => @@ -230,11 +217,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #requestID: requestID, }, ), - returnValue: _i6.Future>>.value( + returnValue: _i5.Future>>.value( >[]), - ) as _i6.Future>>); + ) as _i5.Future>>); @override - _i6.Future>>> getBatchHistory( + _i5.Future>>> getBatchHistory( {required Map>? args}) => (super.noSuchMethod( Invocation.method( @@ -242,11 +229,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { [], {#args: args}, ), - returnValue: _i6.Future>>>.value( + returnValue: _i5.Future>>>.value( >>{}), - ) as _i6.Future>>>); + ) as _i5.Future>>>); @override - _i6.Future>> getUTXOs({ + _i5.Future>> getUTXOs({ required String? scripthash, String? requestID, }) => @@ -259,11 +246,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #requestID: requestID, }, ), - returnValue: _i6.Future>>.value( + returnValue: _i5.Future>>.value( >[]), - ) as _i6.Future>>); + ) as _i5.Future>>); @override - _i6.Future>>> getBatchUTXOs( + _i5.Future>>> getBatchUTXOs( {required Map>? args}) => (super.noSuchMethod( Invocation.method( @@ -271,11 +258,11 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { [], {#args: args}, ), - returnValue: _i6.Future>>>.value( + returnValue: _i5.Future>>>.value( >>{}), - ) as _i6.Future>>>); + ) as _i5.Future>>>); @override - _i6.Future> getTransaction({ + _i5.Future> getTransaction({ required String? txHash, bool? verbose = true, String? requestID, @@ -291,10 +278,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future> getAnonymitySet({ + _i5.Future> getAnonymitySet({ String? groupId = r'1', String? blockhash = r'', String? requestID, @@ -310,10 +297,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future getMintData({ + _i5.Future getMintData({ dynamic mints, String? requestID, }) => @@ -326,10 +313,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #requestID: requestID, }, ), - returnValue: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i5.Future.value(), + ) as _i5.Future); @override - _i6.Future> getUsedCoinSerials({ + _i5.Future> getUsedCoinSerials({ String? requestID, required int? startNumber, }) => @@ -343,19 +330,19 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future getLatestCoinId({String? requestID}) => (super.noSuchMethod( + _i5.Future getLatestCoinId({String? requestID}) => (super.noSuchMethod( Invocation.method( #getLatestCoinId, [], {#requestID: requestID}, ), - returnValue: _i6.Future.value(0), - ) as _i6.Future); + returnValue: _i5.Future.value(0), + ) as _i5.Future); @override - _i6.Future> getFeeRate({String? requestID}) => + _i5.Future> getFeeRate({String? requestID}) => (super.noSuchMethod( Invocation.method( #getFeeRate, @@ -363,10 +350,10 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { {#requestID: requestID}, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future<_i2.Decimal> estimateFee({ + _i5.Future<_i2.Decimal> estimateFee({ String? requestID, required int? blocks, }) => @@ -379,7 +366,7 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { #blocks: blocks, }, ), - returnValue: _i6.Future<_i2.Decimal>.value(_FakeDecimal_0( + returnValue: _i5.Future<_i2.Decimal>.value(_FakeDecimal_0( this, Invocation.method( #estimateFee, @@ -390,15 +377,15 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { }, ), )), - ) as _i6.Future<_i2.Decimal>); + ) as _i5.Future<_i2.Decimal>); @override - _i6.Future<_i2.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( + _i5.Future<_i2.Decimal> relayFee({String? requestID}) => (super.noSuchMethod( Invocation.method( #relayFee, [], {#requestID: requestID}, ), - returnValue: _i6.Future<_i2.Decimal>.value(_FakeDecimal_0( + returnValue: _i5.Future<_i2.Decimal>.value(_FakeDecimal_0( this, Invocation.method( #relayFee, @@ -406,13 +393,13 @@ class MockElectrumX extends _i1.Mock implements _i5.ElectrumX { {#requestID: requestID}, ), )), - ) as _i6.Future<_i2.Decimal>); + ) as _i5.Future<_i2.Decimal>); } /// A class which mocks [CachedElectrumX]. /// /// See the documentation for Mockito's code generation for more information. -class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { +class MockCachedElectrumX extends _i1.Mock implements _i6.CachedElectrumX { MockCachedElectrumX() { _i1.throwOnMissingStub(this); } @@ -441,15 +428,15 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { ), ) as _i3.Prefs); @override - List<_i5.ElectrumXNode> get failovers => (super.noSuchMethod( + List<_i4.ElectrumXNode> get failovers => (super.noSuchMethod( Invocation.getter(#failovers), - returnValue: <_i5.ElectrumXNode>[], - ) as List<_i5.ElectrumXNode>); + returnValue: <_i4.ElectrumXNode>[], + ) as List<_i4.ElectrumXNode>); @override - _i6.Future> getAnonymitySet({ + _i5.Future> getAnonymitySet({ required String? groupId, String? blockhash = r'', - required _i8.Coin? coin, + required _i7.Coin? coin, }) => (super.noSuchMethod( Invocation.method( @@ -462,8 +449,8 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override String base64ToHex(String? source) => (super.noSuchMethod( Invocation.method( @@ -481,9 +468,9 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { returnValue: '', ) as String); @override - _i6.Future> getTransaction({ + _i5.Future> getTransaction({ required String? txHash, - required _i8.Coin? coin, + required _i7.Coin? coin, bool? verbose = true, }) => (super.noSuchMethod( @@ -497,11 +484,11 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { }, ), returnValue: - _i6.Future>.value({}), - ) as _i6.Future>); + _i5.Future>.value({}), + ) as _i5.Future>); @override - _i6.Future> getUsedCoinSerials({ - required _i8.Coin? coin, + _i5.Future> getUsedCoinSerials({ + required _i7.Coin? coin, int? startNumber = 0, }) => (super.noSuchMethod( @@ -513,66 +500,26 @@ class MockCachedElectrumX extends _i1.Mock implements _i7.CachedElectrumX { #startNumber: startNumber, }, ), - returnValue: _i6.Future>.value([]), - ) as _i6.Future>); + returnValue: _i5.Future>.value([]), + ) as _i5.Future>); @override - _i6.Future clearSharedTransactionCache({required _i8.Coin? coin}) => + _i5.Future clearSharedTransactionCache({required _i7.Coin? coin}) => (super.noSuchMethod( Invocation.method( #clearSharedTransactionCache, [], {#coin: coin}, ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); -} - -/// A class which mocks [PriceAPI]. -/// -/// See the documentation for Mockito's code generation for more information. -class MockPriceAPI extends _i1.Mock implements _i9.PriceAPI { - MockPriceAPI() { - _i1.throwOnMissingStub(this); - } - - @override - _i4.Client get client => (super.noSuchMethod( - Invocation.getter(#client), - returnValue: _FakeClient_2( - this, - Invocation.getter(#client), - ), - ) as _i4.Client); - @override - void resetLastCalledToForceNextCallToUpdateCache() => super.noSuchMethod( - Invocation.method( - #resetLastCalledToForceNextCallToUpdateCache, - [], - ), - returnValueForMissingStub: null, - ); - @override - _i6.Future< - Map<_i8.Coin, _i10.Tuple2<_i2.Decimal, double>>> getPricesAnd24hChange( - {required String? baseCurrency}) => - (super.noSuchMethod( - Invocation.method( - #getPricesAnd24hChange, - [], - {#baseCurrency: baseCurrency}, - ), - returnValue: - _i6.Future>>.value( - <_i8.Coin, _i10.Tuple2<_i2.Decimal, double>>{}), - ) as _i6.Future>>); + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); } /// A class which mocks [TransactionNotificationTracker]. /// /// See the documentation for Mockito's code generation for more information. class MockTransactionNotificationTracker extends _i1.Mock - implements _i11.TransactionNotificationTracker { + implements _i8.TransactionNotificationTracker { MockTransactionNotificationTracker() { _i1.throwOnMissingStub(this); } @@ -601,14 +548,14 @@ class MockTransactionNotificationTracker extends _i1.Mock returnValue: false, ) as bool); @override - _i6.Future addNotifiedPending(String? txid) => (super.noSuchMethod( + _i5.Future addNotifiedPending(String? txid) => (super.noSuchMethod( Invocation.method( #addNotifiedPending, [txid], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); @override bool wasNotifiedConfirmed(String? txid) => (super.noSuchMethod( Invocation.method( @@ -618,12 +565,21 @@ class MockTransactionNotificationTracker extends _i1.Mock returnValue: false, ) as bool); @override - _i6.Future addNotifiedConfirmed(String? txid) => (super.noSuchMethod( + _i5.Future addNotifiedConfirmed(String? txid) => (super.noSuchMethod( Invocation.method( #addNotifiedConfirmed, [txid], ), - returnValue: _i6.Future.value(), - returnValueForMissingStub: _i6.Future.value(), - ) as _i6.Future); + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future deleteTransaction(String? txid) => (super.noSuchMethod( + Invocation.method( + #deleteTransaction, + [txid], + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); } diff --git a/test/services/coins/wownero/wownero_wallet_test.dart b/test/services/coins/wownero/wownero_wallet_test.dart index 637a40b81..a14fbf3ad 100644 --- a/test/services/coins/wownero/wownero_wallet_test.dart +++ b/test/services/coins/wownero/wownero_wallet_test.dart @@ -1,369 +1,370 @@ import 'dart:core'; -import 'dart:io'; -import 'dart:math'; +// import 'dart:io'; +// import 'dart:math'; +// +// TODO: move these tests to libmonero +// TODO: use temp dir for wallets testing and not production location +// +// import 'package:cw_core/node.dart'; +// import 'package:cw_core/unspent_coins_info.dart'; +// import 'package:cw_core/wallet_base.dart'; +// import 'package:cw_core/wallet_credentials.dart'; +// import 'package:cw_core/wallet_info.dart'; +// import 'package:cw_core/wallet_service.dart'; +// import 'package:cw_core/wallet_type.dart'; +// import 'package:cw_wownero/wownero_wallet.dart'; +// import 'package:flutter_libmonero/core/key_service.dart'; +// import 'package:flutter_libmonero/core/wallet_creation_service.dart'; +// import 'package:flutter_libmonero/wownero/wownero.dart'; +// import 'package:flutter_test/flutter_test.dart'; +// import 'package:hive/hive.dart'; +// import 'package:hive_test/hive_test.dart'; +// import 'package:path_provider/path_provider.dart'; +// import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; +// +// import 'wownero_wallet_test_data.dart'; +// +// FakeSecureStorage? storage; +// WalletService? walletService; +// KeyService? keysStorage; +// WowneroWalletBase? walletBase; +// late WalletCreationService _walletCreationService; +// dynamic _walletInfoSource; +// +// String path = ''; +// +// String name = ''; +// int nettype = 0; +// WalletType type = WalletType.wownero; -import 'package:cw_core/node.dart'; -import 'package:cw_core/unspent_coins_info.dart'; -import 'package:cw_core/wallet_base.dart'; -import 'package:cw_core/wallet_credentials.dart'; -import 'package:cw_core/wallet_info.dart'; -import 'package:cw_core/wallet_service.dart'; -import 'package:cw_core/wallet_type.dart'; -import 'package:cw_wownero/wownero_wallet.dart'; -import 'package:flutter_libmonero/core/key_service.dart'; -import 'package:flutter_libmonero/core/wallet_creation_service.dart'; -import 'package:flutter_libmonero/wownero/wownero.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hive/hive.dart'; -import 'package:hive_test/hive_test.dart'; -import 'package:mockito/annotations.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; - -import 'wownero_wallet_test_data.dart'; - -FakeSecureStorage? storage; -WalletService? walletService; -KeyService? keysStorage; -WowneroWalletBase? walletBase; -late WalletCreationService _walletCreationService; -dynamic _walletInfoSource; - -String path = ''; - -String name = ''; -int nettype = 0; -WalletType type = WalletType.wownero; - -@GenerateMocks([]) void main() async { - storage = FakeSecureStorage(); - keysStorage = KeyService(storage!); - WalletInfo walletInfo = WalletInfo.external( - id: '', - name: '', - type: type, - isRecovery: false, - restoreHeight: 0, - date: DateTime.now(), - path: '', - address: '', - dirPath: ''); - late WalletCredentials credentials; - - wownero.onStartup(); - - bool hiveAdaptersRegistered = false; - - group("Wownero 14 word seed generation", () { - setUp(() async { - await setUpTestHive(); - if (!hiveAdaptersRegistered) { - hiveAdaptersRegistered = true; - - Hive.registerAdapter(NodeAdapter()); - Hive.registerAdapter(WalletInfoAdapter()); - Hive.registerAdapter(WalletTypeAdapter()); - Hive.registerAdapter(UnspentCoinsInfoAdapter()); - - final wallets = await Hive.openBox('wallets'); - await wallets.put('currentWalletName', name); - - _walletInfoSource = await Hive.openBox(WalletInfo.boxName); - walletService = wownero - .createWowneroWalletService(_walletInfoSource as Box); - } - - bool hasThrown = false; - try { - name = 'namee${Random().nextInt(10000000)}'; - final dirPath = await pathForWalletDir(name: name, type: type); - path = await pathForWallet(name: name, type: type); - credentials = wownero.createWowneroNewWalletCredentials( - name: name, - language: "English", - seedWordsLength: 14); // TODO catch failure - - walletInfo = WalletInfo.external( - id: WalletBase.idFor(name, type), - name: name, - type: type, - isRecovery: false, - restoreHeight: credentials.height ?? 0, - date: DateTime.now(), - path: path, - address: "", - dirPath: dirPath); - credentials.walletInfo = walletInfo; - - _walletCreationService = WalletCreationService( - secureStorage: storage, - walletService: walletService, - keyService: keysStorage, - ); - _walletCreationService.changeWalletType(); - } catch (e, s) { - print(e); - print(s); - hasThrown = true; - } - expect(hasThrown, false); - }); - - test("Wownero 14 word seed address generation", () async { - final wallet = await _walletCreationService.create(credentials); - // TODO validate mnemonic - walletInfo.address = wallet.walletAddresses.address; - - bool hasThrown = false; - try { - await _walletInfoSource.add(walletInfo); - walletBase?.close(); - walletBase = wallet as WowneroWalletBase; - - expect( - await walletBase! - .validateAddress(wallet.walletAddresses.address ?? ''), - true); - } catch (_) { - hasThrown = true; - } - expect(hasThrown, false); - - // Address validation - expect(await walletBase!.validateAddress(''), false); - expect( - await walletBase!.validateAddress( - 'Wo3jmHvTMLwE6h29fpgcb8PbJSpaKuqM7XTXVfiiu8bLCZsJvrQCbQSJR48Vo3BWNQKsMsXZ4VixndXTH25QtorC27NCjmsEi'), - true); - expect( - await walletBase!.validateAddress( - 'WasdfHvTMLwE6h29fpgcb8PbJSpaKuqM7XTXVfiiu8bLCZsJvrQCbQSJR48Vo3BWNQKsMsXZ4VixndXTH25QtorC27NCjmjkl'), - false); - - walletBase?.close(); - walletBase = wallet as WowneroWalletBase; - }); - - // TODO delete left over wallet file with name: name - }); - - group("Wownero 14 word seed restoration", () { - setUp(() async { - bool hasThrown = false; - try { - name = 'namee${Random().nextInt(10000000)}'; - final dirPath = await pathForWalletDir(name: name, type: type); - path = await pathForWallet(name: name, type: type); - credentials = wownero.createWowneroRestoreWalletFromSeedCredentials( - name: name, - height: 465760, - mnemonic: testMnemonic14); // TODO catch failure - - walletInfo = WalletInfo.external( - id: WalletBase.idFor(name, type), - name: name, - type: type, - isRecovery: false, - restoreHeight: credentials.height ?? 0, - date: DateTime.now(), - path: path, - address: "", - dirPath: dirPath); - credentials.walletInfo = walletInfo; - - _walletCreationService = WalletCreationService( - secureStorage: storage, - walletService: walletService, - keyService: keysStorage, - ); - _walletCreationService.changeWalletType(); - } catch (e, s) { - print(e); - print(s); - hasThrown = true; - } - expect(hasThrown, false); - }); - - test("Wownero 14 word seed address generation", () async { - final wallet = await _walletCreationService.restoreFromSeed(credentials); - walletInfo.address = wallet.walletAddresses.address; - - bool hasThrown = false; - try { - await _walletInfoSource.add(walletInfo); - walletBase?.close(); - walletBase = wallet as WowneroWalletBase; - - expect(walletInfo.address, mainnetTestData14[0][0]); - expect(await walletBase!.getTransactionAddress(0, 0), - mainnetTestData14[0][0]); - expect(await walletBase!.getTransactionAddress(0, 1), - mainnetTestData14[0][1]); - expect(await walletBase!.getTransactionAddress(0, 2), - mainnetTestData14[0][2]); - expect(await walletBase!.getTransactionAddress(1, 0), - mainnetTestData14[1][0]); - expect(await walletBase!.getTransactionAddress(1, 1), - mainnetTestData14[1][1]); - expect(await walletBase!.getTransactionAddress(1, 2), - mainnetTestData14[1][2]); - } catch (_) { - hasThrown = true; - } - expect(hasThrown, false); - - walletBase?.close(); - walletBase = wallet as WowneroWalletBase; - }); - - // TODO delete left over wallet file with name: name - }); - - group("Wownero 25 word seed generation", () { - setUp(() async { - bool hasThrown = false; - try { - name = 'namee${Random().nextInt(10000000)}'; - final dirPath = await pathForWalletDir(name: name, type: type); - path = await pathForWallet(name: name, type: type); - credentials = wownero.createWowneroNewWalletCredentials( - name: name, - language: "English", - seedWordsLength: 25); // TODO catch failure - - walletInfo = WalletInfo.external( - id: WalletBase.idFor(name, type), - name: name, - type: type, - isRecovery: false, - restoreHeight: credentials.height ?? 0, - date: DateTime.now(), - path: path, - address: "", - dirPath: dirPath); - credentials.walletInfo = walletInfo; - - _walletCreationService = WalletCreationService( - secureStorage: storage, - walletService: walletService, - keyService: keysStorage, - ); - _walletCreationService.changeWalletType(); - } catch (e, s) { - print(e); - print(s); - hasThrown = true; - } - expect(hasThrown, false); - }); - - test("Wownero 25 word seed address generation", () async { - final wallet = await _walletCreationService.create(credentials); - // TODO validate mnemonic - walletInfo.address = wallet.walletAddresses.address; - - bool hasThrown = false; - try { - await _walletInfoSource.add(walletInfo); - walletBase?.close(); - walletBase = wallet as WowneroWalletBase; - - // TODO validate - //expect(walletInfo.address, mainnetTestData14[0][0]); - } catch (_) { - hasThrown = true; - } - expect(hasThrown, false); - - walletBase?.close(); - walletBase = wallet as WowneroWalletBase; - }); - - // TODO delete left over wallet file with name: name - }); - - group("Wownero 25 word seed restoration", () { - setUp(() async { - bool hasThrown = false; - try { - name = 'namee${Random().nextInt(10000000)}'; - final dirPath = await pathForWalletDir(name: name, type: type); - path = await pathForWallet(name: name, type: type); - credentials = wownero.createWowneroRestoreWalletFromSeedCredentials( - name: name, - height: 465760, - mnemonic: testMnemonic25); // TODO catch failure - - walletInfo = WalletInfo.external( - id: WalletBase.idFor(name, type), - name: name, - type: type, - isRecovery: false, - restoreHeight: credentials.height ?? 0, - date: DateTime.now(), - path: path, - address: "", - dirPath: dirPath); - credentials.walletInfo = walletInfo; - - _walletCreationService = WalletCreationService( - secureStorage: storage, - walletService: walletService, - keyService: keysStorage, - ); - _walletCreationService.changeWalletType(); - } catch (e, s) { - print(e); - print(s); - hasThrown = true; - } - expect(hasThrown, false); - }); - - test("Wownero 25 word seed address generation", () async { - final wallet = await _walletCreationService.restoreFromSeed(credentials); - walletInfo.address = wallet.walletAddresses.address; - - bool hasThrown = false; - try { - await _walletInfoSource.add(walletInfo); - walletBase?.close(); - walletBase = wallet as WowneroWalletBase; - - expect(walletInfo.address, mainnetTestData25[0][0]); - } catch (_) { - hasThrown = true; - } - expect(hasThrown, false); - - walletBase?.close(); - walletBase = wallet as WowneroWalletBase; - }); - - // TODO delete left over wallet file with name: name - }); + // storage = FakeSecureStorage(); + // keysStorage = KeyService(storage!); + // WalletInfo walletInfo = WalletInfo.external( + // id: '', + // name: '', + // type: type, + // isRecovery: false, + // restoreHeight: 0, + // date: DateTime.now(), + // path: '', + // address: '', + // dirPath: ''); + // late WalletCredentials credentials; + // + // wownero.onStartup(); + // + // bool hiveAdaptersRegistered = false; + // + // group("Wownero 14 word seed generation", () { + // setUp(() async { + // await setUpTestHive(); + // if (!hiveAdaptersRegistered) { + // hiveAdaptersRegistered = true; + // + // Hive.registerAdapter(NodeAdapter()); + // Hive.registerAdapter(WalletInfoAdapter()); + // Hive.registerAdapter(WalletTypeAdapter()); + // Hive.registerAdapter(UnspentCoinsInfoAdapter()); + // + // final wallets = await Hive.openBox('wallets'); + // await wallets.put('currentWalletName', name); + // + // _walletInfoSource = await Hive.openBox(WalletInfo.boxName); + // walletService = wownero + // .createWowneroWalletService(_walletInfoSource as Box); + // } + // + // bool hasThrown = false; + // try { + // name = 'namee${Random().nextInt(10000000)}'; + // final dirPath = await pathForWalletDir(name: name, type: type); + // path = await pathForWallet(name: name, type: type); + // credentials = wownero.createWowneroNewWalletCredentials( + // name: name, + // language: "English", + // seedWordsLength: 14); // TODO catch failure + // + // walletInfo = WalletInfo.external( + // id: WalletBase.idFor(name, type), + // name: name, + // type: type, + // isRecovery: false, + // restoreHeight: credentials.height ?? 0, + // date: DateTime.now(), + // path: path, + // address: "", + // dirPath: dirPath); + // credentials.walletInfo = walletInfo; + // + // _walletCreationService = WalletCreationService( + // secureStorage: storage, + // walletService: walletService, + // keyService: keysStorage, + // ); + // _walletCreationService.changeWalletType(); + // } catch (e, s) { + // print(e); + // print(s); + // hasThrown = true; + // } + // expect(hasThrown, false); + // }); + // + // test("Wownero 14 word seed address generation", () async { + // final wallet = await _walletCreationService.create(credentials); + // // TODO validate mnemonic + // walletInfo.address = wallet.walletAddresses.address; + // + // bool hasThrown = false; + // try { + // await _walletInfoSource.add(walletInfo); + // walletBase?.close(); + // walletBase = wallet as WowneroWalletBase; + // + // expect( + // await walletBase! + // .validateAddress(wallet.walletAddresses.address ?? ''), + // true); + // } catch (_) { + // hasThrown = true; + // } + // expect(hasThrown, false); + // + // // Address validation + // expect(walletBase!.validateAddress(''), false); + // expect( + // walletBase!.validateAddress( + // 'Wo3jmHvTMLwE6h29fpgcb8PbJSpaKuqM7XTXVfiiu8bLCZsJvrQCbQSJR48Vo3BWNQKsMsXZ4VixndXTH25QtorC27NCjmsEi'), + // true); + // expect( + // walletBase!.validateAddress( + // 'WasdfHvTMLwE6h29fpgcb8PbJSpaKuqM7XTXVfiiu8bLCZsJvrQCbQSJR48Vo3BWNQKsMsXZ4VixndXTH25QtorC27NCjmjkl'), + // false); + // + // walletBase?.close(); + // walletBase = wallet as WowneroWalletBase; + // }); + // + // // TODO delete left over wallet file with name: name + // }); + // + // group("Wownero 14 word seed restoration", () { + // setUp(() async { + // bool hasThrown = false; + // try { + // name = 'namee${Random().nextInt(10000000)}'; + // final dirPath = await pathForWalletDir(name: name, type: type); + // path = await pathForWallet(name: name, type: type); + // credentials = wownero.createWowneroRestoreWalletFromSeedCredentials( + // name: name, + // height: 465760, + // mnemonic: testMnemonic14); // TODO catch failure + // + // walletInfo = WalletInfo.external( + // id: WalletBase.idFor(name, type), + // name: name, + // type: type, + // isRecovery: false, + // restoreHeight: credentials.height ?? 0, + // date: DateTime.now(), + // path: path, + // address: "", + // dirPath: dirPath); + // credentials.walletInfo = walletInfo; + // + // _walletCreationService = WalletCreationService( + // secureStorage: storage, + // walletService: walletService, + // keyService: keysStorage, + // ); + // _walletCreationService.changeWalletType(); + // } catch (e, s) { + // print(e); + // print(s); + // hasThrown = true; + // } + // expect(hasThrown, false); + // }); + // + // test("Wownero 14 word seed address generation", () async { + // final wallet = await _walletCreationService.restoreFromSeed(credentials); + // walletInfo.address = wallet.walletAddresses.address; + // + // bool hasThrown = false; + // try { + // await _walletInfoSource.add(walletInfo); + // walletBase?.close(); + // walletBase = wallet as WowneroWalletBase; + // + // expect(walletInfo.address, mainnetTestData14[0][0]); + // expect( + // walletBase!.getTransactionAddress(0, 0), mainnetTestData14[0][0]); + // expect( + // walletBase!.getTransactionAddress(0, 1), mainnetTestData14[0][1]); + // expect( + // walletBase!.getTransactionAddress(0, 2), mainnetTestData14[0][2]); + // expect( + // walletBase!.getTransactionAddress(1, 0), mainnetTestData14[1][0]); + // expect( + // walletBase!.getTransactionAddress(1, 1), mainnetTestData14[1][1]); + // expect( + // walletBase!.getTransactionAddress(1, 2), mainnetTestData14[1][2]); + // } catch (_) { + // hasThrown = true; + // } + // expect(hasThrown, false); + // + // walletBase?.close(); + // walletBase = wallet as WowneroWalletBase; + // }); + // + // // TODO delete left over wallet file with name: name + // }); + // + // group("Wownero 25 word seed generation", () { + // setUp(() async { + // bool hasThrown = false; + // try { + // name = 'namee${Random().nextInt(10000000)}'; + // final dirPath = await pathForWalletDir(name: name, type: type); + // path = await pathForWallet(name: name, type: type); + // credentials = wownero.createWowneroNewWalletCredentials( + // name: name, + // language: "English", + // seedWordsLength: 25); // TODO catch failure + // + // walletInfo = WalletInfo.external( + // id: WalletBase.idFor(name, type), + // name: name, + // type: type, + // isRecovery: false, + // restoreHeight: credentials.height ?? 0, + // date: DateTime.now(), + // path: path, + // address: "", + // dirPath: dirPath); + // credentials.walletInfo = walletInfo; + // + // _walletCreationService = WalletCreationService( + // secureStorage: storage, + // walletService: walletService, + // keyService: keysStorage, + // ); + // _walletCreationService.changeWalletType(); + // } catch (e, s) { + // print(e); + // print(s); + // hasThrown = true; + // } + // expect(hasThrown, false); + // }); + // + // test("Wownero 25 word seed address generation", () async { + // final wallet = await _walletCreationService.create(credentials); + // // TODO validate mnemonic + // walletInfo.address = wallet.walletAddresses.address; + // + // bool hasThrown = false; + // try { + // await _walletInfoSource.add(walletInfo); + // walletBase?.close(); + // walletBase = wallet as WowneroWalletBase; + // + // // TODO validate + // //expect(walletInfo.address, mainnetTestData14[0][0]); + // } catch (_) { + // hasThrown = true; + // } + // expect(hasThrown, false); + // + // walletBase?.close(); + // walletBase = wallet as WowneroWalletBase; + // }); + // + // // TODO delete left over wallet file with name: name + // }); + // + // group("Wownero 25 word seed restoration", () { + // setUp(() async { + // bool hasThrown = false; + // try { + // name = 'namee${Random().nextInt(10000000)}'; + // final dirPath = await pathForWalletDir(name: name, type: type); + // path = await pathForWallet(name: name, type: type); + // credentials = wownero.createWowneroRestoreWalletFromSeedCredentials( + // name: name, + // height: 465760, + // mnemonic: testMnemonic25); // TODO catch failure + // + // walletInfo = WalletInfo.external( + // id: WalletBase.idFor(name, type), + // name: name, + // type: type, + // isRecovery: false, + // restoreHeight: credentials.height ?? 0, + // date: DateTime.now(), + // path: path, + // address: "", + // dirPath: dirPath); + // credentials.walletInfo = walletInfo; + // + // _walletCreationService = WalletCreationService( + // secureStorage: storage, + // walletService: walletService, + // keyService: keysStorage, + // ); + // _walletCreationService.changeWalletType(); + // } catch (e, s) { + // print(e); + // print(s); + // hasThrown = true; + // } + // expect(hasThrown, false); + // }); + // + // test("Wownero 25 word seed address generation", () async { + // final wallet = await _walletCreationService.restoreFromSeed(credentials); + // walletInfo.address = wallet.walletAddresses.address; + // + // bool hasThrown = false; + // try { + // await _walletInfoSource.add(walletInfo); + // walletBase?.close(); + // walletBase = wallet as WowneroWalletBase; + // + // expect(walletInfo.address, mainnetTestData25[0][0]); + // } catch (_) { + // hasThrown = true; + // } + // expect(hasThrown, false); + // + // walletBase?.close(); + // walletBase = wallet as WowneroWalletBase; + // }); + // + // // TODO delete left over wallet file with name: name + // }); } -Future pathForWalletDir( - {required String name, required WalletType type}) async { - Directory root = (await getApplicationDocumentsDirectory()); - if (Platform.isIOS) { - root = (await getLibraryDirectory()); - } - final prefix = walletTypeToString(type).toLowerCase(); - final walletsDir = Directory('${root.path}/wallets'); - final walletDire = Directory('${walletsDir.path}/$prefix/$name'); - - if (!walletDire.existsSync()) { - walletDire.createSync(recursive: true); - } - - return walletDire.path; -} - -Future pathForWallet( - {required String name, required WalletType type}) async => - await pathForWalletDir(name: name, type: type) - .then((path) => path + '/$name'); +// Future pathForWalletDir( +// {required String name, required WalletType type}) async { +// Directory root = (await getApplicationDocumentsDirectory()); +// if (Platform.isIOS) { +// root = (await getLibraryDirectory()); +// } +// final prefix = walletTypeToString(type).toLowerCase(); +// final walletsDir = Directory('${root.path}/wallets'); +// final walletDire = Directory('${walletsDir.path}/$prefix/$name'); +// +// if (!walletDire.existsSync()) { +// walletDire.createSync(recursive: true); +// } +// +// return walletDire.path; +// } +// +// Future pathForWallet( +// {required String name, required WalletType type}) async => +// await pathForWalletDir(name: name, type: type) +// .then((path) => '$path/$name'); diff --git a/test/services/node_service_test.dart b/test/services/node_service_test.dart index cea30be2d..2bd889b6e 100644 --- a/test/services/node_service_test.dart +++ b/test/services/node_service_test.dart @@ -1,7 +1,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:hive/hive.dart'; import 'package:hive_test/hive_test.dart'; -import 'package:stackwallet/hive/db.dart'; +import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/services/node_service.dart'; import 'package:stackwallet/utilities/default_nodes.dart'; diff --git a/test/services/wallets_service_test.dart b/test/services/wallets_service_test.dart index 9cd808b7c..86659206a 100644 --- a/test/services/wallets_service_test.dart +++ b/test/services/wallets_service_test.dart @@ -2,16 +2,11 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:hive/hive.dart'; import 'package:hive_test/hive_test.dart'; import 'package:mockito/annotations.dart'; -import 'package:mockito/mockito.dart'; -import 'package:stackwallet/hive/db.dart'; -import 'package:stackwallet/models/notification_model.dart'; -import 'package:stackwallet/models/trade_wallet_lookup.dart'; +import 'package:stackwallet/db/hive/db.dart'; import 'package:stackwallet/services/wallets_service.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart'; -import 'wallets_service_test.mocks.dart'; - @GenerateMocks([SecureStorageWrapper]) void main() { setUp(() async { @@ -124,58 +119,58 @@ void main() { test("get non existent wallet id", () async { final service = WalletsService(secureStorageInterface: FakeSecureStorage()); - expectLater(await service.getWalletId("wallet 99"), null); + await expectLater(await service.getWalletId("wallet 99"), null); }); - test("delete a wallet", () async { - await Hive.openBox(DB.boxNameWalletsToDeleteOnStart); - await Hive.openBox(DB.boxNameTradeLookup); - await Hive.openBox(DB.boxNameNotifications); - final secureStore = MockSecureStorageWrapper(); - - when(secureStore.delete(key: "wallet_id_pin")).thenAnswer((_) async {}); - when(secureStore.delete(key: "wallet_id_mnemonic")) - .thenAnswer((_) async {}); - - final service = WalletsService(secureStorageInterface: secureStore); - - expect(await service.deleteWallet("My Firo Wallet", false), 0); - expect((await service.walletNames).length, 1); - - verify(secureStore.delete(key: "wallet_id_pin")).called(1); - verify(secureStore.delete(key: "wallet_id_mnemonic")).called(1); - - verifyNoMoreInteractions(secureStore); - }); - - test("delete last wallet", () async { - await Hive.openBox(DB.boxNameWalletsToDeleteOnStart); - await Hive.openBox(DB.boxNameTradeLookup); - await Hive.openBox(DB.boxNameNotifications); - final wallets = await Hive.openBox('wallets'); - await wallets.put('names', { - "wallet_id": { - "name": "My Firo Wallet", - "id": "wallet_id", - "coin": "bitcoin", - }, - }); - final secureStore = MockSecureStorageWrapper(); - - when(secureStore.delete(key: "wallet_id_pin")).thenAnswer((_) async {}); - when(secureStore.delete(key: "wallet_id_mnemonic")) - .thenAnswer((_) async {}); - - final service = WalletsService(secureStorageInterface: secureStore); - - expect(await service.deleteWallet("My Firo Wallet", false), 2); - expect((await service.walletNames).length, 0); - - verify(secureStore.delete(key: "wallet_id_pin")).called(1); - verify(secureStore.delete(key: "wallet_id_mnemonic")).called(1); - - verifyNoMoreInteractions(secureStore); - }); + // test("delete a wallet", () async { + // await Hive.openBox(DB.boxNameWalletsToDeleteOnStart); + // await Hive.openBox(DB.boxNameTradeLookup); + // await Hive.openBox(DB.boxNameNotifications); + // final secureStore = MockSecureStorageWrapper(); + // + // when(secureStore.delete(key: "wallet_id_pin")).thenAnswer((_) async {}); + // when(secureStore.delete(key: "wallet_id_mnemonic")) + // .thenAnswer((_) async {}); + // + // final service = WalletsService(secureStorageInterface: secureStore); + // + // expect(await service.deleteWallet("My Firo Wallet", false), 0); + // expect((await service.walletNames).length, 1); + // + // verify(secureStore.delete(key: "wallet_id_pin")).called(1); + // verify(secureStore.delete(key: "wallet_id_mnemonic")).called(1); + // + // verifyNoMoreInteractions(secureStore); + // }); + // + // test("delete last wallet", () async { + // await Hive.openBox(DB.boxNameWalletsToDeleteOnStart); + // await Hive.openBox(DB.boxNameTradeLookup); + // await Hive.openBox(DB.boxNameNotifications); + // final wallets = await Hive.openBox('wallets'); + // await wallets.put('names', { + // "wallet_id": { + // "name": "My Firo Wallet", + // "id": "wallet_id", + // "coin": "bitcoin", + // }, + // }); + // final secureStore = MockSecureStorageWrapper(); + // + // when(secureStore.delete(key: "wallet_id_pin")).thenAnswer((_) async {}); + // when(secureStore.delete(key: "wallet_id_mnemonic")) + // .thenAnswer((_) async {}); + // + // final service = WalletsService(secureStorageInterface: secureStore); + // + // expect(await service.deleteWallet("My Firo Wallet", false), 2); + // expect((await service.walletNames).length, 0); + // + // verify(secureStore.delete(key: "wallet_id_pin")).called(1); + // verify(secureStore.delete(key: "wallet_id_mnemonic")).called(1); + // + // verifyNoMoreInteractions(secureStore); + // }); // test("get", () async { // final service = WalletsService(); diff --git a/test/services/wallets_service_test.mocks.dart b/test/services/wallets_service_test.mocks.dart index 19d525196..c4085b7c3 100644 --- a/test/services/wallets_service_test.mocks.dart +++ b/test/services/wallets_service_test.mocks.dart @@ -30,6 +30,11 @@ class MockSecureStorageWrapper extends _i1.Mock _i1.throwOnMissingStub(this); } + @override + _i3.Future> get keys => (super.noSuchMethod( + Invocation.getter(#keys), + returnValue: _i3.Future>.value([]), + ) as _i3.Future>); @override _i3.Future read({ required String? key, @@ -112,4 +117,29 @@ class MockSecureStorageWrapper extends _i1.Mock returnValue: _i3.Future.value(), returnValueForMissingStub: _i3.Future.value(), ) as _i3.Future); + @override + _i3.Future deleteAll({ + _i4.IOSOptions? iOptions, + _i4.AndroidOptions? aOptions, + _i4.LinuxOptions? lOptions, + _i4.WebOptions? webOptions, + _i4.MacOsOptions? mOptions, + _i4.WindowsOptions? wOptions, + }) => + (super.noSuchMethod( + Invocation.method( + #deleteAll, + [], + { + #iOptions: iOptions, + #aOptions: aOptions, + #lOptions: lOptions, + #webOptions: webOptions, + #mOptions: mOptions, + #wOptions: wOptions, + }, + ), + returnValue: _i3.Future.value(), + returnValueForMissingStub: _i3.Future.value(), + ) as _i3.Future); } diff --git a/test/utilities/amount/amount_test.dart b/test/utilities/amount/amount_test.dart new file mode 100644 index 000000000..c909ff122 --- /dev/null +++ b/test/utilities/amount/amount_test.dart @@ -0,0 +1,219 @@ +import 'package:decimal/decimal.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; + +void main() { + test("Basic Amount Constructor tests", () { + Amount amount = Amount(rawValue: BigInt.two, fractionDigits: 0); + expect(amount.fractionDigits, 0); + expect(amount.raw, BigInt.two); + expect(amount.decimal, Decimal.fromInt(2)); + + amount = Amount(rawValue: BigInt.two, fractionDigits: 2); + expect(amount.fractionDigits, 2); + expect(amount.raw, BigInt.two); + expect(amount.decimal, Decimal.parse("0.02")); + + amount = Amount(rawValue: BigInt.from(123456789), fractionDigits: 7); + expect(amount.fractionDigits, 7); + expect(amount.raw, BigInt.from(123456789)); + expect(amount.decimal, Decimal.parse("12.3456789")); + + bool didThrow = false; + try { + amount = Amount(rawValue: BigInt.one, fractionDigits: -1); + } catch (_) { + didThrow = true; + } + expect(didThrow, true); + }); + + test("Named fromDecimal Amount Constructor tests", () { + Amount amount = Amount.fromDecimal(Decimal.fromInt(2), fractionDigits: 0); + expect(amount.fractionDigits, 0); + expect(amount.raw, BigInt.two); + expect(amount.decimal, Decimal.fromInt(2)); + + amount = Amount.fromDecimal(Decimal.fromInt(2), fractionDigits: 2); + expect(amount.fractionDigits, 2); + expect(amount.raw, BigInt.from(200)); + expect(amount.decimal, Decimal.fromInt(2)); + + amount = + Amount.fromDecimal(Decimal.parse("0.0123456789"), fractionDigits: 7); + expect(amount.fractionDigits, 7); + expect(amount.raw, BigInt.from(123456)); + expect(amount.decimal, Decimal.parse("0.0123456")); + + bool didThrow = false; + try { + amount = Amount.fromDecimal(Decimal.fromInt(2), fractionDigits: -1); + } catch (_) { + didThrow = true; + } + expect(didThrow, true); + }); + + group("serialization", () { + test("toMap", () { + expect( + Amount(rawValue: BigInt.two, fractionDigits: 8).toMap(), + {"raw": "2", "fractionDigits": 8}, + ); + expect( + Amount.fromDecimal(Decimal.fromInt(2), fractionDigits: 8).toMap(), + {"raw": "200000000", "fractionDigits": 8}, + ); + }); + + test("toJsonString", () { + expect( + Amount(rawValue: BigInt.two, fractionDigits: 8).toJsonString(), + '{"raw":"2","fractionDigits":8}', + ); + expect( + Amount.fromDecimal(Decimal.fromInt(2), fractionDigits: 8) + .toJsonString(), + '{"raw":"200000000","fractionDigits":8}', + ); + }); + + test("localizedStringAsFixed", () { + expect( + Amount(rawValue: BigInt.two, fractionDigits: 8) + .localizedStringAsFixed(locale: "en_US"), + "0.00000002", + ); + expect( + Amount(rawValue: BigInt.two, fractionDigits: 8) + .localizedStringAsFixed(locale: "en_US", decimalPlaces: 2), + "0.00", + ); + expect( + Amount.fromDecimal(Decimal.fromInt(2), fractionDigits: 8) + .localizedStringAsFixed(locale: "en_US"), + "2.00000000", + ); + expect( + Amount.fromDecimal(Decimal.fromInt(2), fractionDigits: 8) + .localizedStringAsFixed(locale: "en_US", decimalPlaces: 4), + "2.0000", + ); + expect( + Amount.fromDecimal(Decimal.fromInt(2), fractionDigits: 8) + .localizedStringAsFixed(locale: "en_US", decimalPlaces: 0), + "2", + ); + }); + }); + + group("deserialization", () { + test("fromSerializedJsonString", () { + expect( + Amount.fromSerializedJsonString( + '{"raw":"200000000","fractionDigits":8}'), + Amount.fromDecimal(Decimal.parse("2"), fractionDigits: 8), + ); + }); + }); + + group("operators", () { + final one = Amount(rawValue: BigInt.one, fractionDigits: 0); + final two = Amount(rawValue: BigInt.two, fractionDigits: 0); + final four4 = Amount(rawValue: BigInt.from(4), fractionDigits: 4); + final four4_2 = Amount(rawValue: BigInt.from(4), fractionDigits: 4); + final four5 = Amount(rawValue: BigInt.from(4), fractionDigits: 5); + + test(">", () { + expect(one > two, false); + expect(one > one, false); + + expect(two > two, false); + expect(two > one, true); + }); + + test("<", () { + expect(one < two, true); + expect(one < one, false); + + expect(two < two, false); + expect(two < one, false); + }); + + test(">=", () { + expect(one >= two, false); + expect(one >= one, true); + + expect(two >= two, true); + expect(two >= one, true); + }); + + test("<=", () { + expect(one <= two, true); + expect(one <= one, true); + + expect(two <= two, true); + expect(two <= one, false); + }); + + test("<=", () { + expect(one <= two, true); + expect(one <= one, true); + + expect(two <= two, true); + expect(two <= one, false); + }); + + test("==", () { + expect(one == two, false); + expect(one == one, true); + + expect(BigInt.from(2) == BigInt.from(2), true); + + expect(four4 == four4_2, true); + expect(four4 == four5, false); + }); + + test("+", () { + expect(one + two, Amount(rawValue: BigInt.from(3), fractionDigits: 0)); + expect(one + one, Amount(rawValue: BigInt.from(2), fractionDigits: 0)); + + expect( + Amount(rawValue: BigInt.from(3), fractionDigits: 0) + + Amount(rawValue: BigInt.from(-5), fractionDigits: 0), + Amount(rawValue: BigInt.from(-2), fractionDigits: 0)); + expect( + Amount(rawValue: BigInt.from(-3), fractionDigits: 0) + + Amount(rawValue: BigInt.from(6), fractionDigits: 0), + Amount(rawValue: BigInt.from(3), fractionDigits: 0)); + }); + + test("-", () { + expect(one - two, Amount(rawValue: BigInt.from(-1), fractionDigits: 0)); + expect(one - one, Amount(rawValue: BigInt.from(0), fractionDigits: 0)); + + expect( + Amount(rawValue: BigInt.from(3), fractionDigits: 0) - + Amount(rawValue: BigInt.from(-5), fractionDigits: 0), + Amount(rawValue: BigInt.from(8), fractionDigits: 0)); + expect( + Amount(rawValue: BigInt.from(-3), fractionDigits: 0) - + Amount(rawValue: BigInt.from(6), fractionDigits: 0), + Amount(rawValue: BigInt.from(-9), fractionDigits: 0)); + }); + + test("*", () { + expect(one * two, Amount(rawValue: BigInt.from(2), fractionDigits: 0)); + expect(one * one, Amount(rawValue: BigInt.from(1), fractionDigits: 0)); + + expect( + Amount(rawValue: BigInt.from(3), fractionDigits: 0) * + Amount(rawValue: BigInt.from(-5), fractionDigits: 0), + Amount(rawValue: BigInt.from(-15), fractionDigits: 0)); + expect( + Amount(rawValue: BigInt.from(-3), fractionDigits: 0) * + Amount(rawValue: BigInt.from(-6), fractionDigits: 0), + Amount(rawValue: BigInt.from(18), fractionDigits: 0)); + }); + }); +} diff --git a/test/utilities/amount/amount_unit_test.dart b/test/utilities/amount/amount_unit_test.dart new file mode 100644 index 000000000..2dcd5125c --- /dev/null +++ b/test/utilities/amount/amount_unit_test.dart @@ -0,0 +1,144 @@ +import 'package:decimal/decimal.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/amount/amount_unit.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; + +void main() { + test("displayAmount BTC", () { + final Amount amount = Amount( + rawValue: BigInt.from(1012345678), + fractionDigits: 8, + ); + + expect( + AmountUnit.normal.displayAmount( + amount: amount, + locale: "en_US", + coin: Coin.bitcoin, + maxDecimalPlaces: 8, + ), + "10.12345678 BTC", + ); + + expect( + AmountUnit.milli.displayAmount( + amount: amount, + locale: "en_US", + coin: Coin.bitcoin, + maxDecimalPlaces: 8, + ), + "10123.45678 mBTC", + ); + + expect( + AmountUnit.micro.displayAmount( + amount: amount, + locale: "en_US", + coin: Coin.bitcoin, + maxDecimalPlaces: 8, + ), + "10123456.78 µBTC", + ); + + expect( + AmountUnit.nano.displayAmount( + amount: amount, + locale: "en_US", + coin: Coin.bitcoin, + maxDecimalPlaces: 8, + ), + "1012345678 sats", + ); + final dec = Decimal.parse("10.123456789123456789"); + + expect(dec.toString(), "10.123456789123456789"); + }); + + test("displayAmount ETH", () { + final Amount amount = Amount.fromDecimal( + Decimal.parse("10.123456789123456789"), + fractionDigits: Coin.ethereum.decimals, + ); + + expect( + AmountUnit.normal.displayAmount( + amount: amount, + locale: "en_US", + coin: Coin.ethereum, + maxDecimalPlaces: 8, + ), + "10.12345678 ETH", + ); + + expect( + AmountUnit.normal.displayAmount( + amount: amount, + locale: "en_US", + coin: Coin.ethereum, + maxDecimalPlaces: 18, + ), + "10.123456789123456789 ETH", + ); + + expect( + AmountUnit.milli.displayAmount( + amount: amount, + locale: "en_US", + coin: Coin.ethereum, + maxDecimalPlaces: 9, + ), + "10123.456789123 mETH", + ); + + expect( + AmountUnit.micro.displayAmount( + amount: amount, + locale: "en_US", + coin: Coin.ethereum, + maxDecimalPlaces: 8, + ), + "10123456.78912345 µETH", + ); + + expect( + AmountUnit.nano.displayAmount( + amount: amount, + locale: "en_US", + coin: Coin.ethereum, + maxDecimalPlaces: 1, + ), + "10123456789.1 gwei", + ); + + expect( + AmountUnit.pico.displayAmount( + amount: amount, + locale: "en_US", + coin: Coin.ethereum, + maxDecimalPlaces: 18, + ), + "10123456789123.456789 mwei", + ); + + expect( + AmountUnit.femto.displayAmount( + amount: amount, + locale: "en_US", + coin: Coin.ethereum, + maxDecimalPlaces: 4, + ), + "10123456789123456.789 kwei", + ); + + expect( + AmountUnit.atto.displayAmount( + amount: amount, + locale: "en_US", + coin: Coin.ethereum, + maxDecimalPlaces: 1, + ), + "10123456789123456789 wei", + ); + }); +} diff --git a/test/widget_tests/address_book_card_test.dart b/test/widget_tests/address_book_card_test.dart index 07b1387df..faf78be2f 100644 --- a/test/widget_tests/address_book_card_test.dart +++ b/test/widget_tests/address_book_card_test.dart @@ -5,17 +5,17 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; -import 'package:stackwallet/models/contact.dart'; -import 'package:stackwallet/models/contact_address_entry.dart'; +import 'package:stackwallet/models/isar/models/contact_entry.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; import 'package:stackwallet/pages/address_book_views/subviews/contact_popup.dart'; import 'package:stackwallet/providers/global/address_book_service_provider.dart'; import 'package:stackwallet/services/address_book_service.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/address_book_card.dart'; +import '../sample_data/theme_json.dart'; import 'address_book_card_test.mocks.dart'; class MockedFunctions extends Mock { @@ -26,17 +26,20 @@ class MockedFunctions extends Mock { void main() { testWidgets('test returns Contact Address Entry', (widgetTester) async { final service = MockAddressBookService(); + const applicationThemesDirectoryPath = ""; when(service.getContactById("default")).thenAnswer( - (realInvocation) => Contact( + (realInvocation) => ContactEntry( name: "John Doe", addresses: [ - const ContactAddressEntry( - coin: Coin.bitcoincash, - address: "some bch address", - label: "Bills") + ContactAddressEntry() + ..coinName = Coin.bitcoincash.name + ..address = "some bch address" + ..label = "Bills" + ..other = null ], isFavorite: true, + customId: '', ), ); @@ -51,7 +54,11 @@ void main() { theme: ThemeData( extensions: [ StackColors.fromStackColorTheme( - LightColors(), + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: + applicationThemesDirectoryPath, + ), ), ], ), diff --git a/test/widget_tests/address_book_card_test.mocks.dart b/test/widget_tests/address_book_card_test.mocks.dart index 550095d26..f26ca5dbd 100644 --- a/test/widget_tests/address_book_card_test.mocks.dart +++ b/test/widget_tests/address_book_card_test.mocks.dart @@ -7,7 +7,7 @@ import 'dart:async' as _i4; import 'dart:ui' as _i5; import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/models/contact.dart' as _i2; +import 'package:stackwallet/models/isar/models/contact_entry.dart' as _i2; import 'package:stackwallet/services/address_book_service.dart' as _i3; // ignore_for_file: type=lint @@ -21,8 +21,8 @@ import 'package:stackwallet/services/address_book_service.dart' as _i3; // ignore_for_file: camel_case_types // ignore_for_file: subtype_of_sealed_class -class _FakeContact_0 extends _i1.SmartFake implements _i2.Contact { - _FakeContact_0( +class _FakeContactEntry_0 extends _i1.SmartFake implements _i2.ContactEntry { + _FakeContactEntry_0( Object parent, Invocation parentInvocation, ) : super( @@ -41,46 +41,43 @@ class MockAddressBookService extends _i1.Mock } @override - List<_i2.Contact> get contacts => (super.noSuchMethod( + List<_i2.ContactEntry> get contacts => (super.noSuchMethod( Invocation.getter(#contacts), - returnValue: <_i2.Contact>[], - ) as List<_i2.Contact>); - @override - _i4.Future> get addressBookEntries => (super.noSuchMethod( - Invocation.getter(#addressBookEntries), - returnValue: _i4.Future>.value(<_i2.Contact>[]), - ) as _i4.Future>); + returnValue: <_i2.ContactEntry>[], + ) as List<_i2.ContactEntry>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i2.Contact getContactById(String? id) => (super.noSuchMethod( + _i2.ContactEntry getContactById(String? id) => (super.noSuchMethod( Invocation.method( #getContactById, [id], ), - returnValue: _FakeContact_0( + returnValue: _FakeContactEntry_0( this, Invocation.method( #getContactById, [id], ), ), - ) as _i2.Contact); + ) as _i2.ContactEntry); @override - _i4.Future> search(String? text) => (super.noSuchMethod( + _i4.Future> search(String? text) => + (super.noSuchMethod( Invocation.method( #search, [text], ), - returnValue: _i4.Future>.value(<_i2.Contact>[]), - ) as _i4.Future>); + returnValue: + _i4.Future>.value(<_i2.ContactEntry>[]), + ) as _i4.Future>); @override bool matches( String? term, - _i2.Contact? contact, + _i2.ContactEntry? contact, ) => (super.noSuchMethod( Invocation.method( @@ -93,7 +90,7 @@ class MockAddressBookService extends _i1.Mock returnValue: false, ) as bool); @override - _i4.Future addContact(_i2.Contact? contact) => (super.noSuchMethod( + _i4.Future addContact(_i2.ContactEntry? contact) => (super.noSuchMethod( Invocation.method( #addContact, [contact], @@ -101,7 +98,7 @@ class MockAddressBookService extends _i1.Mock returnValue: _i4.Future.value(false), ) as _i4.Future); @override - _i4.Future editContact(_i2.Contact? editedContact) => + _i4.Future editContact(_i2.ContactEntry? editedContact) => (super.noSuchMethod( Invocation.method( #editContact, diff --git a/test/widget_tests/animated_text_test.dart b/test/widget_tests/animated_text_test.dart index ba2610dc5..6401b5822 100644 --- a/test/widget_tests/animated_text_test.dart +++ b/test/widget_tests/animated_text_test.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/widgets/animated_text.dart'; +import '../sample_data/theme_json.dart'; + void main() { testWidgets("Widget displays first word in strings list", (widgetTester) async { @@ -23,7 +25,12 @@ void main() { MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: const Material( diff --git a/test/widget_tests/custom_buttons/app_bar_icon_button_test.dart b/test/widget_tests/custom_buttons/app_bar_icon_button_test.dart index 36179deaf..0e1902fb5 100644 --- a/test/widget_tests/custom_buttons/app_bar_icon_button_test.dart +++ b/test/widget_tests/custom_buttons/app_bar_icon_button_test.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; +import '../../sample_data/theme_json.dart'; + void main() { testWidgets("AppBarIconButton test", (tester) async { int buttonPressedCount = 0; @@ -23,7 +25,12 @@ void main() { MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: Material( diff --git a/test/widget_tests/custom_buttons/draggable_switch_button_test.dart b/test/widget_tests/custom_buttons/draggable_switch_button_test.dart index d8366a1f0..1c67e2883 100644 --- a/test/widget_tests/custom_buttons/draggable_switch_button_test.dart +++ b/test/widget_tests/custom_buttons/draggable_switch_button_test.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/draggable_switch_button.dart'; +import '../../sample_data/theme_json.dart'; + void main() { testWidgets("DraggableSwitchButton tapped", (tester) async { bool? isButtonOn = false; @@ -19,7 +21,12 @@ void main() { MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: button, @@ -46,7 +53,12 @@ void main() { MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: SizedBox( @@ -78,7 +90,12 @@ void main() { MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: SizedBox( diff --git a/test/widget_tests/custom_buttons/favorite_toggle_test.dart b/test/widget_tests/custom_buttons/favorite_toggle_test.dart index 54de9a44c..a7fdc5492 100644 --- a/test/widget_tests/custom_buttons/favorite_toggle_test.dart +++ b/test/widget_tests/custom_buttons/favorite_toggle_test.dart @@ -1,23 +1,44 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_service.dart'; import 'package:stackwallet/widgets/custom_buttons/favorite_toggle.dart'; +import '../../sample_data/theme_json.dart'; +import 'favorite_toggle_test.mocks.dart'; + +@GenerateMocks([ + ThemeService, +]) void main() { testWidgets("Test widget build", (widgetTester) async { final key = UniqueKey(); + final mockThemeService = MockThemeService(); + when(mockThemeService.getTheme(themeId: "light")).thenAnswer( + (_) => StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ); await widgetTester.pumpWidget( ProviderScope( - overrides: [], + overrides: [ + pThemeService.overrideWithValue(mockThemeService), + ], child: MaterialApp( theme: ThemeData( extensions: [ StackColors.fromStackColorTheme( - LightColors(), + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), ), ], ), diff --git a/test/widget_tests/custom_buttons/favorite_toggle_test.mocks.dart b/test/widget_tests/custom_buttons/favorite_toggle_test.mocks.dart new file mode 100644 index 000000000..9bccdcc86 --- /dev/null +++ b/test/widget_tests/custom_buttons/favorite_toggle_test.mocks.dart @@ -0,0 +1,122 @@ +// Mocks generated by Mockito 5.3.2 from annotations +// in stackwallet/test/widget_tests/custom_buttons/favorite_toggle_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i5; +import 'dart:typed_data' as _i6; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/db/isar/main_db.dart' as _i2; +import 'package:stackwallet/models/isar/stack_theme.dart' as _i4; +import 'package:stackwallet/themes/theme_service.dart' as _i3; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeMainDB_0 extends _i1.SmartFake implements _i2.MainDB { + _FakeMainDB_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [ThemeService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockThemeService extends _i1.Mock implements _i3.ThemeService { + MockThemeService() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.MainDB get db => (super.noSuchMethod( + Invocation.getter(#db), + returnValue: _FakeMainDB_0( + this, + Invocation.getter(#db), + ), + ) as _i2.MainDB); + @override + List<_i4.StackTheme> get installedThemes => (super.noSuchMethod( + Invocation.getter(#installedThemes), + returnValue: <_i4.StackTheme>[], + ) as List<_i4.StackTheme>); + @override + void init(_i2.MainDB? db) => super.noSuchMethod( + Invocation.method( + #init, + [db], + ), + returnValueForMissingStub: null, + ); + @override + _i5.Future install({required _i6.Uint8List? themeArchiveData}) => + (super.noSuchMethod( + Invocation.method( + #install, + [], + {#themeArchiveData: themeArchiveData}, + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future remove({required String? themeId}) => (super.noSuchMethod( + Invocation.method( + #remove, + [], + {#themeId: themeId}, + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future verifyInstalled({required String? themeId}) => + (super.noSuchMethod( + Invocation.method( + #verifyInstalled, + [], + {#themeId: themeId}, + ), + returnValue: _i5.Future.value(false), + ) as _i5.Future); + @override + _i5.Future> fetchThemes() => (super.noSuchMethod( + Invocation.method( + #fetchThemes, + [], + ), + returnValue: _i5.Future>.value( + <_i3.StackThemeMetaData>[]), + ) as _i5.Future>); + @override + _i5.Future<_i6.Uint8List> fetchTheme( + {required _i3.StackThemeMetaData? themeMetaData}) => + (super.noSuchMethod( + Invocation.method( + #fetchTheme, + [], + {#themeMetaData: themeMetaData}, + ), + returnValue: _i5.Future<_i6.Uint8List>.value(_i6.Uint8List(0)), + ) as _i5.Future<_i6.Uint8List>); + @override + _i4.StackTheme? getTheme({required String? themeId}) => + (super.noSuchMethod(Invocation.method( + #getTheme, + [], + {#themeId: themeId}, + )) as _i4.StackTheme?); +} diff --git a/test/widget_tests/custom_loading_overlay_test.dart b/test/widget_tests/custom_loading_overlay_test.dart index 48939c9b4..df976eb91 100644 --- a/test/widget_tests/custom_loading_overlay_test.dart +++ b/test/widget_tests/custom_loading_overlay_test.dart @@ -1,23 +1,51 @@ import 'package:event_bus/event_bus.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_service.dart'; import 'package:stackwallet/widgets/custom_loading_overlay.dart'; +import '../sample_data/theme_json.dart'; +import 'custom_loading_overlay_test.mocks.dart'; + +@GenerateMocks([ + ThemeService, +]) void main() { - testWidgets("Test wiget displays correct text", (widgetTester) async { + testWidgets("Test widget displays correct text", (widgetTester) async { final eventBus = EventBus(); + final mockThemeService = MockThemeService(); + when(mockThemeService.getTheme(themeId: "light")).thenAnswer( + (_) => StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ); + await widgetTester.pumpWidget( - MaterialApp( - theme: ThemeData( - extensions: [ - StackColors.fromStackColorTheme(LightColors()), - ], - ), - home: Material( - child: CustomLoadingOverlay( - message: "Updating exchange rate", eventBus: eventBus), + ProviderScope( + overrides: [ + pThemeService.overrideWithValue(mockThemeService), + ], + child: MaterialApp( + theme: ThemeData( + extensions: [ + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), + ], + ), + home: Material( + child: CustomLoadingOverlay( + message: "Updating exchange rate", eventBus: eventBus), + ), ), ), ); diff --git a/test/widget_tests/custom_loading_overlay_test.mocks.dart b/test/widget_tests/custom_loading_overlay_test.mocks.dart new file mode 100644 index 000000000..c2244014e --- /dev/null +++ b/test/widget_tests/custom_loading_overlay_test.mocks.dart @@ -0,0 +1,122 @@ +// Mocks generated by Mockito 5.3.2 from annotations +// in stackwallet/test/widget_tests/custom_loading_overlay_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i5; +import 'dart:typed_data' as _i6; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/db/isar/main_db.dart' as _i2; +import 'package:stackwallet/models/isar/stack_theme.dart' as _i4; +import 'package:stackwallet/themes/theme_service.dart' as _i3; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeMainDB_0 extends _i1.SmartFake implements _i2.MainDB { + _FakeMainDB_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [ThemeService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockThemeService extends _i1.Mock implements _i3.ThemeService { + MockThemeService() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.MainDB get db => (super.noSuchMethod( + Invocation.getter(#db), + returnValue: _FakeMainDB_0( + this, + Invocation.getter(#db), + ), + ) as _i2.MainDB); + @override + List<_i4.StackTheme> get installedThemes => (super.noSuchMethod( + Invocation.getter(#installedThemes), + returnValue: <_i4.StackTheme>[], + ) as List<_i4.StackTheme>); + @override + void init(_i2.MainDB? db) => super.noSuchMethod( + Invocation.method( + #init, + [db], + ), + returnValueForMissingStub: null, + ); + @override + _i5.Future install({required _i6.Uint8List? themeArchiveData}) => + (super.noSuchMethod( + Invocation.method( + #install, + [], + {#themeArchiveData: themeArchiveData}, + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future remove({required String? themeId}) => (super.noSuchMethod( + Invocation.method( + #remove, + [], + {#themeId: themeId}, + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future verifyInstalled({required String? themeId}) => + (super.noSuchMethod( + Invocation.method( + #verifyInstalled, + [], + {#themeId: themeId}, + ), + returnValue: _i5.Future.value(false), + ) as _i5.Future); + @override + _i5.Future> fetchThemes() => (super.noSuchMethod( + Invocation.method( + #fetchThemes, + [], + ), + returnValue: _i5.Future>.value( + <_i3.StackThemeMetaData>[]), + ) as _i5.Future>); + @override + _i5.Future<_i6.Uint8List> fetchTheme( + {required _i3.StackThemeMetaData? themeMetaData}) => + (super.noSuchMethod( + Invocation.method( + #fetchTheme, + [], + {#themeMetaData: themeMetaData}, + ), + returnValue: _i5.Future<_i6.Uint8List>.value(_i6.Uint8List(0)), + ) as _i5.Future<_i6.Uint8List>); + @override + _i4.StackTheme? getTheme({required String? themeId}) => + (super.noSuchMethod(Invocation.method( + #getTheme, + [], + {#themeId: themeId}, + )) as _i4.StackTheme?); +} diff --git a/test/widget_tests/custom_pin_put_test.dart b/test/widget_tests/custom_pin_put_test.dart index d3a449865..28bc583d4 100644 --- a/test/widget_tests/custom_pin_put_test.dart +++ b/test/widget_tests/custom_pin_put_test.dart @@ -1,21 +1,32 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/widgets/custom_pin_put/custom_pin_put.dart'; import 'package:stackwallet/widgets/custom_pin_put/pin_keyboard.dart'; +import '../sample_data/theme_json.dart'; + void main() { - group("CustomPinPut tests", () { - testWidgets("CustomPinPut with 4 fields builds correctly", (tester) async { - const pinPut = CustomPinPut(fieldsCount: 4); + group("CustomPinPut tests, non-random PIN", () { + testWidgets("CustomPinPut with 4 fields builds correctly, non-random PIN", + (tester) async { + const pinPut = CustomPinPut( + fieldsCount: 4, + isRandom: false, + ); await tester.pumpWidget( MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: const Material( @@ -31,19 +42,29 @@ void main() { expect(find.byType(NumberKey), findsNWidgets(10)); }); - testWidgets("CustomPinPut entering a pin successfully", (tester) async { + testWidgets("CustomPinPut entering a pin successfully, non-random PIN", + (tester) async { bool submittedPinMatches = false; final pinPut = CustomPinPut( fieldsCount: 4, - onSubmit: (pin) => submittedPinMatches = pin == "1234", + onSubmit: (pin) { + submittedPinMatches = pin == "1234"; + print("pin entered: $pin"); + }, useNativeKeyboard: false, + isRandom: false, ); await tester.pumpWidget( MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: Material( @@ -69,23 +90,32 @@ void main() { await tester.tap(find.byWidgetPredicate( (widget) => widget is NumberKey && widget.number == "4")); await tester.pumpAndSettle(); + await tester.tap(find.byType(SubmitKey)); + await tester.pumpAndSettle(); expect(submittedPinMatches, true); }); - testWidgets("CustomPinPut pin enter fade animation", (tester) async { + testWidgets("CustomPinPut pin enter fade animation, non-random PIN", + (tester) async { final controller = TextEditingController(); final pinPut = CustomPinPut( fieldsCount: 4, pinAnimationType: PinAnimationType.fade, controller: controller, + isRandom: false, ); await tester.pumpWidget( MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: Material( @@ -104,19 +134,26 @@ void main() { expect(controller.text, ""); }); - testWidgets("CustomPinPut pin enter scale animation", (tester) async { + testWidgets("CustomPinPut pin enter scale animation, non-random PIN", + (tester) async { final controller = TextEditingController(); final pinPut = CustomPinPut( fieldsCount: 4, pinAnimationType: PinAnimationType.scale, controller: controller, + isRandom: false, ); await tester.pumpWidget( MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: Material( @@ -135,19 +172,26 @@ void main() { expect(controller.text, ""); }); - testWidgets("CustomPinPut pin enter rotate animation", (tester) async { + testWidgets("CustomPinPut pin enter rotate animation, non-random PIN", + (tester) async { final controller = TextEditingController(); final pinPut = CustomPinPut( fieldsCount: 4, pinAnimationType: PinAnimationType.rotation, controller: controller, + isRandom: false, ); await tester.pumpWidget( MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: Material( @@ -167,18 +211,24 @@ void main() { }); }); - testWidgets("PinKeyboard builds correctly", (tester) async { + testWidgets("PinKeyboard builds correctly, non-random PIN", (tester) async { final keyboard = PinKeyboard( onNumberKeyPressed: (_) {}, onBackPressed: () {}, onSubmitPressed: () {}, + isRandom: false, ); await tester.pumpWidget( MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: Material( @@ -188,6 +238,7 @@ void main() { ); expect(find.byType(BackspaceKey), findsOneWidget); + expect(find.byType(SubmitKey), findsOneWidget); expect(find.byType(NumberKey), findsNWidgets(10)); expect(find.text("0"), findsOneWidget); expect(find.text("1"), findsOneWidget); @@ -199,6 +250,250 @@ void main() { expect(find.text("7"), findsOneWidget); expect(find.text("8"), findsOneWidget); expect(find.text("9"), findsOneWidget); - expect(find.byType(SvgPicture), findsOneWidget); + expect(find.byType(SvgPicture), findsNWidgets(2)); + }); + + group("CustomPinPut tests, with random PIN", () { + testWidgets("CustomPinPut with 4 fields builds correctly, with random PIN", + (tester) async { + const pinPut = CustomPinPut( + fieldsCount: 4, + isRandom: true, + ); + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + extensions: [ + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), + ], + ), + home: const Material( + child: pinPut, + ), + ), + ); + + // expects 5 here. Four + the actual text field text + expect(find.text(""), findsNWidgets(5)); + expect(find.byType(PinKeyboard), findsOneWidget); + expect(find.byType(BackspaceKey), findsOneWidget); + expect(find.byType(NumberKey), findsNWidgets(10)); + }); + + testWidgets("CustomPinPut entering a pin successfully, with random PIN", + (tester) async { + bool submittedPinMatches = false; + final pinPut = CustomPinPut( + fieldsCount: 4, + onSubmit: (pin) { + submittedPinMatches = pin == "1234"; + print("pin entered: $pin"); + }, + useNativeKeyboard: false, + isRandom: true, + ); + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + extensions: [ + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), + ], + ), + home: Material( + child: pinPut, + ), + ), + ); + + await tester.tap(find.byWidgetPredicate( + (widget) => widget is NumberKey && widget.number == "1")); + await tester.pumpAndSettle(); + await tester.tap(find.byWidgetPredicate( + (widget) => widget is NumberKey && widget.number == "2")); + await tester.pumpAndSettle(); + await tester.tap(find.byWidgetPredicate( + (widget) => widget is NumberKey && widget.number == "6")); + await tester.pumpAndSettle(); + await tester.tap(find.byType(BackspaceKey)); + await tester.pumpAndSettle(); + await tester.tap(find.byWidgetPredicate( + (widget) => widget is NumberKey && widget.number == "3")); + await tester.pumpAndSettle(); + await tester.tap(find.byWidgetPredicate( + (widget) => widget is NumberKey && widget.number == "4")); + await tester.pumpAndSettle(); + await tester.tap(find.byType(SubmitKey)); + await tester.pumpAndSettle(); + + expect(submittedPinMatches, true); + }); + + testWidgets("CustomPinPut pin enter fade animation, with random PIN", + (tester) async { + final controller = TextEditingController(); + final pinPut = CustomPinPut( + fieldsCount: 4, + pinAnimationType: PinAnimationType.fade, + controller: controller, + isRandom: true, + ); + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + extensions: [ + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), + ], + ), + home: Material( + child: pinPut, + ), + ), + ); + + await tester.tap(find.byWidgetPredicate( + (widget) => widget is NumberKey && widget.number == "1")); + await tester.pumpAndSettle(); + expect(controller.text, "1"); + + await tester.tap(find.byType(BackspaceKey)); + await tester.pumpAndSettle(); + expect(controller.text, ""); + }); + + testWidgets("CustomPinPut pin enter scale animation, with random PIN", + (tester) async { + final controller = TextEditingController(); + final pinPut = CustomPinPut( + fieldsCount: 4, + pinAnimationType: PinAnimationType.scale, + controller: controller, + isRandom: true, + ); + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + extensions: [ + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), + ], + ), + home: Material( + child: pinPut, + ), + ), + ); + + await tester.tap(find.byWidgetPredicate( + (widget) => widget is NumberKey && widget.number == "1")); + await tester.pumpAndSettle(); + expect(controller.text, "1"); + + await tester.tap(find.byType(BackspaceKey)); + await tester.pumpAndSettle(); + expect(controller.text, ""); + }); + + testWidgets("CustomPinPut pin enter rotate animation, with random PIN", + (tester) async { + final controller = TextEditingController(); + final pinPut = CustomPinPut( + fieldsCount: 4, + pinAnimationType: PinAnimationType.rotation, + controller: controller, + isRandom: true, + ); + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + extensions: [ + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), + ], + ), + home: Material( + child: pinPut, + ), + ), + ); + + await tester.tap(find.byWidgetPredicate( + (widget) => widget is NumberKey && widget.number == "1")); + await tester.pumpAndSettle(); + expect(controller.text, "1"); + + await tester.tap(find.byType(BackspaceKey)); + await tester.pumpAndSettle(); + expect(controller.text, ""); + }); + }); + + testWidgets("PinKeyboard builds correctly, with random PIN", (tester) async { + final keyboard = PinKeyboard( + onNumberKeyPressed: (_) {}, + onBackPressed: () {}, + onSubmitPressed: () {}, + isRandom: true, + ); + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + extensions: [ + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), + ], + ), + home: Material( + child: keyboard, + ), + ), + ); + + expect(find.byType(BackspaceKey), findsOneWidget); + expect(find.byType(SubmitKey), findsOneWidget); + expect(find.byType(NumberKey), findsNWidgets(10)); + expect(find.text("0"), findsOneWidget); + expect(find.text("1"), findsOneWidget); + expect(find.text("2"), findsOneWidget); + expect(find.text("3"), findsOneWidget); + expect(find.text("4"), findsOneWidget); + expect(find.text("5"), findsOneWidget); + expect(find.text("6"), findsOneWidget); + expect(find.text("7"), findsOneWidget); + expect(find.text("8"), findsOneWidget); + expect(find.text("9"), findsOneWidget); + expect(find.byType(SvgPicture), findsNWidgets(2)); }); } diff --git a/test/widget_tests/desktop/custom_text_button_test.dart b/test/widget_tests/desktop/custom_text_button_test.dart index 08cf19098..ab10e2e2d 100644 --- a/test/widget_tests/desktop/custom_text_button_test.dart +++ b/test/widget_tests/desktop/custom_text_button_test.dart @@ -1,10 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; -import 'package:stackwallet/utilities/util.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/custom_text_button.dart'; +import '../../sample_data/theme_json.dart'; + void main() { testWidgets("Test text button ", (widgetTester) async { final key = UniqueKey(); @@ -13,7 +14,12 @@ void main() { MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: Material( diff --git a/test/widget_tests/desktop/desktop_app_bar_test.dart b/test/widget_tests/desktop/desktop_app_bar_test.dart index 36ef861eb..eb5ccb7ec 100644 --- a/test/widget_tests/desktop/desktop_app_bar_test.dart +++ b/test/widget_tests/desktop/desktop_app_bar_test.dart @@ -1,11 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_app_bar.dart'; +import '../../sample_data/theme_json.dart'; + void main() { testWidgets("Test DesktopAppBar widget", (widgetTester) async { final key = UniqueKey(); @@ -14,7 +16,12 @@ void main() { MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: Material( diff --git a/test/widget_tests/desktop/desktop_dialog_close_button_test.dart b/test/widget_tests/desktop/desktop_dialog_close_button_test.dart index 4c9a8113f..86de83567 100644 --- a/test/widget_tests/desktop/desktop_dialog_close_button_test.dart +++ b/test/widget_tests/desktop/desktop_dialog_close_button_test.dart @@ -2,11 +2,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockingjay/mockingjay.dart' as mockingjay; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/widgets/custom_buttons/app_bar_icon_button.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import '../../sample_data/theme_json.dart'; + void main() { testWidgets("test DesktopDialog button pressed", (widgetTester) async { final key = UniqueKey(); @@ -19,7 +21,12 @@ void main() { child: MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: mockingjay.MockNavigatorProvider( diff --git a/test/widget_tests/desktop/desktop_dialog_test.dart b/test/widget_tests/desktop/desktop_dialog_test.dart index 48e82bf89..6ea148e09 100644 --- a/test/widget_tests/desktop/desktop_dialog_test.dart +++ b/test/widget_tests/desktop/desktop_dialog_test.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog.dart'; import 'package:stackwallet/widgets/desktop/desktop_dialog_close_button.dart'; +import '../../sample_data/theme_json.dart'; + void main() { testWidgets("test DesktopDialog builds", (widgetTester) async { final key = UniqueKey(); @@ -13,7 +15,12 @@ void main() { MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: Material( diff --git a/test/widget_tests/desktop/desktop_scaffold_test.dart b/test/widget_tests/desktop/desktop_scaffold_test.dart index b7eba2c6d..efb41c9a2 100644 --- a/test/widget_tests/desktop/desktop_scaffold_test.dart +++ b/test/widget_tests/desktop/desktop_scaffold_test.dart @@ -1,23 +1,50 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_service.dart'; import 'package:stackwallet/widgets/desktop/desktop_scaffold.dart'; +import '../../sample_data/theme_json.dart'; +import 'desktop_scaffold_test.mocks.dart'; + +@GenerateMocks([ + ThemeService, +]) void main() { testWidgets("test DesktopScaffold", (widgetTester) async { final key = UniqueKey(); + final mockThemeService = MockThemeService(); + when(mockThemeService.getTheme(themeId: "light")).thenAnswer( + (_) => StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ); await widgetTester.pumpWidget( - MaterialApp( - theme: ThemeData( - extensions: [ - StackColors.fromStackColorTheme(LightColors()), - ], - ), - home: Material( - child: DesktopScaffold( - key: key, - body: const SizedBox(), + ProviderScope( + overrides: [ + pThemeService.overrideWithValue(mockThemeService), + ], + child: MaterialApp( + theme: ThemeData( + extensions: [ + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), + ], + ), + home: Material( + child: DesktopScaffold( + key: key, + body: const SizedBox(), + ), ), ), ), @@ -26,20 +53,37 @@ void main() { testWidgets("Test MasterScaffold for non desktop", (widgetTester) async { final key = UniqueKey(); + final mockThemeService = MockThemeService(); + when(mockThemeService.getTheme(themeId: "light")).thenAnswer( + (_) => StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ); await widgetTester.pumpWidget( - MaterialApp( - theme: ThemeData( - extensions: [ - StackColors.fromStackColorTheme(LightColors()), - ], - ), - home: Material( - child: MasterScaffold( - key: key, - body: const SizedBox(), - appBar: AppBar(), - isDesktop: false, + ProviderScope( + overrides: [ + pThemeService.overrideWithValue(mockThemeService), + ], + child: MaterialApp( + theme: ThemeData( + extensions: [ + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), + ], + ), + home: Material( + child: MasterScaffold( + key: key, + body: const SizedBox(), + appBar: AppBar(), + isDesktop: false, + ), ), ), ), @@ -48,20 +92,38 @@ void main() { testWidgets("Test MasterScaffold for desktop", (widgetTester) async { final key = UniqueKey(); + final mockThemeService = MockThemeService(); + + when(mockThemeService.getTheme(themeId: "light")).thenAnswer( + (_) => StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ); await widgetTester.pumpWidget( - MaterialApp( - theme: ThemeData( - extensions: [ - StackColors.fromStackColorTheme(LightColors()), - ], - ), - home: Material( - child: MasterScaffold( - key: key, - body: const SizedBox(), - appBar: AppBar(), - isDesktop: true, + ProviderScope( + overrides: [ + pThemeService.overrideWithValue(mockThemeService), + ], + child: MaterialApp( + theme: ThemeData( + extensions: [ + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), + ], + ), + home: Material( + child: MasterScaffold( + key: key, + body: const SizedBox(), + appBar: AppBar(), + isDesktop: true, + ), ), ), ), diff --git a/test/widget_tests/desktop/desktop_scaffold_test.mocks.dart b/test/widget_tests/desktop/desktop_scaffold_test.mocks.dart new file mode 100644 index 000000000..aa5f1260d --- /dev/null +++ b/test/widget_tests/desktop/desktop_scaffold_test.mocks.dart @@ -0,0 +1,122 @@ +// Mocks generated by Mockito 5.3.2 from annotations +// in stackwallet/test/widget_tests/desktop/desktop_scaffold_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i5; +import 'dart:typed_data' as _i6; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/db/isar/main_db.dart' as _i2; +import 'package:stackwallet/models/isar/stack_theme.dart' as _i4; +import 'package:stackwallet/themes/theme_service.dart' as _i3; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeMainDB_0 extends _i1.SmartFake implements _i2.MainDB { + _FakeMainDB_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [ThemeService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockThemeService extends _i1.Mock implements _i3.ThemeService { + MockThemeService() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.MainDB get db => (super.noSuchMethod( + Invocation.getter(#db), + returnValue: _FakeMainDB_0( + this, + Invocation.getter(#db), + ), + ) as _i2.MainDB); + @override + List<_i4.StackTheme> get installedThemes => (super.noSuchMethod( + Invocation.getter(#installedThemes), + returnValue: <_i4.StackTheme>[], + ) as List<_i4.StackTheme>); + @override + void init(_i2.MainDB? db) => super.noSuchMethod( + Invocation.method( + #init, + [db], + ), + returnValueForMissingStub: null, + ); + @override + _i5.Future install({required _i6.Uint8List? themeArchiveData}) => + (super.noSuchMethod( + Invocation.method( + #install, + [], + {#themeArchiveData: themeArchiveData}, + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future remove({required String? themeId}) => (super.noSuchMethod( + Invocation.method( + #remove, + [], + {#themeId: themeId}, + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future verifyInstalled({required String? themeId}) => + (super.noSuchMethod( + Invocation.method( + #verifyInstalled, + [], + {#themeId: themeId}, + ), + returnValue: _i5.Future.value(false), + ) as _i5.Future); + @override + _i5.Future> fetchThemes() => (super.noSuchMethod( + Invocation.method( + #fetchThemes, + [], + ), + returnValue: _i5.Future>.value( + <_i3.StackThemeMetaData>[]), + ) as _i5.Future>); + @override + _i5.Future<_i6.Uint8List> fetchTheme( + {required _i3.StackThemeMetaData? themeMetaData}) => + (super.noSuchMethod( + Invocation.method( + #fetchTheme, + [], + {#themeMetaData: themeMetaData}, + ), + returnValue: _i5.Future<_i6.Uint8List>.value(_i6.Uint8List(0)), + ) as _i5.Future<_i6.Uint8List>); + @override + _i4.StackTheme? getTheme({required String? themeId}) => + (super.noSuchMethod(Invocation.method( + #getTheme, + [], + {#themeId: themeId}, + )) as _i4.StackTheme?); +} diff --git a/test/widget_tests/emoji_select_sheet_test.dart b/test/widget_tests/emoji_select_sheet_test.dart index aec05d580..4bb6b9046 100644 --- a/test/widget_tests/emoji_select_sheet_test.dart +++ b/test/widget_tests/emoji_select_sheet_test.dart @@ -3,10 +3,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockingjay/mockingjay.dart' as mockingjay; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/widgets/emoji_select_sheet.dart'; +import '../sample_data/theme_json.dart'; + void main() { testWidgets("Widget displays correctly", (tester) async { const emojiSelectSheet = EmojiSelectSheet(); @@ -15,7 +17,12 @@ void main() { MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: const Material( @@ -39,7 +46,12 @@ void main() { child: MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: mockingjay.MockNavigatorProvider( diff --git a/test/widget_tests/icon_widgets/addressbook_icon_test.dart b/test/widget_tests/icon_widgets/addressbook_icon_test.dart index 89ff4f236..48b7925d4 100644 --- a/test/widget_tests/icon_widgets/addressbook_icon_test.dart +++ b/test/widget_tests/icon_widgets/addressbook_icon_test.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/widgets/icon_widgets/addressbook_icon.dart'; +import '../../sample_data/theme_json.dart'; + void main() { testWidgets("test address book icon widget", (widgetTester) async { final key = UniqueKey(); @@ -16,7 +18,12 @@ void main() { MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: Material( diff --git a/test/widget_tests/managed_favorite_test.dart b/test/widget_tests/managed_favorite_test.dart index dc643e831..569785374 100644 --- a/test/widget_tests/managed_favorite_test.dart +++ b/test/widget_tests/managed_favorite_test.dart @@ -1,10 +1,11 @@ -import 'dart:ffi'; - +import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/coins/bitcoin/bitcoin_wallet.dart'; import 'package:stackwallet/services/coins/coin_service.dart'; @@ -13,19 +14,28 @@ import 'package:stackwallet/services/locale_service.dart'; import 'package:stackwallet/services/node_service.dart'; import 'package:stackwallet/services/wallets.dart'; import 'package:stackwallet/services/wallets_service.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_service.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; +import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/listenable_list.dart'; import 'package:stackwallet/widgets/managed_favorite.dart'; -import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; -import 'package:stackwallet/utilities/util.dart'; +import '../sample_data/theme_json.dart'; import 'managed_favorite_test.mocks.dart'; +/// quick amount constructor wrapper. Using an int is bad practice but for +/// testing with small amounts this should be fine +Amount _a(int i) => Amount.fromDecimal( + Decimal.fromInt(i), + fractionDigits: 8, + ); + @GenerateMocks([ Wallets, WalletsService, BitcoinWallet, + ThemeService, LocaleService ], customMocks: [ MockSpec(returnNullOnMissingStub: true), @@ -36,7 +46,14 @@ void main() { testWidgets("Test wallet info row displays correctly", (widgetTester) async { final wallets = MockWallets(); final CoinServiceAPI wallet = MockBitcoinWallet(); + final mockThemeService = MockThemeService(); + when(mockThemeService.getTheme(themeId: "light")).thenAnswer( + (_) => StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ); when(wallet.coin).thenAnswer((_) => Coin.bitcoin); when(wallet.walletName).thenAnswer((_) => "some wallet"); when(wallet.walletId).thenAnswer((_) => "some wallet id"); @@ -44,6 +61,14 @@ void main() { final manager = Manager(wallet); when(wallets.getManager("some wallet id")) .thenAnswer((realInvocation) => manager); + when(manager.balance).thenAnswer( + (realInvocation) => Balance( + total: _a(10), + spendable: _a(10), + blockedTotal: _a(0), + pendingSpendable: _a(0), + ), + ); when(manager.isFavorite).thenAnswer((realInvocation) => false); final key = UniqueKey(); @@ -52,12 +77,16 @@ void main() { ProviderScope( overrides: [ walletsChangeNotifierProvider.overrideWithValue(wallets), + pThemeService.overrideWithValue(mockThemeService), ], child: MaterialApp( theme: ThemeData( extensions: [ StackColors.fromStackColorTheme( - LightColors(), + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), ), ], ), @@ -79,7 +108,14 @@ void main() { final CoinServiceAPI wallet = MockBitcoinWallet(); final mockLocaleService = MockLocaleService(); final mockWalletsService = MockWalletsService(); + final mockThemeService = MockThemeService(); + when(mockThemeService.getTheme(themeId: "light")).thenAnswer( + (_) => StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ); when(wallet.coin).thenAnswer((_) => Coin.bitcoin); when(wallet.walletName).thenAnswer((_) => "some wallet"); when(wallet.walletId).thenAnswer((_) => "some wallet id"); @@ -88,6 +124,14 @@ void main() { when(wallets.getManager("some wallet id")) .thenAnswer((realInvocation) => manager); + when(manager.balance).thenAnswer( + (realInvocation) => Balance( + total: _a(10), + spendable: _a(10), + blockedTotal: _a(0), + pendingSpendable: _a(0), + ), + ); when(manager.isFavorite).thenAnswer((realInvocation) => false); @@ -111,6 +155,7 @@ void main() { .overrideWithValue(mockLocaleService), favoritesProvider.overrideWithValue(favorites), nonFavoritesProvider.overrideWithValue(nonfavorites), + pThemeService.overrideWithValue(mockThemeService), walletsServiceChangeNotifierProvider .overrideWithValue(mockWalletsService) ], @@ -118,7 +163,10 @@ void main() { theme: ThemeData( extensions: [ StackColors.fromStackColorTheme( - LightColors(), + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), ), ], ), @@ -139,7 +187,14 @@ void main() { final CoinServiceAPI wallet = MockBitcoinWallet(); final mockLocaleService = MockLocaleService(); final mockWalletsService = MockWalletsService(); + final mockThemeService = MockThemeService(); + when(mockThemeService.getTheme(themeId: "light")).thenAnswer( + (_) => StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ); when(wallet.coin).thenAnswer((_) => Coin.bitcoin); when(wallet.walletName).thenAnswer((_) => "some wallet"); when(wallet.walletId).thenAnswer((_) => "some wallet id"); @@ -150,6 +205,14 @@ void main() { .thenAnswer((realInvocation) => manager); when(manager.isFavorite).thenAnswer((realInvocation) => true); + when(manager.balance).thenAnswer( + (realInvocation) => Balance( + total: _a(10), + spendable: _a(10), + blockedTotal: _a(0), + pendingSpendable: _a(0), + ), + ); when(mockLocaleService.locale).thenAnswer((_) => "en_US"); @@ -171,6 +234,7 @@ void main() { .overrideWithValue(mockLocaleService), favoritesProvider.overrideWithValue(favorites), nonFavoritesProvider.overrideWithValue(nonfavorites), + pThemeService.overrideWithValue(mockThemeService), walletsServiceChangeNotifierProvider .overrideWithValue(mockWalletsService) ], @@ -178,7 +242,10 @@ void main() { theme: ThemeData( extensions: [ StackColors.fromStackColorTheme( - LightColors(), + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), ), ], ), diff --git a/test/widget_tests/managed_favorite_test.mocks.dart b/test/widget_tests/managed_favorite_test.mocks.dart index 117a810cf..1be169e52 100644 --- a/test/widget_tests/managed_favorite_test.mocks.dart +++ b/test/widget_tests/managed_favorite_test.mocks.dart @@ -3,30 +3,42 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i16; -import 'dart:ui' as _i18; +import 'dart:async' as _i23; +import 'dart:typed_data' as _i29; +import 'dart:ui' as _i25; -import 'package:decimal/decimal.dart' as _i9; +import 'package:bip32/bip32.dart' as _i16; +import 'package:bip47/bip47.dart' as _i18; +import 'package:bitcoindart/bitcoindart.dart' as _i13; import 'package:flutter/foundation.dart' as _i4; import 'package:flutter_riverpod/flutter_riverpod.dart' as _i5; import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i11; -import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i10; -import 'package:stackwallet/models/models.dart' as _i8; -import 'package:stackwallet/models/node_model.dart' as _i21; -import 'package:stackwallet/services/coins/bitcoin/bitcoin_wallet.dart' as _i19; -import 'package:stackwallet/services/coins/coin_service.dart' as _i13; +import 'package:stackwallet/db/isar/main_db.dart' as _i12; +import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i10; +import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i9; +import 'package:stackwallet/models/balance.dart' as _i11; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i17; +import 'package:stackwallet/models/isar/stack_theme.dart' as _i31; +import 'package:stackwallet/models/node_model.dart' as _i33; +import 'package:stackwallet/models/paymint/fee_object_model.dart' as _i8; +import 'package:stackwallet/models/signing_data.dart' as _i28; +import 'package:stackwallet/services/coins/bitcoin/bitcoin_wallet.dart' as _i26; +import 'package:stackwallet/services/coins/coin_service.dart' as _i20; import 'package:stackwallet/services/coins/manager.dart' as _i6; -import 'package:stackwallet/services/locale_service.dart' as _i20; +import 'package:stackwallet/services/locale_service.dart' as _i32; import 'package:stackwallet/services/node_service.dart' as _i3; import 'package:stackwallet/services/transaction_notification_tracker.dart' as _i7; -import 'package:stackwallet/services/wallets.dart' as _i14; +import 'package:stackwallet/services/wallets.dart' as _i21; import 'package:stackwallet/services/wallets_service.dart' as _i2; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i15; +import 'package:stackwallet/themes/theme_service.dart' as _i30; +import 'package:stackwallet/utilities/amount/amount.dart' as _i14; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i22; +import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart' as _i27; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' - as _i12; -import 'package:stackwallet/utilities/prefs.dart' as _i17; + as _i19; +import 'package:stackwallet/utilities/prefs.dart' as _i24; +import 'package:tuple/tuple.dart' as _i15; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -92,8 +104,8 @@ class _FakeTransactionNotificationTracker_4 extends _i1.SmartFake ); } -class _FakeUtxoData_5 extends _i1.SmartFake implements _i8.UtxoData { - _FakeUtxoData_5( +class _FakeFeeObject_5 extends _i1.SmartFake implements _i8.FeeObject { + _FakeFeeObject_5( Object parent, Invocation parentInvocation, ) : super( @@ -102,8 +114,8 @@ class _FakeUtxoData_5 extends _i1.SmartFake implements _i8.UtxoData { ); } -class _FakeDecimal_6 extends _i1.SmartFake implements _i9.Decimal { - _FakeDecimal_6( +class _FakeElectrumX_6 extends _i1.SmartFake implements _i9.ElectrumX { + _FakeElectrumX_6( Object parent, Invocation parentInvocation, ) : super( @@ -112,8 +124,9 @@ class _FakeDecimal_6 extends _i1.SmartFake implements _i9.Decimal { ); } -class _FakeFeeObject_7 extends _i1.SmartFake implements _i8.FeeObject { - _FakeFeeObject_7( +class _FakeCachedElectrumX_7 extends _i1.SmartFake + implements _i10.CachedElectrumX { + _FakeCachedElectrumX_7( Object parent, Invocation parentInvocation, ) : super( @@ -122,9 +135,8 @@ class _FakeFeeObject_7 extends _i1.SmartFake implements _i8.FeeObject { ); } -class _FakeTransactionData_8 extends _i1.SmartFake - implements _i8.TransactionData { - _FakeTransactionData_8( +class _FakeBalance_8 extends _i1.SmartFake implements _i11.Balance { + _FakeBalance_8( Object parent, Invocation parentInvocation, ) : super( @@ -133,8 +145,8 @@ class _FakeTransactionData_8 extends _i1.SmartFake ); } -class _FakeElectrumX_9 extends _i1.SmartFake implements _i10.ElectrumX { - _FakeElectrumX_9( +class _FakeMainDB_9 extends _i1.SmartFake implements _i12.MainDB { + _FakeMainDB_9( Object parent, Invocation parentInvocation, ) : super( @@ -143,9 +155,8 @@ class _FakeElectrumX_9 extends _i1.SmartFake implements _i10.ElectrumX { ); } -class _FakeCachedElectrumX_10 extends _i1.SmartFake - implements _i11.CachedElectrumX { - _FakeCachedElectrumX_10( +class _FakeNetworkType_10 extends _i1.SmartFake implements _i13.NetworkType { + _FakeNetworkType_10( Object parent, Invocation parentInvocation, ) : super( @@ -154,8 +165,7 @@ class _FakeCachedElectrumX_10 extends _i1.SmartFake ); } -class _FakeElectrumXNode_11 extends _i1.SmartFake - implements _i10.ElectrumXNode { +class _FakeElectrumXNode_11 extends _i1.SmartFake implements _i9.ElectrumXNode { _FakeElectrumXNode_11( Object parent, Invocation parentInvocation, @@ -165,9 +175,8 @@ class _FakeElectrumXNode_11 extends _i1.SmartFake ); } -class _FakeSecureStorageInterface_12 extends _i1.SmartFake - implements _i12.SecureStorageInterface { - _FakeSecureStorageInterface_12( +class _FakeAmount_12 extends _i1.SmartFake implements _i14.Amount { + _FakeAmount_12( Object parent, Invocation parentInvocation, ) : super( @@ -176,9 +185,61 @@ class _FakeSecureStorageInterface_12 extends _i1.SmartFake ); } -class _FakeCoinServiceAPI_13 extends _i1.SmartFake - implements _i13.CoinServiceAPI { - _FakeCoinServiceAPI_13( +class _FakeTuple2_13 extends _i1.SmartFake + implements _i15.Tuple2 { + _FakeTuple2_13( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeBIP32_14 extends _i1.SmartFake implements _i16.BIP32 { + _FakeBIP32_14( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAddress_15 extends _i1.SmartFake implements _i17.Address { + _FakeAddress_15( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePaymentCode_16 extends _i1.SmartFake implements _i18.PaymentCode { + _FakePaymentCode_16( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeSecureStorageInterface_17 extends _i1.SmartFake + implements _i19.SecureStorageInterface { + _FakeSecureStorageInterface_17( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeCoinServiceAPI_18 extends _i1.SmartFake + implements _i20.CoinServiceAPI { + _FakeCoinServiceAPI_18( Object parent, Invocation parentInvocation, ) : super( @@ -190,7 +251,7 @@ class _FakeCoinServiceAPI_13 extends _i1.SmartFake /// A class which mocks [Wallets]. /// /// See the documentation for Mockito's code generation for more information. -class MockWallets extends _i1.Mock implements _i14.Wallets { +class MockWallets extends _i1.Mock implements _i21.Wallets { MockWallets() { _i1.throwOnMissingStub(this); } @@ -257,7 +318,7 @@ class MockWallets extends _i1.Mock implements _i14.Wallets { returnValueForMissingStub: null, ); @override - List getWalletIdsFor({required _i15.Coin? coin}) => + List getWalletIdsFor({required _i22.Coin? coin}) => (super.noSuchMethod( Invocation.method( #getWalletIdsFor, @@ -267,15 +328,28 @@ class MockWallets extends _i1.Mock implements _i14.Wallets { returnValue: [], ) as List); @override - Map<_i15.Coin, List<_i5.ChangeNotifierProvider<_i6.Manager>>> + List<_i15.Tuple2<_i22.Coin, List<_i5.ChangeNotifierProvider<_i6.Manager>>>> getManagerProvidersByCoin() => (super.noSuchMethod( Invocation.method( #getManagerProvidersByCoin, [], ), - returnValue: <_i15.Coin, - List<_i5.ChangeNotifierProvider<_i6.Manager>>>{}, - ) as Map<_i15.Coin, List<_i5.ChangeNotifierProvider<_i6.Manager>>>); + returnValue: < + _i15.Tuple2<_i22.Coin, + List<_i5.ChangeNotifierProvider<_i6.Manager>>>>[], + ) as List< + _i15.Tuple2<_i22.Coin, + List<_i5.ChangeNotifierProvider<_i6.Manager>>>>); + @override + List<_i5.ChangeNotifierProvider<_i6.Manager>> getManagerProvidersForCoin( + _i22.Coin? coin) => + (super.noSuchMethod( + Invocation.method( + #getManagerProvidersForCoin, + [coin], + ), + returnValue: <_i5.ChangeNotifierProvider<_i6.Manager>>[], + ) as List<_i5.ChangeNotifierProvider<_i6.Manager>>); @override _i5.ChangeNotifierProvider<_i6.Manager> getManagerProvider( String? walletId) => @@ -332,17 +406,17 @@ class MockWallets extends _i1.Mock implements _i14.Wallets { returnValueForMissingStub: null, ); @override - _i16.Future load(_i17.Prefs? prefs) => (super.noSuchMethod( + _i23.Future load(_i24.Prefs? prefs) => (super.noSuchMethod( Invocation.method( #load, [prefs], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future loadAfterStackRestore( - _i17.Prefs? prefs, + _i23.Future loadAfterStackRestore( + _i24.Prefs? prefs, List<_i6.Manager>? managers, ) => (super.noSuchMethod( @@ -353,11 +427,11 @@ class MockWallets extends _i1.Mock implements _i14.Wallets { managers, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -365,7 +439,7 @@ class MockWallets extends _i1.Mock implements _i14.Wallets { returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -391,19 +465,19 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { } @override - _i16.Future> get walletNames => + _i23.Future> get walletNames => (super.noSuchMethod( Invocation.getter(#walletNames), - returnValue: _i16.Future>.value( + returnValue: _i23.Future>.value( {}), - ) as _i16.Future>); + ) as _i23.Future>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i16.Future renameWallet({ + _i23.Future renameWallet({ required String? from, required String? to, required bool? shouldNotifyListeners, @@ -418,13 +492,21 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future addExistingStackWallet({ + Map fetchWalletsData() => (super.noSuchMethod( + Invocation.method( + #fetchWalletsData, + [], + ), + returnValue: {}, + ) as Map); + @override + _i23.Future addExistingStackWallet({ required String? name, required String? walletId, - required _i15.Coin? coin, + required _i22.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -438,13 +520,13 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future addNewWallet({ + _i23.Future addNewWallet({ required String? name, - required _i15.Coin? coin, + required _i22.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -457,46 +539,46 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future> getFavoriteWalletIds() => (super.noSuchMethod( + _i23.Future> getFavoriteWalletIds() => (super.noSuchMethod( Invocation.method( #getFavoriteWalletIds, [], ), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); + returnValue: _i23.Future>.value([]), + ) as _i23.Future>); @override - _i16.Future saveFavoriteWalletIds(List? walletIds) => + _i23.Future saveFavoriteWalletIds(List? walletIds) => (super.noSuchMethod( Invocation.method( #saveFavoriteWalletIds, [walletIds], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future addFavorite(String? walletId) => (super.noSuchMethod( + _i23.Future addFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #addFavorite, [walletId], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future removeFavorite(String? walletId) => (super.noSuchMethod( + _i23.Future removeFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #removeFavorite, [walletId], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future moveFavorite({ + _i23.Future moveFavorite({ required int? fromIndex, required int? toIndex, }) => @@ -509,48 +591,48 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { #toIndex: toIndex, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future checkForDuplicate(String? name) => (super.noSuchMethod( + _i23.Future checkForDuplicate(String? name) => (super.noSuchMethod( Invocation.method( #checkForDuplicate, [name], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future getWalletId(String? walletName) => (super.noSuchMethod( + _i23.Future getWalletId(String? walletName) => (super.noSuchMethod( Invocation.method( #getWalletId, [walletName], ), - returnValue: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future isMnemonicVerified({required String? walletId}) => + _i23.Future isMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #isMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future setMnemonicVerified({required String? walletId}) => + _i23.Future setMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #setMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future deleteWallet( + _i23.Future deleteWallet( String? name, bool? shouldNotifyListeners, ) => @@ -562,20 +644,20 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { shouldNotifyListeners, ], ), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future.value(0), + ) as _i23.Future); @override - _i16.Future refreshWallets(bool? shouldNotifyListeners) => + _i23.Future refreshWallets(bool? shouldNotifyListeners) => (super.noSuchMethod( Invocation.method( #refreshWallets, [shouldNotifyListeners], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -583,7 +665,7 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -611,13 +693,13 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { /// A class which mocks [BitcoinWallet]. /// /// See the documentation for Mockito's code generation for more information. -class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { +class MockBitcoinWallet extends _i1.Mock implements _i26.BitcoinWallet { MockBitcoinWallet() { _i1.throwOnMissingStub(this); } @override - set timer(_i16.Timer? _timer) => super.noSuchMethod( + set timer(_i23.Timer? _timer) => super.noSuchMethod( Invocation.setter( #timer, _timer, @@ -642,19 +724,6 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValueForMissingStub: null, ); @override - List<_i8.UtxoObject> get outputsList => (super.noSuchMethod( - Invocation.getter(#outputsList), - returnValue: <_i8.UtxoObject>[], - ) as List<_i8.UtxoObject>); - @override - set outputsList(List<_i8.UtxoObject>? _outputsList) => super.noSuchMethod( - Invocation.setter( - #outputsList, - _outputsList, - ), - returnValueForMissingStub: null, - ); - @override bool get longMutex => (super.noSuchMethod( Invocation.getter(#longMutex), returnValue: false, @@ -681,14 +750,6 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValueForMissingStub: null, ); @override - set cachedTxData(_i8.TransactionData? _cachedTxData) => super.noSuchMethod( - Invocation.setter( - #cachedTxData, - _cachedTxData, - ), - returnValueForMissingStub: null, - ); - @override bool get isActive => (super.noSuchMethod( Invocation.getter(#isActive), returnValue: false, @@ -715,104 +776,74 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValue: false, ) as bool); @override - _i15.Coin get coin => (super.noSuchMethod( + _i22.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i15.Coin.bitcoin, - ) as _i15.Coin); + returnValue: _i22.Coin.bitcoin, + ) as _i22.Coin); @override - _i16.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); + _i23.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i23.Future>.value(<_i17.UTXO>[]), + ) as _i23.Future>); @override - _i16.Future<_i8.UtxoData> get utxoData => (super.noSuchMethod( - Invocation.getter(#utxoData), - returnValue: _i16.Future<_i8.UtxoData>.value(_FakeUtxoData_5( - this, - Invocation.getter(#utxoData), - )), - ) as _i16.Future<_i8.UtxoData>); - @override - _i16.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), + _i23.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i16.Future>.value(<_i8.UtxoObject>[]), - ) as _i16.Future>); + _i23.Future>.value(<_i17.Transaction>[]), + ) as _i23.Future>); @override - _i16.Future<_i9.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#availableBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i9.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i9.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i9.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#totalBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future get currentReceivingAddress => (super.noSuchMethod( + _i23.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future get currentLegacyReceivingAddress => (super.noSuchMethod( - Invocation.getter(#currentLegacyReceivingAddress), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + _i23.Future get currentChangeAddress => (super.noSuchMethod( + Invocation.getter(#currentChangeAddress), + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future get currentReceivingAddressP2SH => (super.noSuchMethod( - Invocation.getter(#currentReceivingAddressP2SH), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + _i23.Future get currentChangeAddressP2PKH => (super.noSuchMethod( + Invocation.getter(#currentChangeAddressP2PKH), + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override bool get hasCalledExit => (super.noSuchMethod( Invocation.getter(#hasCalledExit), returnValue: false, ) as bool); @override - _i16.Future<_i8.FeeObject> get fees => (super.noSuchMethod( + _i23.Future<_i8.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i16.Future<_i8.FeeObject>.value(_FakeFeeObject_7( + returnValue: _i23.Future<_i8.FeeObject>.value(_FakeFeeObject_5( this, Invocation.getter(#fees), )), - ) as _i16.Future<_i8.FeeObject>); + ) as _i23.Future<_i8.FeeObject>); @override - _i16.Future get maxFee => (super.noSuchMethod( + _i23.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future.value(0), + ) as _i23.Future); @override - _i16.Future> get mnemonic => (super.noSuchMethod( + _i23.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); + returnValue: _i23.Future>.value([]), + ) as _i23.Future>); @override - _i16.Future get chainHeight => (super.noSuchMethod( + _i23.Future get mnemonicString => (super.noSuchMethod( + Invocation.getter(#mnemonicString), + returnValue: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future get chainHeight => (super.noSuchMethod( Invocation.getter(#chainHeight), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future.value(0), + ) as _i23.Future); @override int get storedChainHeight => (super.noSuchMethod( Invocation.getter(#storedChainHeight), @@ -842,15 +873,6 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValue: false, ) as bool); @override - _i16.Future<_i8.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), - returnValue: - _i16.Future<_i8.TransactionData>.value(_FakeTransactionData_8( - this, - Invocation.getter(#transactionData), - )), - ) as _i16.Future<_i8.TransactionData>); - @override String get walletId => (super.noSuchMethod( Invocation.getter(#walletId), returnValue: '', @@ -869,21 +891,34 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValueForMissingStub: null, ); @override - _i10.ElectrumX get electrumXClient => (super.noSuchMethod( + _i9.ElectrumX get electrumXClient => (super.noSuchMethod( Invocation.getter(#electrumXClient), - returnValue: _FakeElectrumX_9( + returnValue: _FakeElectrumX_6( this, Invocation.getter(#electrumXClient), ), - ) as _i10.ElectrumX); + ) as _i9.ElectrumX); @override - _i11.CachedElectrumX get cachedElectrumXClient => (super.noSuchMethod( + _i10.CachedElectrumX get cachedElectrumXClient => (super.noSuchMethod( Invocation.getter(#cachedElectrumXClient), - returnValue: _FakeCachedElectrumX_10( + returnValue: _FakeCachedElectrumX_7( this, Invocation.getter(#cachedElectrumXClient), ), - ) as _i11.CachedElectrumX); + ) as _i10.CachedElectrumX); + @override + _i11.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_8( + this, + Invocation.getter(#balance), + ), + ) as _i11.Balance); + @override + _i23.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override set onIsActiveWalletChanged(void Function(bool)? _onIsActiveWalletChanged) => super.noSuchMethod( @@ -894,38 +929,44 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValueForMissingStub: null, ); @override - _i16.Future exit() => (super.noSuchMethod( + _i12.MainDB get db => (super.noSuchMethod( + Invocation.getter(#db), + returnValue: _FakeMainDB_9( + this, + Invocation.getter(#db), + ), + ) as _i12.MainDB); + @override + _i13.NetworkType get networkType => (super.noSuchMethod( + Invocation.getter(#networkType), + returnValue: _FakeNetworkType_10( + this, + Invocation.getter(#networkType), + ), + ) as _i13.NetworkType); + @override + _i23.Future exit() => (super.noSuchMethod( Invocation.method( #exit, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future updateStoredChainHeight({required int? newHeight}) => - (super.noSuchMethod( - Invocation.method( - #updateStoredChainHeight, - [], - {#newHeight: newHeight}, - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i19.DerivePathType addressType({required String? address}) => + _i27.DerivePathType addressType({required String? address}) => (super.noSuchMethod( Invocation.method( #addressType, [], {#address: address}, ), - returnValue: _i19.DerivePathType.bip44, - ) as _i19.DerivePathType); + returnValue: _i27.DerivePathType.bip44, + ) as _i27.DerivePathType); @override - _i16.Future recoverFromMnemonic({ + _i23.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -936,55 +977,55 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future getTransactionCacheEarly(List? allAddresses) => + _i23.Future getTransactionCacheEarly(List? allAddresses) => (super.noSuchMethod( Invocation.method( #getTransactionCacheEarly, [allAddresses], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future refreshIfThereIsNewData() => (super.noSuchMethod( + _i23.Future refreshIfThereIsNewData() => (super.noSuchMethod( Invocation.method( #refreshIfThereIsNewData, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future getAllTxsToWatch(_i8.TransactionData? txData) => - (super.noSuchMethod( + _i23.Future getAllTxsToWatch() => (super.noSuchMethod( Invocation.method( #getAllTxsToWatch, - [txData], + [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future refresh() => (super.noSuchMethod( + _i23.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future> prepareSend({ + _i23.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i14.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -993,49 +1034,31 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i23.Future>.value({}), + ) as _i23.Future>); @override - _i16.Future confirmSend({required Map? txData}) => + _i23.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); - @override - _i16.Future testNetworkConnection() => (super.noSuchMethod( + _i23.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override void startNetworkAlivePinging() => super.noSuchMethod( Invocation.method( @@ -1053,33 +1076,33 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValueForMissingStub: null, ); @override - _i16.Future initializeNew() => (super.noSuchMethod( + _i23.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future initializeExisting() => (super.noSuchMethod( + _i23.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future updateSentCachedTxData(Map? txData) => + _i23.Future updateSentCachedTxData(Map? txData) => (super.noSuchMethod( Invocation.method( #updateSentCachedTxData, [txData], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -1089,112 +1112,69 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValue: false, ) as bool); @override - _i16.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i23.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future<_i10.ElectrumXNode> getCurrentNode() => (super.noSuchMethod( + _i23.Future<_i9.ElectrumXNode> getCurrentNode() => (super.noSuchMethod( Invocation.method( #getCurrentNode, [], ), - returnValue: - _i16.Future<_i10.ElectrumXNode>.value(_FakeElectrumXNode_11( + returnValue: _i23.Future<_i9.ElectrumXNode>.value(_FakeElectrumXNode_11( this, Invocation.method( #getCurrentNode, [], ), )), - ) as _i16.Future<_i10.ElectrumXNode>); + ) as _i23.Future<_i9.ElectrumXNode>); @override - _i16.Future addDerivation({ - required int? chain, - required String? address, - required String? pubKey, - required String? wif, - required _i19.DerivePathType? derivePathType, - }) => - (super.noSuchMethod( - Invocation.method( - #addDerivation, - [], - { - #chain: chain, - #address: address, - #pubKey: pubKey, - #wif: wif, - #derivePathType: derivePathType, - }, - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i16.Future addDerivations({ - required int? chain, - required _i19.DerivePathType? derivePathType, - required Map? derivationsToAdd, - }) => - (super.noSuchMethod( - Invocation.method( - #addDerivations, - [], - { - #chain: chain, - #derivePathType: derivePathType, - #derivationsToAdd: derivationsToAdd, - }, - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i16.Future getTxCount({required String? address}) => - (super.noSuchMethod( - Invocation.method( - #getTxCount, - [], - {#address: address}, - ), - returnValue: _i16.Future.value(0), - ) as _i16.Future); - @override - _i16.Future checkCurrentReceivingAddressesForTransactions() => - (super.noSuchMethod( - Invocation.method( - #checkCurrentReceivingAddressesForTransactions, - [], - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i16.Future checkCurrentChangeAddressesForTransactions() => - (super.noSuchMethod( - Invocation.method( - #checkCurrentChangeAddressesForTransactions, - [], - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i16.Future>> fastFetch( + _i23.Future>> fastFetch( List? allTxHashes) => (super.noSuchMethod( Invocation.method( #fastFetch, [allTxHashes], ), - returnValue: _i16.Future>>.value( + returnValue: _i23.Future>>.value( >[]), - ) as _i16.Future>>); + ) as _i23.Future>>); + @override + _i23.Future getTxCount({required String? address}) => + (super.noSuchMethod( + Invocation.method( + #getTxCount, + [], + {#address: address}, + ), + returnValue: _i23.Future.value(0), + ) as _i23.Future); + @override + _i23.Future checkCurrentReceivingAddressesForTransactions() => + (super.noSuchMethod( + Invocation.method( + #checkCurrentReceivingAddressesForTransactions, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future checkCurrentChangeAddressesForTransactions() => + (super.noSuchMethod( + Invocation.method( + #checkCurrentChangeAddressesForTransactions, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override int estimateTxFee({ required int? vSize, @@ -1212,42 +1192,42 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValue: 0, ) as int); @override - dynamic coinSelection( - int? satoshiAmountToSend, - int? selectedTxFeeRate, - String? _recipientAddress, - bool? isSendAll, { + dynamic coinSelection({ + required int? satoshiAmountToSend, + required int? selectedTxFeeRate, + required String? recipientAddress, + required bool? coinControl, + required bool? isSendAll, int? additionalOutputs = 0, - List<_i8.UtxoObject>? utxos, + List<_i17.UTXO>? utxos, }) => super.noSuchMethod(Invocation.method( #coinSelection, - [ - satoshiAmountToSend, - selectedTxFeeRate, - _recipientAddress, - isSendAll, - ], + [], { + #satoshiAmountToSend: satoshiAmountToSend, + #selectedTxFeeRate: selectedTxFeeRate, + #recipientAddress: recipientAddress, + #coinControl: coinControl, + #isSendAll: isSendAll, #additionalOutputs: additionalOutputs, #utxos: utxos, }, )); @override - _i16.Future> fetchBuildTxData( - List<_i8.UtxoObject>? utxosToUse) => + _i23.Future> fetchBuildTxData( + List<_i17.UTXO>? utxosToUse) => (super.noSuchMethod( Invocation.method( #fetchBuildTxData, [utxosToUse], ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i23.Future>.value(<_i28.SigningData>[]), + ) as _i23.Future>); @override - _i16.Future> buildTransaction({ - required List<_i8.UtxoObject>? utxosToUse, - required Map? utxoSigningData, + _i23.Future> buildTransaction({ + required List<_i28.SigningData>? utxoSigningData, required List? recipients, required List? satoshiAmounts, }) => @@ -1256,17 +1236,16 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { #buildTransaction, [], { - #utxosToUse: utxosToUse, #utxoSigningData: utxoSigningData, #recipients: recipients, #satoshiAmounts: satoshiAmounts, }, ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i23.Future>.value({}), + ) as _i23.Future>); @override - _i16.Future fullRescan( + _i23.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -1278,26 +1257,35 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { maxNumberOfIndexesToCheck, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future estimateFeeFor( - int? satoshiAmount, + _i23.Future<_i14.Amount> estimateFeeFor( + _i14.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future<_i14.Amount>.value(_FakeAmount_12( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i23.Future<_i14.Amount>); @override - int roughFeeEstimate( + _i14.Amount roughFeeEstimate( int? inputCount, int? outputCount, int? feeRatePerKB, @@ -1311,30 +1299,758 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { feeRatePerKB, ], ), - returnValue: 0, - ) as int); + returnValue: _FakeAmount_12( + this, + Invocation.method( + #roughFeeEstimate, + [ + inputCount, + outputCount, + feeRatePerKB, + ], + ), + ), + ) as _i14.Amount); @override - int sweepAllEstimate(int? feeRate) => (super.noSuchMethod( + _i23.Future<_i14.Amount> sweepAllEstimate(int? feeRate) => + (super.noSuchMethod( Invocation.method( #sweepAllEstimate, [feeRate], ), - returnValue: 0, - ) as int); + returnValue: _i23.Future<_i14.Amount>.value(_FakeAmount_12( + this, + Invocation.method( + #sweepAllEstimate, + [feeRate], + ), + )), + ) as _i23.Future<_i14.Amount>); @override - _i16.Future generateNewAddress() => (super.noSuchMethod( + _i23.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); + @override + void initCache( + String? walletId, + _i22.Coin? coin, + ) => + super.noSuchMethod( + Invocation.method( + #initCache, + [ + walletId, + coin, + ], + ), + returnValueForMissingStub: null, + ); + @override + _i23.Future updateCachedId(String? id) => (super.noSuchMethod( + Invocation.method( + #updateCachedId, + [id], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + int getCachedChainHeight() => (super.noSuchMethod( + Invocation.method( + #getCachedChainHeight, + [], + ), + returnValue: 0, + ) as int); + @override + _i23.Future updateCachedChainHeight(int? height) => (super.noSuchMethod( + Invocation.method( + #updateCachedChainHeight, + [height], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + bool getCachedIsFavorite() => (super.noSuchMethod( + Invocation.method( + #getCachedIsFavorite, + [], + ), + returnValue: false, + ) as bool); + @override + _i23.Future updateCachedIsFavorite(bool? isFavorite) => + (super.noSuchMethod( + Invocation.method( + #updateCachedIsFavorite, + [isFavorite], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i11.Balance getCachedBalance() => (super.noSuchMethod( + Invocation.method( + #getCachedBalance, + [], + ), + returnValue: _FakeBalance_8( + this, + Invocation.method( + #getCachedBalance, + [], + ), + ), + ) as _i11.Balance); + @override + _i23.Future updateCachedBalance(_i11.Balance? balance) => + (super.noSuchMethod( + Invocation.method( + #updateCachedBalance, + [balance], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i11.Balance getCachedBalanceSecondary() => (super.noSuchMethod( + Invocation.method( + #getCachedBalanceSecondary, + [], + ), + returnValue: _FakeBalance_8( + this, + Invocation.method( + #getCachedBalanceSecondary, + [], + ), + ), + ) as _i11.Balance); + @override + _i23.Future updateCachedBalanceSecondary(_i11.Balance? balance) => + (super.noSuchMethod( + Invocation.method( + #updateCachedBalanceSecondary, + [balance], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + List getWalletTokenContractAddresses() => (super.noSuchMethod( + Invocation.method( + #getWalletTokenContractAddresses, + [], + ), + returnValue: [], + ) as List); + @override + _i23.Future updateWalletTokenContractAddresses( + List? contractAddresses) => + (super.noSuchMethod( + Invocation.method( + #updateWalletTokenContractAddresses, + [contractAddresses], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + void initWalletDB({_i12.MainDB? mockableOverride}) => super.noSuchMethod( + Invocation.method( + #initWalletDB, + [], + {#mockableOverride: mockableOverride}, + ), + returnValueForMissingStub: null, + ); + @override + _i23.Future<_i15.Tuple2<_i17.Transaction, _i17.Address>> parseTransaction( + Map? txData, + dynamic electrumxClient, + List<_i17.Address>? myAddresses, + _i22.Coin? coin, + int? minConfirms, + String? walletId, + ) => + (super.noSuchMethod( + Invocation.method( + #parseTransaction, + [ + txData, + electrumxClient, + myAddresses, + coin, + minConfirms, + walletId, + ], + ), + returnValue: + _i23.Future<_i15.Tuple2<_i17.Transaction, _i17.Address>>.value( + _FakeTuple2_13<_i17.Transaction, _i17.Address>( + this, + Invocation.method( + #parseTransaction, + [ + txData, + electrumxClient, + myAddresses, + coin, + minConfirms, + walletId, + ], + ), + )), + ) as _i23.Future<_i15.Tuple2<_i17.Transaction, _i17.Address>>); + @override + void initPaynymWalletInterface({ + required String? walletId, + required String? walletName, + required _i13.NetworkType? network, + required _i22.Coin? coin, + required _i12.MainDB? db, + required _i9.ElectrumX? electrumXClient, + required _i19.SecureStorageInterface? secureStorage, + required int? dustLimit, + required int? dustLimitP2PKH, + required int? minConfirms, + required _i23.Future Function()? getMnemonicString, + required _i23.Future Function()? getMnemonicPassphrase, + required _i23.Future Function()? getChainHeight, + required _i23.Future Function()? getCurrentChangeAddress, + required int Function({ + required int feeRatePerKB, + required int vSize, + })? + estimateTxFee, + required _i23.Future> Function({ + required String address, + required _i14.Amount amount, + Map? args, + })? + prepareSend, + required _i23.Future Function({required String address})? getTxCount, + required _i23.Future> Function(List<_i17.UTXO>)? + fetchBuildTxData, + required _i23.Future Function()? refresh, + required _i23.Future Function()? checkChangeAddressForTransactions, + }) => + super.noSuchMethod( + Invocation.method( + #initPaynymWalletInterface, + [], + { + #walletId: walletId, + #walletName: walletName, + #network: network, + #coin: coin, + #db: db, + #electrumXClient: electrumXClient, + #secureStorage: secureStorage, + #dustLimit: dustLimit, + #dustLimitP2PKH: dustLimitP2PKH, + #minConfirms: minConfirms, + #getMnemonicString: getMnemonicString, + #getMnemonicPassphrase: getMnemonicPassphrase, + #getChainHeight: getChainHeight, + #getCurrentChangeAddress: getCurrentChangeAddress, + #estimateTxFee: estimateTxFee, + #prepareSend: prepareSend, + #getTxCount: getTxCount, + #fetchBuildTxData: fetchBuildTxData, + #refresh: refresh, + #checkChangeAddressForTransactions: + checkChangeAddressForTransactions, + }, + ), + returnValueForMissingStub: null, + ); + @override + _i23.Future<_i16.BIP32> getBip47BaseNode() => (super.noSuchMethod( + Invocation.method( + #getBip47BaseNode, + [], + ), + returnValue: _i23.Future<_i16.BIP32>.value(_FakeBIP32_14( + this, + Invocation.method( + #getBip47BaseNode, + [], + ), + )), + ) as _i23.Future<_i16.BIP32>); + @override + _i23.Future<_i29.Uint8List> getPrivateKeyForPaynymReceivingAddress({ + required String? paymentCodeString, + required int? index, + }) => + (super.noSuchMethod( + Invocation.method( + #getPrivateKeyForPaynymReceivingAddress, + [], + { + #paymentCodeString: paymentCodeString, + #index: index, + }, + ), + returnValue: _i23.Future<_i29.Uint8List>.value(_i29.Uint8List(0)), + ) as _i23.Future<_i29.Uint8List>); + @override + _i23.Future<_i17.Address> currentReceivingPaynymAddress({ + required _i18.PaymentCode? sender, + required bool? isSegwit, + }) => + (super.noSuchMethod( + Invocation.method( + #currentReceivingPaynymAddress, + [], + { + #sender: sender, + #isSegwit: isSegwit, + }, + ), + returnValue: _i23.Future<_i17.Address>.value(_FakeAddress_15( + this, + Invocation.method( + #currentReceivingPaynymAddress, + [], + { + #sender: sender, + #isSegwit: isSegwit, + }, + ), + )), + ) as _i23.Future<_i17.Address>); + @override + _i23.Future checkCurrentPaynymReceivingAddressForTransactions({ + required _i18.PaymentCode? sender, + required bool? isSegwit, + }) => + (super.noSuchMethod( + Invocation.method( + #checkCurrentPaynymReceivingAddressForTransactions, + [], + { + #sender: sender, + #isSegwit: isSegwit, + }, + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future checkAllCurrentReceivingPaynymAddressesForTransactions() => + (super.noSuchMethod( + Invocation.method( + #checkAllCurrentReceivingPaynymAddressesForTransactions, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future<_i16.BIP32> deriveNotificationBip32Node() => (super.noSuchMethod( + Invocation.method( + #deriveNotificationBip32Node, + [], + ), + returnValue: _i23.Future<_i16.BIP32>.value(_FakeBIP32_14( + this, + Invocation.method( + #deriveNotificationBip32Node, + [], + ), + )), + ) as _i23.Future<_i16.BIP32>); + @override + _i23.Future<_i18.PaymentCode> getPaymentCode({required bool? isSegwit}) => + (super.noSuchMethod( + Invocation.method( + #getPaymentCode, + [], + {#isSegwit: isSegwit}, + ), + returnValue: _i23.Future<_i18.PaymentCode>.value(_FakePaymentCode_16( + this, + Invocation.method( + #getPaymentCode, + [], + {#isSegwit: isSegwit}, + ), + )), + ) as _i23.Future<_i18.PaymentCode>); + @override + _i23.Future<_i29.Uint8List> signWithNotificationKey(_i29.Uint8List? data) => + (super.noSuchMethod( + Invocation.method( + #signWithNotificationKey, + [data], + ), + returnValue: _i23.Future<_i29.Uint8List>.value(_i29.Uint8List(0)), + ) as _i23.Future<_i29.Uint8List>); + @override + _i23.Future signStringWithNotificationKey(String? data) => + (super.noSuchMethod( + Invocation.method( + #signStringWithNotificationKey, + [data], + ), + returnValue: _i23.Future.value(''), + ) as _i23.Future); + @override + _i23.Future> preparePaymentCodeSend({ + required _i18.PaymentCode? paymentCode, + required bool? isSegwit, + required _i14.Amount? amount, + Map? args, + }) => + (super.noSuchMethod( + Invocation.method( + #preparePaymentCodeSend, + [], + { + #paymentCode: paymentCode, + #isSegwit: isSegwit, + #amount: amount, + #args: args, + }, + ), + returnValue: + _i23.Future>.value({}), + ) as _i23.Future>); + @override + _i23.Future<_i17.Address> nextUnusedSendAddressFrom({ + required _i18.PaymentCode? pCode, + required bool? isSegwit, + required _i16.BIP32? privateKeyNode, + int? startIndex = 0, + }) => + (super.noSuchMethod( + Invocation.method( + #nextUnusedSendAddressFrom, + [], + { + #pCode: pCode, + #isSegwit: isSegwit, + #privateKeyNode: privateKeyNode, + #startIndex: startIndex, + }, + ), + returnValue: _i23.Future<_i17.Address>.value(_FakeAddress_15( + this, + Invocation.method( + #nextUnusedSendAddressFrom, + [], + { + #pCode: pCode, + #isSegwit: isSegwit, + #privateKeyNode: privateKeyNode, + #startIndex: startIndex, + }, + ), + )), + ) as _i23.Future<_i17.Address>); + @override + _i23.Future> prepareNotificationTx({ + required int? selectedTxFeeRate, + required String? targetPaymentCodeString, + int? additionalOutputs = 0, + List<_i17.UTXO>? utxos, + }) => + (super.noSuchMethod( + Invocation.method( + #prepareNotificationTx, + [], + { + #selectedTxFeeRate: selectedTxFeeRate, + #targetPaymentCodeString: targetPaymentCodeString, + #additionalOutputs: additionalOutputs, + #utxos: utxos, + }, + ), + returnValue: + _i23.Future>.value({}), + ) as _i23.Future>); + @override + _i23.Future broadcastNotificationTx( + {required Map? preparedTx}) => + (super.noSuchMethod( + Invocation.method( + #broadcastNotificationTx, + [], + {#preparedTx: preparedTx}, + ), + returnValue: _i23.Future.value(''), + ) as _i23.Future); + @override + _i23.Future hasConnected(String? paymentCodeString) => + (super.noSuchMethod( + Invocation.method( + #hasConnected, + [paymentCodeString], + ), + returnValue: _i23.Future.value(false), + ) as _i23.Future); + @override + _i23.Future<_i18.PaymentCode?> unBlindedPaymentCodeFromTransaction( + {required _i17.Transaction? transaction}) => + (super.noSuchMethod( + Invocation.method( + #unBlindedPaymentCodeFromTransaction, + [], + {#transaction: transaction}, + ), + returnValue: _i23.Future<_i18.PaymentCode?>.value(), + ) as _i23.Future<_i18.PaymentCode?>); + @override + _i23.Future<_i18.PaymentCode?> unBlindedPaymentCodeFromTransactionBad( + {required _i17.Transaction? transaction}) => + (super.noSuchMethod( + Invocation.method( + #unBlindedPaymentCodeFromTransactionBad, + [], + {#transaction: transaction}, + ), + returnValue: _i23.Future<_i18.PaymentCode?>.value(), + ) as _i23.Future<_i18.PaymentCode?>); + @override + _i23.Future> + getAllPaymentCodesFromNotificationTransactions() => (super.noSuchMethod( + Invocation.method( + #getAllPaymentCodesFromNotificationTransactions, + [], + ), + returnValue: + _i23.Future>.value(<_i18.PaymentCode>[]), + ) as _i23.Future>); + @override + _i23.Future checkForNotificationTransactionsTo( + Set? otherCodeStrings) => + (super.noSuchMethod( + Invocation.method( + #checkForNotificationTransactionsTo, + [otherCodeStrings], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future restoreAllHistory({ + required int? maxUnusedAddressGap, + required int? maxNumberOfIndexesToCheck, + required Set? paymentCodeStrings, + }) => + (super.noSuchMethod( + Invocation.method( + #restoreAllHistory, + [], + { + #maxUnusedAddressGap: maxUnusedAddressGap, + #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + #paymentCodeStrings: paymentCodeStrings, + }, + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future restoreHistoryWith({ + required _i18.PaymentCode? other, + required bool? checkSegwitAsWell, + required int? maxUnusedAddressGap, + required int? maxNumberOfIndexesToCheck, + }) => + (super.noSuchMethod( + Invocation.method( + #restoreHistoryWith, + [], + { + #other: other, + #checkSegwitAsWell: checkSegwitAsWell, + #maxUnusedAddressGap: maxUnusedAddressGap, + #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + }, + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future<_i17.Address> getMyNotificationAddress() => (super.noSuchMethod( + Invocation.method( + #getMyNotificationAddress, + [], + ), + returnValue: _i23.Future<_i17.Address>.value(_FakeAddress_15( + this, + Invocation.method( + #getMyNotificationAddress, + [], + ), + )), + ) as _i23.Future<_i17.Address>); + @override + _i23.Future> lookupKey(String? paymentCodeString) => + (super.noSuchMethod( + Invocation.method( + #lookupKey, + [paymentCodeString], + ), + returnValue: _i23.Future>.value([]), + ) as _i23.Future>); + @override + _i23.Future paymentCodeStringByKey(String? key) => + (super.noSuchMethod( + Invocation.method( + #paymentCodeStringByKey, + [key], + ), + returnValue: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future storeCode(String? paymentCodeString) => + (super.noSuchMethod( + Invocation.method( + #storeCode, + [paymentCodeString], + ), + returnValue: _i23.Future.value(''), + ) as _i23.Future); + @override + void initCoinControlInterface({ + required String? walletId, + required String? walletName, + required _i22.Coin? coin, + required _i12.MainDB? db, + required _i23.Future Function()? getChainHeight, + required _i23.Future Function(_i11.Balance)? refreshedBalanceCallback, + }) => + super.noSuchMethod( + Invocation.method( + #initCoinControlInterface, + [], + { + #walletId: walletId, + #walletName: walletName, + #coin: coin, + #db: db, + #getChainHeight: getChainHeight, + #refreshedBalanceCallback: refreshedBalanceCallback, + }, + ), + returnValueForMissingStub: null, + ); + @override + _i23.Future refreshBalance({bool? notify = false}) => + (super.noSuchMethod( + Invocation.method( + #refreshBalance, + [], + {#notify: notify}, + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); +} + +/// A class which mocks [ThemeService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockThemeService extends _i1.Mock implements _i30.ThemeService { + MockThemeService() { + _i1.throwOnMissingStub(this); + } + + @override + _i12.MainDB get db => (super.noSuchMethod( + Invocation.getter(#db), + returnValue: _FakeMainDB_9( + this, + Invocation.getter(#db), + ), + ) as _i12.MainDB); + @override + List<_i31.StackTheme> get installedThemes => (super.noSuchMethod( + Invocation.getter(#installedThemes), + returnValue: <_i31.StackTheme>[], + ) as List<_i31.StackTheme>); + @override + void init(_i12.MainDB? db) => super.noSuchMethod( + Invocation.method( + #init, + [db], + ), + returnValueForMissingStub: null, + ); + @override + _i23.Future install({required _i29.Uint8List? themeArchiveData}) => + (super.noSuchMethod( + Invocation.method( + #install, + [], + {#themeArchiveData: themeArchiveData}, + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future remove({required String? themeId}) => (super.noSuchMethod( + Invocation.method( + #remove, + [], + {#themeId: themeId}, + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future verifyInstalled({required String? themeId}) => + (super.noSuchMethod( + Invocation.method( + #verifyInstalled, + [], + {#themeId: themeId}, + ), + returnValue: _i23.Future.value(false), + ) as _i23.Future); + @override + _i23.Future> fetchThemes() => + (super.noSuchMethod( + Invocation.method( + #fetchThemes, + [], + ), + returnValue: _i23.Future>.value( + <_i30.StackThemeMetaData>[]), + ) as _i23.Future>); + @override + _i23.Future<_i29.Uint8List> fetchTheme( + {required _i30.StackThemeMetaData? themeMetaData}) => + (super.noSuchMethod( + Invocation.method( + #fetchTheme, + [], + {#themeMetaData: themeMetaData}, + ), + returnValue: _i23.Future<_i29.Uint8List>.value(_i29.Uint8List(0)), + ) as _i23.Future<_i29.Uint8List>); + @override + _i31.StackTheme? getTheme({required String? themeId}) => + (super.noSuchMethod(Invocation.method( + #getTheme, + [], + {#themeId: themeId}, + )) as _i31.StackTheme?); } /// A class which mocks [LocaleService]. /// /// See the documentation for Mockito's code generation for more information. -class MockLocaleService extends _i1.Mock implements _i20.LocaleService { +class MockLocaleService extends _i1.Mock implements _i32.LocaleService { MockLocaleService() { _i1.throwOnMissingStub(this); } @@ -1350,17 +2066,17 @@ class MockLocaleService extends _i1.Mock implements _i20.LocaleService { returnValue: false, ) as bool); @override - _i16.Future loadLocale({bool? notify = true}) => (super.noSuchMethod( + _i23.Future loadLocale({bool? notify = true}) => (super.noSuchMethod( Invocation.method( #loadLocale, [], {#notify: notify}, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -1368,7 +2084,7 @@ class MockLocaleService extends _i1.Mock implements _i20.LocaleService { returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -1398,41 +2114,41 @@ class MockLocaleService extends _i1.Mock implements _i20.LocaleService { /// See the documentation for Mockito's code generation for more information. class MockNodeService extends _i1.Mock implements _i3.NodeService { @override - _i12.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod( + _i19.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod( Invocation.getter(#secureStorageInterface), - returnValue: _FakeSecureStorageInterface_12( + returnValue: _FakeSecureStorageInterface_17( this, Invocation.getter(#secureStorageInterface), ), - ) as _i12.SecureStorageInterface); + ) as _i19.SecureStorageInterface); @override - List<_i21.NodeModel> get primaryNodes => (super.noSuchMethod( + List<_i33.NodeModel> get primaryNodes => (super.noSuchMethod( Invocation.getter(#primaryNodes), - returnValue: <_i21.NodeModel>[], - ) as List<_i21.NodeModel>); + returnValue: <_i33.NodeModel>[], + ) as List<_i33.NodeModel>); @override - List<_i21.NodeModel> get nodes => (super.noSuchMethod( + List<_i33.NodeModel> get nodes => (super.noSuchMethod( Invocation.getter(#nodes), - returnValue: <_i21.NodeModel>[], - ) as List<_i21.NodeModel>); + returnValue: <_i33.NodeModel>[], + ) as List<_i33.NodeModel>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i16.Future updateDefaults() => (super.noSuchMethod( + _i23.Future updateDefaults() => (super.noSuchMethod( Invocation.method( #updateDefaults, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future setPrimaryNodeFor({ - required _i15.Coin? coin, - required _i21.NodeModel? node, + _i23.Future setPrimaryNodeFor({ + required _i22.Coin? coin, + required _i33.NodeModel? node, bool? shouldNotifyListeners = false, }) => (super.noSuchMethod( @@ -1445,44 +2161,44 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i21.NodeModel? getPrimaryNodeFor({required _i15.Coin? coin}) => + _i33.NodeModel? getPrimaryNodeFor({required _i22.Coin? coin}) => (super.noSuchMethod(Invocation.method( #getPrimaryNodeFor, [], {#coin: coin}, - )) as _i21.NodeModel?); + )) as _i33.NodeModel?); @override - List<_i21.NodeModel> getNodesFor(_i15.Coin? coin) => (super.noSuchMethod( + List<_i33.NodeModel> getNodesFor(_i22.Coin? coin) => (super.noSuchMethod( Invocation.method( #getNodesFor, [coin], ), - returnValue: <_i21.NodeModel>[], - ) as List<_i21.NodeModel>); + returnValue: <_i33.NodeModel>[], + ) as List<_i33.NodeModel>); @override - _i21.NodeModel? getNodeById({required String? id}) => + _i33.NodeModel? getNodeById({required String? id}) => (super.noSuchMethod(Invocation.method( #getNodeById, [], {#id: id}, - )) as _i21.NodeModel?); + )) as _i33.NodeModel?); @override - List<_i21.NodeModel> failoverNodesFor({required _i15.Coin? coin}) => + List<_i33.NodeModel> failoverNodesFor({required _i22.Coin? coin}) => (super.noSuchMethod( Invocation.method( #failoverNodesFor, [], {#coin: coin}, ), - returnValue: <_i21.NodeModel>[], - ) as List<_i21.NodeModel>); + returnValue: <_i33.NodeModel>[], + ) as List<_i33.NodeModel>); @override - _i16.Future add( - _i21.NodeModel? node, + _i23.Future add( + _i33.NodeModel? node, String? password, bool? shouldNotifyListeners, ) => @@ -1495,11 +2211,11 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { shouldNotifyListeners, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future delete( + _i23.Future delete( String? id, bool? shouldNotifyListeners, ) => @@ -1511,11 +2227,11 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { shouldNotifyListeners, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future setEnabledState( + _i23.Future setEnabledState( String? id, bool? enabled, bool? shouldNotifyListeners, @@ -1529,12 +2245,12 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { shouldNotifyListeners, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future edit( - _i21.NodeModel? editedNode, + _i23.Future edit( + _i33.NodeModel? editedNode, String? password, bool? shouldNotifyListeners, ) => @@ -1547,20 +2263,20 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { shouldNotifyListeners, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future updateCommunityNodes() => (super.noSuchMethod( + _i23.Future updateCommunityNodes() => (super.noSuchMethod( Invocation.method( #updateCommunityNodes, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -1568,7 +2284,7 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -1611,23 +2327,23 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValueForMissingStub: null, ); @override - _i13.CoinServiceAPI get wallet => (super.noSuchMethod( + _i20.CoinServiceAPI get wallet => (super.noSuchMethod( Invocation.getter(#wallet), - returnValue: _FakeCoinServiceAPI_13( + returnValue: _FakeCoinServiceAPI_18( this, Invocation.getter(#wallet), ), - ) as _i13.CoinServiceAPI); + ) as _i20.CoinServiceAPI); @override bool get hasBackgroundRefreshListener => (super.noSuchMethod( Invocation.getter(#hasBackgroundRefreshListener), returnValue: false, ) as bool); @override - _i15.Coin get coin => (super.noSuchMethod( + _i22.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i15.Coin.bitcoin, - ) as _i15.Coin); + returnValue: _i22.Coin.bitcoin, + ) as _i22.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -1660,91 +2376,42 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValueForMissingStub: null, ); @override - _i16.Future<_i8.FeeObject> get fees => (super.noSuchMethod( + _i23.Future<_i8.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i16.Future<_i8.FeeObject>.value(_FakeFeeObject_7( + returnValue: _i23.Future<_i8.FeeObject>.value(_FakeFeeObject_5( this, Invocation.getter(#fees), )), - ) as _i16.Future<_i8.FeeObject>); + ) as _i23.Future<_i8.FeeObject>); @override - _i16.Future get maxFee => (super.noSuchMethod( + _i23.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future.value(0), + ) as _i23.Future); @override - _i16.Future get currentReceivingAddress => (super.noSuchMethod( + _i23.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future<_i9.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( + _i11.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_8( this, - Invocation.getter(#availableBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i9.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_6( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i9.Decimal); + ) as _i11.Balance); @override - _i16.Future<_i9.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i9.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i9.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#totalBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i9.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_6( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i9.Decimal); - @override - _i16.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); - @override - _i16.Future<_i8.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i23.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i16.Future<_i8.TransactionData>.value(_FakeTransactionData_8( - this, - Invocation.getter(#transactionData), - )), - ) as _i16.Future<_i8.TransactionData>); + _i23.Future>.value(<_i17.Transaction>[]), + ) as _i23.Future>); @override - _i16.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: - _i16.Future>.value(<_i8.UtxoObject>[]), - ) as _i16.Future>); + _i23.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i23.Future>.value(<_i17.UTXO>[]), + ) as _i23.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -1764,29 +2431,74 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValue: '', ) as String); @override - _i16.Future> get mnemonic => (super.noSuchMethod( + _i23.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); + returnValue: _i23.Future>.value([]), + ) as _i23.Future>); + @override + _i23.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i23.Future.value(), + ) as _i23.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i23.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i23.Future.value(''), + ) as _i23.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i16.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i23.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -1796,9 +2508,9 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValueForMissingStub: null, ); @override - _i16.Future> prepareSend({ + _i23.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i14.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -1807,50 +2519,32 @@ class MockManager extends _i1.Mock implements _i6.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i23.Future>.value({}), + ) as _i23.Future>); @override - _i16.Future confirmSend({required Map? txData}) => + _i23.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); - @override - _i16.Future refresh() => (super.noSuchMethod( + _i23.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -1860,34 +2554,35 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValue: false, ) as bool); @override - _i16.Future testNetworkConnection() => (super.noSuchMethod( + _i23.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future initializeNew() => (super.noSuchMethod( + _i23.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future initializeExisting() => (super.noSuchMethod( + _i23.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future recoverFromMnemonic({ + _i23.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -1898,25 +2593,26 @@ class MockManager extends _i1.Mock implements _i6.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future exitCurrentWallet() => (super.noSuchMethod( + _i23.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future fullRescan( + _i23.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -1928,42 +2624,52 @@ class MockManager extends _i1.Mock implements _i6.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); - @override - _i16.Future estimateFeeFor( - int? satoshiAmount, + _i23.Future<_i14.Amount> estimateFeeFor( + _i14.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future<_i14.Amount>.value(_FakeAmount_12( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i23.Future<_i14.Amount>); @override - _i16.Future generateNewAddress() => (super.noSuchMethod( + _i23.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + _i23.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + void addListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -1971,7 +2677,7 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -1991,7 +2697,7 @@ class MockManager extends _i1.Mock implements _i6.Manager { /// A class which mocks [CoinServiceAPI]. /// /// See the documentation for Mockito's code generation for more information. -class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { +class MockCoinServiceAPI extends _i1.Mock implements _i20.CoinServiceAPI { @override set onIsActiveWalletChanged(void Function(bool)? _onIsActiveWalletChanged) => super.noSuchMethod( @@ -2002,10 +2708,10 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { returnValueForMissingStub: null, ); @override - _i15.Coin get coin => (super.noSuchMethod( + _i22.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i15.Coin.bitcoin, - ) as _i15.Coin); + returnValue: _i22.Coin.bitcoin, + ) as _i22.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -2038,75 +2744,42 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { returnValueForMissingStub: null, ); @override - _i16.Future<_i8.FeeObject> get fees => (super.noSuchMethod( + _i23.Future<_i8.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i16.Future<_i8.FeeObject>.value(_FakeFeeObject_7( + returnValue: _i23.Future<_i8.FeeObject>.value(_FakeFeeObject_5( this, Invocation.getter(#fees), )), - ) as _i16.Future<_i8.FeeObject>); + ) as _i23.Future<_i8.FeeObject>); @override - _i16.Future get maxFee => (super.noSuchMethod( + _i23.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future.value(0), + ) as _i23.Future); @override - _i16.Future get currentReceivingAddress => (super.noSuchMethod( + _i23.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future<_i9.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( + _i11.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_8( this, - Invocation.getter(#availableBalance), - )), - ) as _i16.Future<_i9.Decimal>); + Invocation.getter(#balance), + ), + ) as _i11.Balance); @override - _i16.Future<_i9.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i9.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#totalBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i9.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); - @override - _i16.Future<_i8.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i23.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i16.Future<_i8.TransactionData>.value(_FakeTransactionData_8( - this, - Invocation.getter(#transactionData), - )), - ) as _i16.Future<_i8.TransactionData>); + _i23.Future>.value(<_i17.Transaction>[]), + ) as _i23.Future>); @override - _i16.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: - _i16.Future>.value(<_i8.UtxoObject>[]), - ) as _i16.Future>); + _i23.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i23.Future>.value(<_i17.UTXO>[]), + ) as _i23.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -2126,10 +2799,20 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { returnValue: '', ) as String); @override - _i16.Future> get mnemonic => (super.noSuchMethod( + _i23.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); + returnValue: _i23.Future>.value([]), + ) as _i23.Future>); + @override + _i23.Future get mnemonicString => (super.noSuchMethod( + Invocation.getter(#mnemonicString), + returnValue: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i23.Future.value(), + ) as _i23.Future); @override bool get hasCalledExit => (super.noSuchMethod( Invocation.getter(#hasCalledExit), @@ -2141,9 +2824,14 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { returnValue: false, ) as bool); @override - _i16.Future> prepareSend({ + int get storedChainHeight => (super.noSuchMethod( + Invocation.getter(#storedChainHeight), + returnValue: 0, + ) as int); + @override + _i23.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i14.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -2152,59 +2840,41 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i23.Future>.value({}), + ) as _i23.Future>); @override - _i16.Future confirmSend({required Map? txData}) => + _i23.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future send({ - required String? toAddress, - required int? amount, - Map? args, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); - @override - _i16.Future refresh() => (super.noSuchMethod( + _i23.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i23.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -2214,16 +2884,17 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { returnValue: false, ) as bool); @override - _i16.Future testNetworkConnection() => (super.noSuchMethod( + _i23.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future recoverFromMnemonic({ + _i23.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -2234,43 +2905,44 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future initializeNew() => (super.noSuchMethod( + _i23.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future initializeExisting() => (super.noSuchMethod( + _i23.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future exit() => (super.noSuchMethod( + _i23.Future exit() => (super.noSuchMethod( Invocation.method( #exit, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future fullRescan( + _i23.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -2282,40 +2954,49 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { maxNumberOfIndexesToCheck, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future estimateFeeFor( - int? satoshiAmount, + _i23.Future<_i14.Amount> estimateFeeFor( + _i14.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future<_i14.Amount>.value(_FakeAmount_12( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i23.Future<_i14.Amount>); @override - _i16.Future generateNewAddress() => (super.noSuchMethod( + _i23.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future updateSentCachedTxData(Map? txData) => + _i23.Future updateSentCachedTxData(Map? txData) => (super.noSuchMethod( Invocation.method( #updateSentCachedTxData, [txData], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); } diff --git a/test/widget_tests/node_card_test.dart b/test/widget_tests/node_card_test.dart index 22e0661bf..cf7be76af 100644 --- a/test/widget_tests/node_card_test.dart +++ b/test/widget_tests/node_card_test.dart @@ -4,16 +4,17 @@ import 'package:flutter_svg/svg.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; import 'package:stackwallet/models/node_model.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/node_service.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/node_card.dart'; import 'package:stackwallet/widgets/node_options_sheet.dart'; +import '../sample_data/theme_json.dart'; import 'node_card_test.mocks.dart'; @GenerateMocks([NodeService]) @@ -54,7 +55,10 @@ void main() { theme: ThemeData( extensions: [ StackColors.fromStackColorTheme( - LightColors(), + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), ), ], ), @@ -112,7 +116,10 @@ void main() { theme: ThemeData( extensions: [ StackColors.fromStackColorTheme( - LightColors(), + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), ), ], ), @@ -171,7 +178,10 @@ void main() { theme: ThemeData( extensions: [ StackColors.fromStackColorTheme( - LightColors(), + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), ), ], ), diff --git a/test/widget_tests/node_options_sheet_test.dart b/test/widget_tests/node_options_sheet_test.dart index 2a4782c07..c1aacbb51 100644 --- a/test/widget_tests/node_options_sheet_test.dart +++ b/test/widget_tests/node_options_sheet_test.dart @@ -1,21 +1,22 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:mockingjay/mockingjay.dart' as mockingjay; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; import 'package:stackwallet/models/node_model.dart'; -import 'package:mockingjay/mockingjay.dart' as mockingjay; import 'package:stackwallet/providers/providers.dart'; +import 'package:stackwallet/services/node_service.dart'; import 'package:stackwallet/services/wallets.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/prefs.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; -import 'package:stackwallet/services/node_service.dart'; import 'package:stackwallet/widgets/node_options_sheet.dart'; import 'package:tuple/tuple.dart'; +import '../sample_data/theme_json.dart'; import 'node_options_sheet_test.mocks.dart'; @GenerateMocks([Wallets, Prefs, NodeService]) @@ -60,7 +61,10 @@ void main() { theme: ThemeData( extensions: [ StackColors.fromStackColorTheme( - LightColors(), + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), ), ], ), @@ -129,7 +133,12 @@ void main() { child: MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: mockingjay.MockNavigatorProvider( @@ -192,7 +201,10 @@ void main() { theme: ThemeData( extensions: [ StackColors.fromStackColorTheme( - LightColors(), + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), ), ], ), diff --git a/test/widget_tests/node_options_sheet_test.mocks.dart b/test/widget_tests/node_options_sheet_test.mocks.dart index 7e3e3a92d..d90ee6681 100644 --- a/test/widget_tests/node_options_sheet_test.mocks.dart +++ b/test/widget_tests/node_options_sheet_test.mocks.dart @@ -3,25 +3,24 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i10; -import 'dart:ui' as _i12; +import 'dart:async' as _i11; +import 'dart:ui' as _i13; import 'package:flutter/foundation.dart' as _i4; import 'package:flutter_riverpod/flutter_riverpod.dart' as _i5; import 'package:mockito/mockito.dart' as _i1; import 'package:stackwallet/models/node_model.dart' as _i16; -import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart' - as _i14; import 'package:stackwallet/services/coins/manager.dart' as _i6; import 'package:stackwallet/services/node_service.dart' as _i3; import 'package:stackwallet/services/wallets.dart' as _i8; import 'package:stackwallet/services/wallets_service.dart' as _i2; import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i15; import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i9; -import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i13; +import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i14; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' as _i7; -import 'package:stackwallet/utilities/prefs.dart' as _i11; +import 'package:stackwallet/utilities/prefs.dart' as _i12; +import 'package:tuple/tuple.dart' as _i10; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -167,15 +166,28 @@ class MockWallets extends _i1.Mock implements _i8.Wallets { returnValue: [], ) as List); @override - Map<_i9.Coin, List<_i5.ChangeNotifierProvider<_i6.Manager>>> + List<_i10.Tuple2<_i9.Coin, List<_i5.ChangeNotifierProvider<_i6.Manager>>>> getManagerProvidersByCoin() => (super.noSuchMethod( Invocation.method( #getManagerProvidersByCoin, [], ), - returnValue: <_i9.Coin, - List<_i5.ChangeNotifierProvider<_i6.Manager>>>{}, - ) as Map<_i9.Coin, List<_i5.ChangeNotifierProvider<_i6.Manager>>>); + returnValue: < + _i10.Tuple2<_i9.Coin, + List<_i5.ChangeNotifierProvider<_i6.Manager>>>>[], + ) as List< + _i10.Tuple2<_i9.Coin, + List<_i5.ChangeNotifierProvider<_i6.Manager>>>>); + @override + List<_i5.ChangeNotifierProvider<_i6.Manager>> getManagerProvidersForCoin( + _i9.Coin? coin) => + (super.noSuchMethod( + Invocation.method( + #getManagerProvidersForCoin, + [coin], + ), + returnValue: <_i5.ChangeNotifierProvider<_i6.Manager>>[], + ) as List<_i5.ChangeNotifierProvider<_i6.Manager>>); @override _i5.ChangeNotifierProvider<_i6.Manager> getManagerProvider( String? walletId) => @@ -232,17 +244,17 @@ class MockWallets extends _i1.Mock implements _i8.Wallets { returnValueForMissingStub: null, ); @override - _i10.Future load(_i11.Prefs? prefs) => (super.noSuchMethod( + _i11.Future load(_i12.Prefs? prefs) => (super.noSuchMethod( Invocation.method( #load, [prefs], ), - returnValue: _i10.Future.value(), - returnValueForMissingStub: _i10.Future.value(), - ) as _i10.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i10.Future loadAfterStackRestore( - _i11.Prefs? prefs, + _i11.Future loadAfterStackRestore( + _i12.Prefs? prefs, List<_i6.Manager>? managers, ) => (super.noSuchMethod( @@ -253,11 +265,11 @@ class MockWallets extends _i1.Mock implements _i8.Wallets { managers, ], ), - returnValue: _i10.Future.value(), - returnValueForMissingStub: _i10.Future.value(), - ) as _i10.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - void addListener(_i12.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i13.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -265,7 +277,7 @@ class MockWallets extends _i1.Mock implements _i8.Wallets { returnValueForMissingStub: null, ); @override - void removeListener(_i12.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i13.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -285,7 +297,7 @@ class MockWallets extends _i1.Mock implements _i8.Wallets { /// A class which mocks [Prefs]. /// /// See the documentation for Mockito's code generation for more information. -class MockPrefs extends _i1.Mock implements _i11.Prefs { +class MockPrefs extends _i1.Mock implements _i12.Prefs { MockPrefs() { _i1.throwOnMissingStub(this); } @@ -341,12 +353,12 @@ class MockPrefs extends _i1.Mock implements _i11.Prefs { returnValueForMissingStub: null, ); @override - _i13.SyncingType get syncType => (super.noSuchMethod( + _i14.SyncingType get syncType => (super.noSuchMethod( Invocation.getter(#syncType), - returnValue: _i13.SyncingType.currentWalletOnly, - ) as _i13.SyncingType); + returnValue: _i14.SyncingType.currentWalletOnly, + ) as _i14.SyncingType); @override - set syncType(_i13.SyncingType? syncType) => super.noSuchMethod( + set syncType(_i14.SyncingType? syncType) => super.noSuchMethod( Invocation.setter( #syncType, syncType, @@ -406,16 +418,15 @@ class MockPrefs extends _i1.Mock implements _i11.Prefs { returnValueForMissingStub: null, ); @override - _i14.ExchangeRateType get exchangeRateType => (super.noSuchMethod( - Invocation.getter(#exchangeRateType), - returnValue: _i14.ExchangeRateType.estimated, - ) as _i14.ExchangeRateType); + bool get randomizePIN => (super.noSuchMethod( + Invocation.getter(#randomizePIN), + returnValue: false, + ) as bool); @override - set exchangeRateType(_i14.ExchangeRateType? exchangeRateType) => - super.noSuchMethod( + set randomizePIN(bool? randomizePIN) => super.noSuchMethod( Invocation.setter( - #exchangeRateType, - exchangeRateType, + #randomizePIN, + randomizePIN, ), returnValueForMissingStub: null, ); @@ -563,38 +574,124 @@ class MockPrefs extends _i1.Mock implements _i11.Prefs { returnValueForMissingStub: null, ); @override + bool get enableCoinControl => (super.noSuchMethod( + Invocation.getter(#enableCoinControl), + returnValue: false, + ) as bool); + @override + set enableCoinControl(bool? enableCoinControl) => super.noSuchMethod( + Invocation.setter( + #enableCoinControl, + enableCoinControl, + ), + returnValueForMissingStub: null, + ); + @override + bool get enableSystemBrightness => (super.noSuchMethod( + Invocation.getter(#enableSystemBrightness), + returnValue: false, + ) as bool); + @override + set enableSystemBrightness(bool? enableSystemBrightness) => + super.noSuchMethod( + Invocation.setter( + #enableSystemBrightness, + enableSystemBrightness, + ), + returnValueForMissingStub: null, + ); + @override + String get themeId => (super.noSuchMethod( + Invocation.getter(#themeId), + returnValue: '', + ) as String); + @override + set themeId(String? themeId) => super.noSuchMethod( + Invocation.setter( + #themeId, + themeId, + ), + returnValueForMissingStub: null, + ); + @override + String get systemBrightnessLightThemeId => (super.noSuchMethod( + Invocation.getter(#systemBrightnessLightThemeId), + returnValue: '', + ) as String); + @override + set systemBrightnessLightThemeId(String? systemBrightnessLightThemeId) => + super.noSuchMethod( + Invocation.setter( + #systemBrightnessLightThemeId, + systemBrightnessLightThemeId, + ), + returnValueForMissingStub: null, + ); + @override + String get systemBrightnessDarkThemeId => (super.noSuchMethod( + Invocation.getter(#systemBrightnessDarkThemeId), + returnValue: '', + ) as String); + @override + set systemBrightnessDarkThemeId(String? systemBrightnessDarkThemeId) => + super.noSuchMethod( + Invocation.setter( + #systemBrightnessDarkThemeId, + systemBrightnessDarkThemeId, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i10.Future init() => (super.noSuchMethod( + _i11.Future init() => (super.noSuchMethod( Invocation.method( #init, [], ), - returnValue: _i10.Future.value(), - returnValueForMissingStub: _i10.Future.value(), - ) as _i10.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i10.Future incrementCurrentNotificationIndex() => (super.noSuchMethod( + _i11.Future incrementCurrentNotificationIndex() => (super.noSuchMethod( Invocation.method( #incrementCurrentNotificationIndex, [], ), - returnValue: _i10.Future.value(), - returnValueForMissingStub: _i10.Future.value(), - ) as _i10.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i10.Future isExternalCallsSet() => (super.noSuchMethod( + _i11.Future isExternalCallsSet() => (super.noSuchMethod( Invocation.method( #isExternalCallsSet, [], ), - returnValue: _i10.Future.value(false), - ) as _i10.Future); + returnValue: _i11.Future.value(false), + ) as _i11.Future); @override - void addListener(_i12.VoidCallback? listener) => super.noSuchMethod( + _i11.Future saveUserID(String? userId) => (super.noSuchMethod( + Invocation.method( + #saveUserID, + [userId], + ), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + @override + _i11.Future saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod( + Invocation.method( + #saveSignupEpoch, + [signupEpoch], + ), + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); + @override + void addListener(_i13.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -602,7 +699,7 @@ class MockPrefs extends _i1.Mock implements _i11.Prefs { returnValueForMissingStub: null, ); @override - void removeListener(_i12.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i13.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -659,16 +756,16 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { returnValue: false, ) as bool); @override - _i10.Future updateDefaults() => (super.noSuchMethod( + _i11.Future updateDefaults() => (super.noSuchMethod( Invocation.method( #updateDefaults, [], ), - returnValue: _i10.Future.value(), - returnValueForMissingStub: _i10.Future.value(), - ) as _i10.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i10.Future setPrimaryNodeFor({ + _i11.Future setPrimaryNodeFor({ required _i9.Coin? coin, required _i16.NodeModel? node, bool? shouldNotifyListeners = false, @@ -683,9 +780,9 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i10.Future.value(), - returnValueForMissingStub: _i10.Future.value(), - ) as _i10.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override _i16.NodeModel? getPrimaryNodeFor({required _i9.Coin? coin}) => (super.noSuchMethod(Invocation.method( @@ -719,7 +816,7 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { returnValue: <_i16.NodeModel>[], ) as List<_i16.NodeModel>); @override - _i10.Future add( + _i11.Future add( _i16.NodeModel? node, String? password, bool? shouldNotifyListeners, @@ -733,11 +830,11 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { shouldNotifyListeners, ], ), - returnValue: _i10.Future.value(), - returnValueForMissingStub: _i10.Future.value(), - ) as _i10.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i10.Future delete( + _i11.Future delete( String? id, bool? shouldNotifyListeners, ) => @@ -749,11 +846,11 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { shouldNotifyListeners, ], ), - returnValue: _i10.Future.value(), - returnValueForMissingStub: _i10.Future.value(), - ) as _i10.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i10.Future setEnabledState( + _i11.Future setEnabledState( String? id, bool? enabled, bool? shouldNotifyListeners, @@ -767,11 +864,11 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { shouldNotifyListeners, ], ), - returnValue: _i10.Future.value(), - returnValueForMissingStub: _i10.Future.value(), - ) as _i10.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i10.Future edit( + _i11.Future edit( _i16.NodeModel? editedNode, String? password, bool? shouldNotifyListeners, @@ -785,20 +882,20 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { shouldNotifyListeners, ], ), - returnValue: _i10.Future.value(), - returnValueForMissingStub: _i10.Future.value(), - ) as _i10.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - _i10.Future updateCommunityNodes() => (super.noSuchMethod( + _i11.Future updateCommunityNodes() => (super.noSuchMethod( Invocation.method( #updateCommunityNodes, [], ), - returnValue: _i10.Future.value(), - returnValueForMissingStub: _i10.Future.value(), - ) as _i10.Future); + returnValue: _i11.Future.value(), + returnValueForMissingStub: _i11.Future.value(), + ) as _i11.Future); @override - void addListener(_i12.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i13.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -806,7 +903,7 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { returnValueForMissingStub: null, ); @override - void removeListener(_i12.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i13.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], diff --git a/test/widget_tests/progress_bar_test.dart b/test/widget_tests/progress_bar_test.dart index 1fe1876d6..f375e7392 100644 --- a/test/widget_tests/progress_bar_test.dart +++ b/test/widget_tests/progress_bar_test.dart @@ -1,26 +1,31 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/widgets/progress_bar.dart'; +import '../sample_data/theme_json.dart'; + void main() { testWidgets("Widget build", (widgetTester) async { + final theme = StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ); await widgetTester.pumpWidget( MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme(theme), ], ), home: Material( child: ProgressBar( width: 20, height: 10, - fillColor: - StackColors.fromStackColorTheme(LightColors()).accentColorRed, - backgroundColor: StackColors.fromStackColorTheme(LightColors()) - .accentColorYellow, + fillColor: StackColors.fromStackColorTheme(theme).accentColorRed, + backgroundColor: + StackColors.fromStackColorTheme(theme).accentColorYellow, percent: 30), ), ), diff --git a/test/widget_tests/shake/shake_test.dart b/test/widget_tests/shake/shake_test.dart index cddd99c05..e699a2fca 100644 --- a/test/widget_tests/shake/shake_test.dart +++ b/test/widget_tests/shake/shake_test.dart @@ -1,16 +1,23 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/widgets/shake/shake.dart'; +import '../../sample_data/theme_json.dart'; + void main() { testWidgets("Widget build", (widgetTester) async { await widgetTester.pumpWidget( MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: Material( diff --git a/test/widget_tests/stack_dialog_test.dart b/test/widget_tests/stack_dialog_test.dart index 1f33acb32..3397e3c9e 100644 --- a/test/widget_tests/stack_dialog_test.dart +++ b/test/widget_tests/stack_dialog_test.dart @@ -1,10 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; -import 'package:stackwallet/widgets/stack_dialog.dart'; import 'package:mockingjay/mockingjay.dart' as mockingjay; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/widgets/stack_dialog.dart'; + +import '../sample_data/theme_json.dart'; void main() { testWidgets("test StackDialogBase", (widgetTester) async { @@ -12,7 +14,12 @@ void main() { MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: const Material( @@ -29,7 +36,12 @@ void main() { MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: const Material( @@ -60,7 +72,12 @@ void main() { child: MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: mockingjay.MockNavigatorProvider( diff --git a/test/widget_tests/table_view/table_view_cell_test.dart b/test/widget_tests/table_view/table_view_cell_test.dart index dff6ba513..228a7c2e9 100644 --- a/test/widget_tests/table_view/table_view_cell_test.dart +++ b/test/widget_tests/table_view/table_view_cell_test.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/widgets/table_view/table_view_cell.dart'; +import '../../sample_data/theme_json.dart'; + void main() { testWidgets("Widget build correctly", (widgetTester) async { const tableViewCell = TableViewCell(flex: 16, child: Text("data")); @@ -11,7 +13,12 @@ void main() { MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: const Material( diff --git a/test/widget_tests/table_view/table_view_row_test.dart b/test/widget_tests/table_view/table_view_row_test.dart index 9b675b271..676d18f3b 100644 --- a/test/widget_tests/table_view/table_view_row_test.dart +++ b/test/widget_tests/table_view/table_view_row_test.dart @@ -3,6 +3,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; import 'package:stackwallet/pages_desktop_specific/my_stack_view/coin_wallets_table.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/coins/bitcoin/bitcoin_wallet.dart'; @@ -10,17 +12,20 @@ import 'package:stackwallet/services/coins/coin_service.dart'; import 'package:stackwallet/services/coins/manager.dart'; import 'package:stackwallet/services/wallets.dart'; import 'package:stackwallet/services/wallets_service.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_service.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/table_view/table_view_cell.dart'; import 'package:stackwallet/widgets/table_view/table_view_row.dart'; +import '../../sample_data/theme_json.dart'; import 'table_view_row_test.mocks.dart'; @GenerateMocks([ Wallets, WalletsService, + ThemeService, BitcoinWallet ], customMocks: [ MockSpec(returnNullOnMissingStub: true), @@ -28,12 +33,29 @@ import 'table_view_row_test.mocks.dart'; ]) void main() { testWidgets('Test table view row', (widgetTester) async { + widgetTester.binding.window.physicalSizeTestValue = const Size(2500, 1800); + final mockWallet = MockWallets(); + final mockThemeService = MockThemeService(); final CoinServiceAPI wallet = MockBitcoinWallet(); when(wallet.coin).thenAnswer((_) => Coin.bitcoin); + when(mockThemeService.getTheme(themeId: "light")).thenAnswer( + (_) => StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ); when(wallet.walletName).thenAnswer((_) => "some wallet"); when(wallet.walletId).thenAnswer((_) => "Wallet id 1"); + when(wallet.balance).thenAnswer( + (_) => Balance( + total: Amount.zero, + spendable: Amount.zero, + blockedTotal: Amount.zero, + pendingSpendable: Amount.zero, + ), + ); final manager = Manager(wallet); @@ -46,36 +68,41 @@ void main() { when(mockWallet.getManagerProvider("wallet id 2")).thenAnswer( (realInvocation) => ChangeNotifierProvider((ref) => manager)); - final walletIds = mockWallet.getWalletIdsFor(coin: Coin.bitcoin); - await widgetTester.pumpWidget( ProviderScope( overrides: [ walletsChangeNotifierProvider.overrideWithValue(mockWallet), + pThemeService.overrideWithValue(mockThemeService), ], child: MaterialApp( theme: ThemeData( extensions: [ StackColors.fromStackColorTheme( - LightColors(), + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), ), ], ), home: Material( child: TableViewRow( - cells: [ - for (int j = 1; j <= 5; j++) - TableViewCell(flex: 16, child: Text("Some Text ${j}")) - ], - expandingChild: const CoinWalletsTable( - coin: Coin.bitcoin, - )), + cells: [ + for (int j = 1; j <= 5; j++) + TableViewCell(flex: 16, child: Text("Some ${j}")) + ], + expandingChild: const CoinWalletsTable( + coin: Coin.bitcoin, + ), + ), ), ), ), ); - expect(find.text("Some Text 1"), findsOneWidget); + await widgetTester.pumpAndSettle(); + + expect(find.text("Some 1"), findsOneWidget); expect(find.byType(TableViewRow), findsWidgets); expect(find.byType(TableViewCell), findsWidgets); expect(find.byType(CoinWalletsTable), findsWidgets); diff --git a/test/widget_tests/table_view/table_view_row_test.mocks.dart b/test/widget_tests/table_view/table_view_row_test.mocks.dart index 5a47436ff..62a4e3bd5 100644 --- a/test/widget_tests/table_view/table_view_row_test.mocks.dart +++ b/test/widget_tests/table_view/table_view_row_test.mocks.dart @@ -3,26 +3,40 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i15; -import 'dart:ui' as _i17; +import 'dart:async' as _i22; +import 'dart:typed_data' as _i27; +import 'dart:ui' as _i24; -import 'package:decimal/decimal.dart' as _i9; +import 'package:bip32/bip32.dart' as _i16; +import 'package:bip47/bip47.dart' as _i18; +import 'package:bitcoindart/bitcoindart.dart' as _i13; import 'package:flutter/foundation.dart' as _i4; import 'package:flutter_riverpod/flutter_riverpod.dart' as _i5; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/db/isar/main_db.dart' as _i7; import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i11; import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i10; -import 'package:stackwallet/models/models.dart' as _i8; -import 'package:stackwallet/services/coins/bitcoin/bitcoin_wallet.dart' as _i18; -import 'package:stackwallet/services/coins/coin_service.dart' as _i12; +import 'package:stackwallet/models/balance.dart' as _i12; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i17; +import 'package:stackwallet/models/isar/stack_theme.dart' as _i26; +import 'package:stackwallet/models/paymint/fee_object_model.dart' as _i9; +import 'package:stackwallet/models/signing_data.dart' as _i30; +import 'package:stackwallet/services/coins/bitcoin/bitcoin_wallet.dart' as _i28; +import 'package:stackwallet/services/coins/coin_service.dart' as _i19; import 'package:stackwallet/services/coins/manager.dart' as _i6; import 'package:stackwallet/services/node_service.dart' as _i3; import 'package:stackwallet/services/transaction_notification_tracker.dart' - as _i7; -import 'package:stackwallet/services/wallets.dart' as _i13; + as _i8; +import 'package:stackwallet/services/wallets.dart' as _i20; import 'package:stackwallet/services/wallets_service.dart' as _i2; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i14; -import 'package:stackwallet/utilities/prefs.dart' as _i16; +import 'package:stackwallet/themes/theme_service.dart' as _i25; +import 'package:stackwallet/utilities/amount/amount.dart' as _i14; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i21; +import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart' as _i29; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' + as _i31; +import 'package:stackwallet/utilities/prefs.dart' as _i23; +import 'package:tuple/tuple.dart' as _i15; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -77,9 +91,8 @@ class _FakeManager_3 extends _i1.SmartFake implements _i6.Manager { ); } -class _FakeTransactionNotificationTracker_4 extends _i1.SmartFake - implements _i7.TransactionNotificationTracker { - _FakeTransactionNotificationTracker_4( +class _FakeMainDB_4 extends _i1.SmartFake implements _i7.MainDB { + _FakeMainDB_4( Object parent, Invocation parentInvocation, ) : super( @@ -88,8 +101,9 @@ class _FakeTransactionNotificationTracker_4 extends _i1.SmartFake ); } -class _FakeUtxoData_5 extends _i1.SmartFake implements _i8.UtxoData { - _FakeUtxoData_5( +class _FakeTransactionNotificationTracker_5 extends _i1.SmartFake + implements _i8.TransactionNotificationTracker { + _FakeTransactionNotificationTracker_5( Object parent, Invocation parentInvocation, ) : super( @@ -98,8 +112,8 @@ class _FakeUtxoData_5 extends _i1.SmartFake implements _i8.UtxoData { ); } -class _FakeDecimal_6 extends _i1.SmartFake implements _i9.Decimal { - _FakeDecimal_6( +class _FakeFeeObject_6 extends _i1.SmartFake implements _i9.FeeObject { + _FakeFeeObject_6( Object parent, Invocation parentInvocation, ) : super( @@ -108,8 +122,8 @@ class _FakeDecimal_6 extends _i1.SmartFake implements _i9.Decimal { ); } -class _FakeFeeObject_7 extends _i1.SmartFake implements _i8.FeeObject { - _FakeFeeObject_7( +class _FakeElectrumX_7 extends _i1.SmartFake implements _i10.ElectrumX { + _FakeElectrumX_7( Object parent, Invocation parentInvocation, ) : super( @@ -118,30 +132,29 @@ class _FakeFeeObject_7 extends _i1.SmartFake implements _i8.FeeObject { ); } -class _FakeTransactionData_8 extends _i1.SmartFake - implements _i8.TransactionData { - _FakeTransactionData_8( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakeElectrumX_9 extends _i1.SmartFake implements _i10.ElectrumX { - _FakeElectrumX_9( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakeCachedElectrumX_10 extends _i1.SmartFake +class _FakeCachedElectrumX_8 extends _i1.SmartFake implements _i11.CachedElectrumX { - _FakeCachedElectrumX_10( + _FakeCachedElectrumX_8( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeBalance_9 extends _i1.SmartFake implements _i12.Balance { + _FakeBalance_9( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeNetworkType_10 extends _i1.SmartFake implements _i13.NetworkType { + _FakeNetworkType_10( Object parent, Invocation parentInvocation, ) : super( @@ -161,9 +174,60 @@ class _FakeElectrumXNode_11 extends _i1.SmartFake ); } -class _FakeCoinServiceAPI_12 extends _i1.SmartFake - implements _i12.CoinServiceAPI { - _FakeCoinServiceAPI_12( +class _FakeAmount_12 extends _i1.SmartFake implements _i14.Amount { + _FakeAmount_12( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeTuple2_13 extends _i1.SmartFake + implements _i15.Tuple2 { + _FakeTuple2_13( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeBIP32_14 extends _i1.SmartFake implements _i16.BIP32 { + _FakeBIP32_14( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAddress_15 extends _i1.SmartFake implements _i17.Address { + _FakeAddress_15( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePaymentCode_16 extends _i1.SmartFake implements _i18.PaymentCode { + _FakePaymentCode_16( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeCoinServiceAPI_17 extends _i1.SmartFake + implements _i19.CoinServiceAPI { + _FakeCoinServiceAPI_17( Object parent, Invocation parentInvocation, ) : super( @@ -175,7 +239,7 @@ class _FakeCoinServiceAPI_12 extends _i1.SmartFake /// A class which mocks [Wallets]. /// /// See the documentation for Mockito's code generation for more information. -class MockWallets extends _i1.Mock implements _i13.Wallets { +class MockWallets extends _i1.Mock implements _i20.Wallets { MockWallets() { _i1.throwOnMissingStub(this); } @@ -242,7 +306,7 @@ class MockWallets extends _i1.Mock implements _i13.Wallets { returnValueForMissingStub: null, ); @override - List getWalletIdsFor({required _i14.Coin? coin}) => + List getWalletIdsFor({required _i21.Coin? coin}) => (super.noSuchMethod( Invocation.method( #getWalletIdsFor, @@ -252,15 +316,28 @@ class MockWallets extends _i1.Mock implements _i13.Wallets { returnValue: [], ) as List); @override - Map<_i14.Coin, List<_i5.ChangeNotifierProvider<_i6.Manager>>> + List<_i15.Tuple2<_i21.Coin, List<_i5.ChangeNotifierProvider<_i6.Manager>>>> getManagerProvidersByCoin() => (super.noSuchMethod( Invocation.method( #getManagerProvidersByCoin, [], ), - returnValue: <_i14.Coin, - List<_i5.ChangeNotifierProvider<_i6.Manager>>>{}, - ) as Map<_i14.Coin, List<_i5.ChangeNotifierProvider<_i6.Manager>>>); + returnValue: < + _i15.Tuple2<_i21.Coin, + List<_i5.ChangeNotifierProvider<_i6.Manager>>>>[], + ) as List< + _i15.Tuple2<_i21.Coin, + List<_i5.ChangeNotifierProvider<_i6.Manager>>>>); + @override + List<_i5.ChangeNotifierProvider<_i6.Manager>> getManagerProvidersForCoin( + _i21.Coin? coin) => + (super.noSuchMethod( + Invocation.method( + #getManagerProvidersForCoin, + [coin], + ), + returnValue: <_i5.ChangeNotifierProvider<_i6.Manager>>[], + ) as List<_i5.ChangeNotifierProvider<_i6.Manager>>); @override _i5.ChangeNotifierProvider<_i6.Manager> getManagerProvider( String? walletId) => @@ -317,17 +394,17 @@ class MockWallets extends _i1.Mock implements _i13.Wallets { returnValueForMissingStub: null, ); @override - _i15.Future load(_i16.Prefs? prefs) => (super.noSuchMethod( + _i22.Future load(_i23.Prefs? prefs) => (super.noSuchMethod( Invocation.method( #load, [prefs], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future loadAfterStackRestore( - _i16.Prefs? prefs, + _i22.Future loadAfterStackRestore( + _i23.Prefs? prefs, List<_i6.Manager>? managers, ) => (super.noSuchMethod( @@ -338,11 +415,11 @@ class MockWallets extends _i1.Mock implements _i13.Wallets { managers, ], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - void addListener(_i17.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i24.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -350,7 +427,7 @@ class MockWallets extends _i1.Mock implements _i13.Wallets { returnValueForMissingStub: null, ); @override - void removeListener(_i17.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i24.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -376,19 +453,19 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { } @override - _i15.Future> get walletNames => + _i22.Future> get walletNames => (super.noSuchMethod( Invocation.getter(#walletNames), - returnValue: _i15.Future>.value( + returnValue: _i22.Future>.value( {}), - ) as _i15.Future>); + ) as _i22.Future>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i15.Future renameWallet({ + _i22.Future renameWallet({ required String? from, required String? to, required bool? shouldNotifyListeners, @@ -403,13 +480,21 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i15.Future.value(false), - ) as _i15.Future); + returnValue: _i22.Future.value(false), + ) as _i22.Future); @override - _i15.Future addExistingStackWallet({ + Map fetchWalletsData() => (super.noSuchMethod( + Invocation.method( + #fetchWalletsData, + [], + ), + returnValue: {}, + ) as Map); + @override + _i22.Future addExistingStackWallet({ required String? name, required String? walletId, - required _i14.Coin? coin, + required _i21.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -423,13 +508,13 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future addNewWallet({ + _i22.Future addNewWallet({ required String? name, - required _i14.Coin? coin, + required _i21.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -442,46 +527,46 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future> getFavoriteWalletIds() => (super.noSuchMethod( + _i22.Future> getFavoriteWalletIds() => (super.noSuchMethod( Invocation.method( #getFavoriteWalletIds, [], ), - returnValue: _i15.Future>.value([]), - ) as _i15.Future>); + returnValue: _i22.Future>.value([]), + ) as _i22.Future>); @override - _i15.Future saveFavoriteWalletIds(List? walletIds) => + _i22.Future saveFavoriteWalletIds(List? walletIds) => (super.noSuchMethod( Invocation.method( #saveFavoriteWalletIds, [walletIds], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future addFavorite(String? walletId) => (super.noSuchMethod( + _i22.Future addFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #addFavorite, [walletId], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future removeFavorite(String? walletId) => (super.noSuchMethod( + _i22.Future removeFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #removeFavorite, [walletId], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future moveFavorite({ + _i22.Future moveFavorite({ required int? fromIndex, required int? toIndex, }) => @@ -494,48 +579,48 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { #toIndex: toIndex, }, ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future checkForDuplicate(String? name) => (super.noSuchMethod( + _i22.Future checkForDuplicate(String? name) => (super.noSuchMethod( Invocation.method( #checkForDuplicate, [name], ), - returnValue: _i15.Future.value(false), - ) as _i15.Future); + returnValue: _i22.Future.value(false), + ) as _i22.Future); @override - _i15.Future getWalletId(String? walletName) => (super.noSuchMethod( + _i22.Future getWalletId(String? walletName) => (super.noSuchMethod( Invocation.method( #getWalletId, [walletName], ), - returnValue: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future isMnemonicVerified({required String? walletId}) => + _i22.Future isMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #isMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i15.Future.value(false), - ) as _i15.Future); + returnValue: _i22.Future.value(false), + ) as _i22.Future); @override - _i15.Future setMnemonicVerified({required String? walletId}) => + _i22.Future setMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #setMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future deleteWallet( + _i22.Future deleteWallet( String? name, bool? shouldNotifyListeners, ) => @@ -547,20 +632,20 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { shouldNotifyListeners, ], ), - returnValue: _i15.Future.value(0), - ) as _i15.Future); + returnValue: _i22.Future.value(0), + ) as _i22.Future); @override - _i15.Future refreshWallets(bool? shouldNotifyListeners) => + _i22.Future refreshWallets(bool? shouldNotifyListeners) => (super.noSuchMethod( Invocation.method( #refreshWallets, [shouldNotifyListeners], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - void addListener(_i17.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i24.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -568,7 +653,7 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { returnValueForMissingStub: null, ); @override - void removeListener(_i17.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i24.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -593,16 +678,106 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { ); } +/// A class which mocks [ThemeService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockThemeService extends _i1.Mock implements _i25.ThemeService { + MockThemeService() { + _i1.throwOnMissingStub(this); + } + + @override + _i7.MainDB get db => (super.noSuchMethod( + Invocation.getter(#db), + returnValue: _FakeMainDB_4( + this, + Invocation.getter(#db), + ), + ) as _i7.MainDB); + @override + List<_i26.StackTheme> get installedThemes => (super.noSuchMethod( + Invocation.getter(#installedThemes), + returnValue: <_i26.StackTheme>[], + ) as List<_i26.StackTheme>); + @override + void init(_i7.MainDB? db) => super.noSuchMethod( + Invocation.method( + #init, + [db], + ), + returnValueForMissingStub: null, + ); + @override + _i22.Future install({required _i27.Uint8List? themeArchiveData}) => + (super.noSuchMethod( + Invocation.method( + #install, + [], + {#themeArchiveData: themeArchiveData}, + ), + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); + @override + _i22.Future remove({required String? themeId}) => (super.noSuchMethod( + Invocation.method( + #remove, + [], + {#themeId: themeId}, + ), + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); + @override + _i22.Future verifyInstalled({required String? themeId}) => + (super.noSuchMethod( + Invocation.method( + #verifyInstalled, + [], + {#themeId: themeId}, + ), + returnValue: _i22.Future.value(false), + ) as _i22.Future); + @override + _i22.Future> fetchThemes() => + (super.noSuchMethod( + Invocation.method( + #fetchThemes, + [], + ), + returnValue: _i22.Future>.value( + <_i25.StackThemeMetaData>[]), + ) as _i22.Future>); + @override + _i22.Future<_i27.Uint8List> fetchTheme( + {required _i25.StackThemeMetaData? themeMetaData}) => + (super.noSuchMethod( + Invocation.method( + #fetchTheme, + [], + {#themeMetaData: themeMetaData}, + ), + returnValue: _i22.Future<_i27.Uint8List>.value(_i27.Uint8List(0)), + ) as _i22.Future<_i27.Uint8List>); + @override + _i26.StackTheme? getTheme({required String? themeId}) => + (super.noSuchMethod(Invocation.method( + #getTheme, + [], + {#themeId: themeId}, + )) as _i26.StackTheme?); +} + /// A class which mocks [BitcoinWallet]. /// /// See the documentation for Mockito's code generation for more information. -class MockBitcoinWallet extends _i1.Mock implements _i18.BitcoinWallet { +class MockBitcoinWallet extends _i1.Mock implements _i28.BitcoinWallet { MockBitcoinWallet() { _i1.throwOnMissingStub(this); } @override - set timer(_i15.Timer? _timer) => super.noSuchMethod( + set timer(_i22.Timer? _timer) => super.noSuchMethod( Invocation.setter( #timer, _timer, @@ -610,15 +785,15 @@ class MockBitcoinWallet extends _i1.Mock implements _i18.BitcoinWallet { returnValueForMissingStub: null, ); @override - _i7.TransactionNotificationTracker get txTracker => (super.noSuchMethod( + _i8.TransactionNotificationTracker get txTracker => (super.noSuchMethod( Invocation.getter(#txTracker), - returnValue: _FakeTransactionNotificationTracker_4( + returnValue: _FakeTransactionNotificationTracker_5( this, Invocation.getter(#txTracker), ), - ) as _i7.TransactionNotificationTracker); + ) as _i8.TransactionNotificationTracker); @override - set txTracker(_i7.TransactionNotificationTracker? _txTracker) => + set txTracker(_i8.TransactionNotificationTracker? _txTracker) => super.noSuchMethod( Invocation.setter( #txTracker, @@ -627,19 +802,6 @@ class MockBitcoinWallet extends _i1.Mock implements _i18.BitcoinWallet { returnValueForMissingStub: null, ); @override - List<_i8.UtxoObject> get outputsList => (super.noSuchMethod( - Invocation.getter(#outputsList), - returnValue: <_i8.UtxoObject>[], - ) as List<_i8.UtxoObject>); - @override - set outputsList(List<_i8.UtxoObject>? _outputsList) => super.noSuchMethod( - Invocation.setter( - #outputsList, - _outputsList, - ), - returnValueForMissingStub: null, - ); - @override bool get longMutex => (super.noSuchMethod( Invocation.getter(#longMutex), returnValue: false, @@ -666,14 +828,6 @@ class MockBitcoinWallet extends _i1.Mock implements _i18.BitcoinWallet { returnValueForMissingStub: null, ); @override - set cachedTxData(_i8.TransactionData? _cachedTxData) => super.noSuchMethod( - Invocation.setter( - #cachedTxData, - _cachedTxData, - ), - returnValueForMissingStub: null, - ); - @override bool get isActive => (super.noSuchMethod( Invocation.getter(#isActive), returnValue: false, @@ -700,104 +854,74 @@ class MockBitcoinWallet extends _i1.Mock implements _i18.BitcoinWallet { returnValue: false, ) as bool); @override - _i14.Coin get coin => (super.noSuchMethod( + _i21.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i14.Coin.bitcoin, - ) as _i14.Coin); + returnValue: _i21.Coin.bitcoin, + ) as _i21.Coin); @override - _i15.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i15.Future>.value([]), - ) as _i15.Future>); + _i22.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i22.Future>.value(<_i17.UTXO>[]), + ) as _i22.Future>); @override - _i15.Future<_i8.UtxoData> get utxoData => (super.noSuchMethod( - Invocation.getter(#utxoData), - returnValue: _i15.Future<_i8.UtxoData>.value(_FakeUtxoData_5( - this, - Invocation.getter(#utxoData), - )), - ) as _i15.Future<_i8.UtxoData>); - @override - _i15.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), + _i22.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i15.Future>.value(<_i8.UtxoObject>[]), - ) as _i15.Future>); + _i22.Future>.value(<_i17.Transaction>[]), + ) as _i22.Future>); @override - _i15.Future<_i9.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i15.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#availableBalance), - )), - ) as _i15.Future<_i9.Decimal>); - @override - _i15.Future<_i9.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i15.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i15.Future<_i9.Decimal>); - @override - _i15.Future<_i9.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i15.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i15.Future<_i9.Decimal>); - @override - _i15.Future<_i9.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i15.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#totalBalance), - )), - ) as _i15.Future<_i9.Decimal>); - @override - _i15.Future get currentReceivingAddress => (super.noSuchMethod( + _i22.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i15.Future.value(''), - ) as _i15.Future); + returnValue: _i22.Future.value(''), + ) as _i22.Future); @override - _i15.Future get currentLegacyReceivingAddress => (super.noSuchMethod( - Invocation.getter(#currentLegacyReceivingAddress), - returnValue: _i15.Future.value(''), - ) as _i15.Future); + _i22.Future get currentChangeAddress => (super.noSuchMethod( + Invocation.getter(#currentChangeAddress), + returnValue: _i22.Future.value(''), + ) as _i22.Future); @override - _i15.Future get currentReceivingAddressP2SH => (super.noSuchMethod( - Invocation.getter(#currentReceivingAddressP2SH), - returnValue: _i15.Future.value(''), - ) as _i15.Future); + _i22.Future get currentChangeAddressP2PKH => (super.noSuchMethod( + Invocation.getter(#currentChangeAddressP2PKH), + returnValue: _i22.Future.value(''), + ) as _i22.Future); @override bool get hasCalledExit => (super.noSuchMethod( Invocation.getter(#hasCalledExit), returnValue: false, ) as bool); @override - _i15.Future<_i8.FeeObject> get fees => (super.noSuchMethod( + _i22.Future<_i9.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i15.Future<_i8.FeeObject>.value(_FakeFeeObject_7( + returnValue: _i22.Future<_i9.FeeObject>.value(_FakeFeeObject_6( this, Invocation.getter(#fees), )), - ) as _i15.Future<_i8.FeeObject>); + ) as _i22.Future<_i9.FeeObject>); @override - _i15.Future get maxFee => (super.noSuchMethod( + _i22.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i15.Future.value(0), - ) as _i15.Future); + returnValue: _i22.Future.value(0), + ) as _i22.Future); @override - _i15.Future> get mnemonic => (super.noSuchMethod( + _i22.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i15.Future>.value([]), - ) as _i15.Future>); + returnValue: _i22.Future>.value([]), + ) as _i22.Future>); @override - _i15.Future get chainHeight => (super.noSuchMethod( + _i22.Future get mnemonicString => (super.noSuchMethod( + Invocation.getter(#mnemonicString), + returnValue: _i22.Future.value(), + ) as _i22.Future); + @override + _i22.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i22.Future.value(), + ) as _i22.Future); + @override + _i22.Future get chainHeight => (super.noSuchMethod( Invocation.getter(#chainHeight), - returnValue: _i15.Future.value(0), - ) as _i15.Future); + returnValue: _i22.Future.value(0), + ) as _i22.Future); @override int get storedChainHeight => (super.noSuchMethod( Invocation.getter(#storedChainHeight), @@ -827,15 +951,6 @@ class MockBitcoinWallet extends _i1.Mock implements _i18.BitcoinWallet { returnValue: false, ) as bool); @override - _i15.Future<_i8.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), - returnValue: - _i15.Future<_i8.TransactionData>.value(_FakeTransactionData_8( - this, - Invocation.getter(#transactionData), - )), - ) as _i15.Future<_i8.TransactionData>); - @override String get walletId => (super.noSuchMethod( Invocation.getter(#walletId), returnValue: '', @@ -856,7 +971,7 @@ class MockBitcoinWallet extends _i1.Mock implements _i18.BitcoinWallet { @override _i10.ElectrumX get electrumXClient => (super.noSuchMethod( Invocation.getter(#electrumXClient), - returnValue: _FakeElectrumX_9( + returnValue: _FakeElectrumX_7( this, Invocation.getter(#electrumXClient), ), @@ -864,12 +979,25 @@ class MockBitcoinWallet extends _i1.Mock implements _i18.BitcoinWallet { @override _i11.CachedElectrumX get cachedElectrumXClient => (super.noSuchMethod( Invocation.getter(#cachedElectrumXClient), - returnValue: _FakeCachedElectrumX_10( + returnValue: _FakeCachedElectrumX_8( this, Invocation.getter(#cachedElectrumXClient), ), ) as _i11.CachedElectrumX); @override + _i12.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_9( + this, + Invocation.getter(#balance), + ), + ) as _i12.Balance); + @override + _i22.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i22.Future.value(''), + ) as _i22.Future); + @override set onIsActiveWalletChanged(void Function(bool)? _onIsActiveWalletChanged) => super.noSuchMethod( Invocation.setter( @@ -879,38 +1007,44 @@ class MockBitcoinWallet extends _i1.Mock implements _i18.BitcoinWallet { returnValueForMissingStub: null, ); @override - _i15.Future exit() => (super.noSuchMethod( + _i7.MainDB get db => (super.noSuchMethod( + Invocation.getter(#db), + returnValue: _FakeMainDB_4( + this, + Invocation.getter(#db), + ), + ) as _i7.MainDB); + @override + _i13.NetworkType get networkType => (super.noSuchMethod( + Invocation.getter(#networkType), + returnValue: _FakeNetworkType_10( + this, + Invocation.getter(#networkType), + ), + ) as _i13.NetworkType); + @override + _i22.Future exit() => (super.noSuchMethod( Invocation.method( #exit, [], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future updateStoredChainHeight({required int? newHeight}) => - (super.noSuchMethod( - Invocation.method( - #updateStoredChainHeight, - [], - {#newHeight: newHeight}, - ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); - @override - _i18.DerivePathType addressType({required String? address}) => + _i29.DerivePathType addressType({required String? address}) => (super.noSuchMethod( Invocation.method( #addressType, [], {#address: address}, ), - returnValue: _i18.DerivePathType.bip44, - ) as _i18.DerivePathType); + returnValue: _i29.DerivePathType.bip44, + ) as _i29.DerivePathType); @override - _i15.Future recoverFromMnemonic({ + _i22.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -921,55 +1055,55 @@ class MockBitcoinWallet extends _i1.Mock implements _i18.BitcoinWallet { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future getTransactionCacheEarly(List? allAddresses) => + _i22.Future getTransactionCacheEarly(List? allAddresses) => (super.noSuchMethod( Invocation.method( #getTransactionCacheEarly, [allAddresses], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future refreshIfThereIsNewData() => (super.noSuchMethod( + _i22.Future refreshIfThereIsNewData() => (super.noSuchMethod( Invocation.method( #refreshIfThereIsNewData, [], ), - returnValue: _i15.Future.value(false), - ) as _i15.Future); + returnValue: _i22.Future.value(false), + ) as _i22.Future); @override - _i15.Future getAllTxsToWatch(_i8.TransactionData? txData) => - (super.noSuchMethod( + _i22.Future getAllTxsToWatch() => (super.noSuchMethod( Invocation.method( #getAllTxsToWatch, - [txData], + [], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future refresh() => (super.noSuchMethod( + _i22.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future> prepareSend({ + _i22.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i14.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -978,49 +1112,31 @@ class MockBitcoinWallet extends _i1.Mock implements _i18.BitcoinWallet { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i15.Future>.value({}), - ) as _i15.Future>); + _i22.Future>.value({}), + ) as _i22.Future>); @override - _i15.Future confirmSend({required Map? txData}) => + _i22.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i15.Future.value(''), - ) as _i15.Future); + returnValue: _i22.Future.value(''), + ) as _i22.Future); @override - _i15.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i15.Future.value(''), - ) as _i15.Future); - @override - _i15.Future testNetworkConnection() => (super.noSuchMethod( + _i22.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i15.Future.value(false), - ) as _i15.Future); + returnValue: _i22.Future.value(false), + ) as _i22.Future); @override void startNetworkAlivePinging() => super.noSuchMethod( Invocation.method( @@ -1038,33 +1154,33 @@ class MockBitcoinWallet extends _i1.Mock implements _i18.BitcoinWallet { returnValueForMissingStub: null, ); @override - _i15.Future initializeNew() => (super.noSuchMethod( + _i22.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future initializeExisting() => (super.noSuchMethod( + _i22.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future updateSentCachedTxData(Map? txData) => + _i22.Future updateSentCachedTxData(Map? txData) => (super.noSuchMethod( Invocation.method( #updateSentCachedTxData, [txData], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -1074,112 +1190,70 @@ class MockBitcoinWallet extends _i1.Mock implements _i18.BitcoinWallet { returnValue: false, ) as bool); @override - _i15.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i22.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future<_i10.ElectrumXNode> getCurrentNode() => (super.noSuchMethod( + _i22.Future<_i10.ElectrumXNode> getCurrentNode() => (super.noSuchMethod( Invocation.method( #getCurrentNode, [], ), returnValue: - _i15.Future<_i10.ElectrumXNode>.value(_FakeElectrumXNode_11( + _i22.Future<_i10.ElectrumXNode>.value(_FakeElectrumXNode_11( this, Invocation.method( #getCurrentNode, [], ), )), - ) as _i15.Future<_i10.ElectrumXNode>); + ) as _i22.Future<_i10.ElectrumXNode>); @override - _i15.Future addDerivation({ - required int? chain, - required String? address, - required String? pubKey, - required String? wif, - required _i18.DerivePathType? derivePathType, - }) => - (super.noSuchMethod( - Invocation.method( - #addDerivation, - [], - { - #chain: chain, - #address: address, - #pubKey: pubKey, - #wif: wif, - #derivePathType: derivePathType, - }, - ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); - @override - _i15.Future addDerivations({ - required int? chain, - required _i18.DerivePathType? derivePathType, - required Map? derivationsToAdd, - }) => - (super.noSuchMethod( - Invocation.method( - #addDerivations, - [], - { - #chain: chain, - #derivePathType: derivePathType, - #derivationsToAdd: derivationsToAdd, - }, - ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); - @override - _i15.Future getTxCount({required String? address}) => - (super.noSuchMethod( - Invocation.method( - #getTxCount, - [], - {#address: address}, - ), - returnValue: _i15.Future.value(0), - ) as _i15.Future); - @override - _i15.Future checkCurrentReceivingAddressesForTransactions() => - (super.noSuchMethod( - Invocation.method( - #checkCurrentReceivingAddressesForTransactions, - [], - ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); - @override - _i15.Future checkCurrentChangeAddressesForTransactions() => - (super.noSuchMethod( - Invocation.method( - #checkCurrentChangeAddressesForTransactions, - [], - ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); - @override - _i15.Future>> fastFetch( + _i22.Future>> fastFetch( List? allTxHashes) => (super.noSuchMethod( Invocation.method( #fastFetch, [allTxHashes], ), - returnValue: _i15.Future>>.value( + returnValue: _i22.Future>>.value( >[]), - ) as _i15.Future>>); + ) as _i22.Future>>); + @override + _i22.Future getTxCount({required String? address}) => + (super.noSuchMethod( + Invocation.method( + #getTxCount, + [], + {#address: address}, + ), + returnValue: _i22.Future.value(0), + ) as _i22.Future); + @override + _i22.Future checkCurrentReceivingAddressesForTransactions() => + (super.noSuchMethod( + Invocation.method( + #checkCurrentReceivingAddressesForTransactions, + [], + ), + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); + @override + _i22.Future checkCurrentChangeAddressesForTransactions() => + (super.noSuchMethod( + Invocation.method( + #checkCurrentChangeAddressesForTransactions, + [], + ), + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override int estimateTxFee({ required int? vSize, @@ -1197,42 +1271,42 @@ class MockBitcoinWallet extends _i1.Mock implements _i18.BitcoinWallet { returnValue: 0, ) as int); @override - dynamic coinSelection( - int? satoshiAmountToSend, - int? selectedTxFeeRate, - String? _recipientAddress, - bool? isSendAll, { + dynamic coinSelection({ + required int? satoshiAmountToSend, + required int? selectedTxFeeRate, + required String? recipientAddress, + required bool? coinControl, + required bool? isSendAll, int? additionalOutputs = 0, - List<_i8.UtxoObject>? utxos, + List<_i17.UTXO>? utxos, }) => super.noSuchMethod(Invocation.method( #coinSelection, - [ - satoshiAmountToSend, - selectedTxFeeRate, - _recipientAddress, - isSendAll, - ], + [], { + #satoshiAmountToSend: satoshiAmountToSend, + #selectedTxFeeRate: selectedTxFeeRate, + #recipientAddress: recipientAddress, + #coinControl: coinControl, + #isSendAll: isSendAll, #additionalOutputs: additionalOutputs, #utxos: utxos, }, )); @override - _i15.Future> fetchBuildTxData( - List<_i8.UtxoObject>? utxosToUse) => + _i22.Future> fetchBuildTxData( + List<_i17.UTXO>? utxosToUse) => (super.noSuchMethod( Invocation.method( #fetchBuildTxData, [utxosToUse], ), returnValue: - _i15.Future>.value({}), - ) as _i15.Future>); + _i22.Future>.value(<_i30.SigningData>[]), + ) as _i22.Future>); @override - _i15.Future> buildTransaction({ - required List<_i8.UtxoObject>? utxosToUse, - required Map? utxoSigningData, + _i22.Future> buildTransaction({ + required List<_i30.SigningData>? utxoSigningData, required List? recipients, required List? satoshiAmounts, }) => @@ -1241,17 +1315,16 @@ class MockBitcoinWallet extends _i1.Mock implements _i18.BitcoinWallet { #buildTransaction, [], { - #utxosToUse: utxosToUse, #utxoSigningData: utxoSigningData, #recipients: recipients, #satoshiAmounts: satoshiAmounts, }, ), returnValue: - _i15.Future>.value({}), - ) as _i15.Future>); + _i22.Future>.value({}), + ) as _i22.Future>); @override - _i15.Future fullRescan( + _i22.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -1263,26 +1336,35 @@ class MockBitcoinWallet extends _i1.Mock implements _i18.BitcoinWallet { maxNumberOfIndexesToCheck, ], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future estimateFeeFor( - int? satoshiAmount, + _i22.Future<_i14.Amount> estimateFeeFor( + _i14.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i15.Future.value(0), - ) as _i15.Future); + returnValue: _i22.Future<_i14.Amount>.value(_FakeAmount_12( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i22.Future<_i14.Amount>); @override - int roughFeeEstimate( + _i14.Amount roughFeeEstimate( int? inputCount, int? outputCount, int? feeRatePerKB, @@ -1296,24 +1378,662 @@ class MockBitcoinWallet extends _i1.Mock implements _i18.BitcoinWallet { feeRatePerKB, ], ), - returnValue: 0, - ) as int); + returnValue: _FakeAmount_12( + this, + Invocation.method( + #roughFeeEstimate, + [ + inputCount, + outputCount, + feeRatePerKB, + ], + ), + ), + ) as _i14.Amount); @override - int sweepAllEstimate(int? feeRate) => (super.noSuchMethod( + _i22.Future<_i14.Amount> sweepAllEstimate(int? feeRate) => + (super.noSuchMethod( Invocation.method( #sweepAllEstimate, [feeRate], ), - returnValue: 0, - ) as int); + returnValue: _i22.Future<_i14.Amount>.value(_FakeAmount_12( + this, + Invocation.method( + #sweepAllEstimate, + [feeRate], + ), + )), + ) as _i22.Future<_i14.Amount>); @override - _i15.Future generateNewAddress() => (super.noSuchMethod( + _i22.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i15.Future.value(false), - ) as _i15.Future); + returnValue: _i22.Future.value(false), + ) as _i22.Future); + @override + void initCache( + String? walletId, + _i21.Coin? coin, + ) => + super.noSuchMethod( + Invocation.method( + #initCache, + [ + walletId, + coin, + ], + ), + returnValueForMissingStub: null, + ); + @override + _i22.Future updateCachedId(String? id) => (super.noSuchMethod( + Invocation.method( + #updateCachedId, + [id], + ), + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); + @override + int getCachedChainHeight() => (super.noSuchMethod( + Invocation.method( + #getCachedChainHeight, + [], + ), + returnValue: 0, + ) as int); + @override + _i22.Future updateCachedChainHeight(int? height) => (super.noSuchMethod( + Invocation.method( + #updateCachedChainHeight, + [height], + ), + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); + @override + bool getCachedIsFavorite() => (super.noSuchMethod( + Invocation.method( + #getCachedIsFavorite, + [], + ), + returnValue: false, + ) as bool); + @override + _i22.Future updateCachedIsFavorite(bool? isFavorite) => + (super.noSuchMethod( + Invocation.method( + #updateCachedIsFavorite, + [isFavorite], + ), + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); + @override + _i12.Balance getCachedBalance() => (super.noSuchMethod( + Invocation.method( + #getCachedBalance, + [], + ), + returnValue: _FakeBalance_9( + this, + Invocation.method( + #getCachedBalance, + [], + ), + ), + ) as _i12.Balance); + @override + _i22.Future updateCachedBalance(_i12.Balance? balance) => + (super.noSuchMethod( + Invocation.method( + #updateCachedBalance, + [balance], + ), + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); + @override + _i12.Balance getCachedBalanceSecondary() => (super.noSuchMethod( + Invocation.method( + #getCachedBalanceSecondary, + [], + ), + returnValue: _FakeBalance_9( + this, + Invocation.method( + #getCachedBalanceSecondary, + [], + ), + ), + ) as _i12.Balance); + @override + _i22.Future updateCachedBalanceSecondary(_i12.Balance? balance) => + (super.noSuchMethod( + Invocation.method( + #updateCachedBalanceSecondary, + [balance], + ), + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); + @override + List getWalletTokenContractAddresses() => (super.noSuchMethod( + Invocation.method( + #getWalletTokenContractAddresses, + [], + ), + returnValue: [], + ) as List); + @override + _i22.Future updateWalletTokenContractAddresses( + List? contractAddresses) => + (super.noSuchMethod( + Invocation.method( + #updateWalletTokenContractAddresses, + [contractAddresses], + ), + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); + @override + void initWalletDB({_i7.MainDB? mockableOverride}) => super.noSuchMethod( + Invocation.method( + #initWalletDB, + [], + {#mockableOverride: mockableOverride}, + ), + returnValueForMissingStub: null, + ); + @override + _i22.Future<_i15.Tuple2<_i17.Transaction, _i17.Address>> parseTransaction( + Map? txData, + dynamic electrumxClient, + List<_i17.Address>? myAddresses, + _i21.Coin? coin, + int? minConfirms, + String? walletId, + ) => + (super.noSuchMethod( + Invocation.method( + #parseTransaction, + [ + txData, + electrumxClient, + myAddresses, + coin, + minConfirms, + walletId, + ], + ), + returnValue: + _i22.Future<_i15.Tuple2<_i17.Transaction, _i17.Address>>.value( + _FakeTuple2_13<_i17.Transaction, _i17.Address>( + this, + Invocation.method( + #parseTransaction, + [ + txData, + electrumxClient, + myAddresses, + coin, + minConfirms, + walletId, + ], + ), + )), + ) as _i22.Future<_i15.Tuple2<_i17.Transaction, _i17.Address>>); + @override + void initPaynymWalletInterface({ + required String? walletId, + required String? walletName, + required _i13.NetworkType? network, + required _i21.Coin? coin, + required _i7.MainDB? db, + required _i10.ElectrumX? electrumXClient, + required _i31.SecureStorageInterface? secureStorage, + required int? dustLimit, + required int? dustLimitP2PKH, + required int? minConfirms, + required _i22.Future Function()? getMnemonicString, + required _i22.Future Function()? getMnemonicPassphrase, + required _i22.Future Function()? getChainHeight, + required _i22.Future Function()? getCurrentChangeAddress, + required int Function({ + required int feeRatePerKB, + required int vSize, + })? + estimateTxFee, + required _i22.Future> Function({ + required String address, + required _i14.Amount amount, + Map? args, + })? + prepareSend, + required _i22.Future Function({required String address})? getTxCount, + required _i22.Future> Function(List<_i17.UTXO>)? + fetchBuildTxData, + required _i22.Future Function()? refresh, + required _i22.Future Function()? checkChangeAddressForTransactions, + }) => + super.noSuchMethod( + Invocation.method( + #initPaynymWalletInterface, + [], + { + #walletId: walletId, + #walletName: walletName, + #network: network, + #coin: coin, + #db: db, + #electrumXClient: electrumXClient, + #secureStorage: secureStorage, + #dustLimit: dustLimit, + #dustLimitP2PKH: dustLimitP2PKH, + #minConfirms: minConfirms, + #getMnemonicString: getMnemonicString, + #getMnemonicPassphrase: getMnemonicPassphrase, + #getChainHeight: getChainHeight, + #getCurrentChangeAddress: getCurrentChangeAddress, + #estimateTxFee: estimateTxFee, + #prepareSend: prepareSend, + #getTxCount: getTxCount, + #fetchBuildTxData: fetchBuildTxData, + #refresh: refresh, + #checkChangeAddressForTransactions: + checkChangeAddressForTransactions, + }, + ), + returnValueForMissingStub: null, + ); + @override + _i22.Future<_i16.BIP32> getBip47BaseNode() => (super.noSuchMethod( + Invocation.method( + #getBip47BaseNode, + [], + ), + returnValue: _i22.Future<_i16.BIP32>.value(_FakeBIP32_14( + this, + Invocation.method( + #getBip47BaseNode, + [], + ), + )), + ) as _i22.Future<_i16.BIP32>); + @override + _i22.Future<_i27.Uint8List> getPrivateKeyForPaynymReceivingAddress({ + required String? paymentCodeString, + required int? index, + }) => + (super.noSuchMethod( + Invocation.method( + #getPrivateKeyForPaynymReceivingAddress, + [], + { + #paymentCodeString: paymentCodeString, + #index: index, + }, + ), + returnValue: _i22.Future<_i27.Uint8List>.value(_i27.Uint8List(0)), + ) as _i22.Future<_i27.Uint8List>); + @override + _i22.Future<_i17.Address> currentReceivingPaynymAddress({ + required _i18.PaymentCode? sender, + required bool? isSegwit, + }) => + (super.noSuchMethod( + Invocation.method( + #currentReceivingPaynymAddress, + [], + { + #sender: sender, + #isSegwit: isSegwit, + }, + ), + returnValue: _i22.Future<_i17.Address>.value(_FakeAddress_15( + this, + Invocation.method( + #currentReceivingPaynymAddress, + [], + { + #sender: sender, + #isSegwit: isSegwit, + }, + ), + )), + ) as _i22.Future<_i17.Address>); + @override + _i22.Future checkCurrentPaynymReceivingAddressForTransactions({ + required _i18.PaymentCode? sender, + required bool? isSegwit, + }) => + (super.noSuchMethod( + Invocation.method( + #checkCurrentPaynymReceivingAddressForTransactions, + [], + { + #sender: sender, + #isSegwit: isSegwit, + }, + ), + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); + @override + _i22.Future checkAllCurrentReceivingPaynymAddressesForTransactions() => + (super.noSuchMethod( + Invocation.method( + #checkAllCurrentReceivingPaynymAddressesForTransactions, + [], + ), + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); + @override + _i22.Future<_i16.BIP32> deriveNotificationBip32Node() => (super.noSuchMethod( + Invocation.method( + #deriveNotificationBip32Node, + [], + ), + returnValue: _i22.Future<_i16.BIP32>.value(_FakeBIP32_14( + this, + Invocation.method( + #deriveNotificationBip32Node, + [], + ), + )), + ) as _i22.Future<_i16.BIP32>); + @override + _i22.Future<_i18.PaymentCode> getPaymentCode({required bool? isSegwit}) => + (super.noSuchMethod( + Invocation.method( + #getPaymentCode, + [], + {#isSegwit: isSegwit}, + ), + returnValue: _i22.Future<_i18.PaymentCode>.value(_FakePaymentCode_16( + this, + Invocation.method( + #getPaymentCode, + [], + {#isSegwit: isSegwit}, + ), + )), + ) as _i22.Future<_i18.PaymentCode>); + @override + _i22.Future<_i27.Uint8List> signWithNotificationKey(_i27.Uint8List? data) => + (super.noSuchMethod( + Invocation.method( + #signWithNotificationKey, + [data], + ), + returnValue: _i22.Future<_i27.Uint8List>.value(_i27.Uint8List(0)), + ) as _i22.Future<_i27.Uint8List>); + @override + _i22.Future signStringWithNotificationKey(String? data) => + (super.noSuchMethod( + Invocation.method( + #signStringWithNotificationKey, + [data], + ), + returnValue: _i22.Future.value(''), + ) as _i22.Future); + @override + _i22.Future> preparePaymentCodeSend({ + required _i18.PaymentCode? paymentCode, + required bool? isSegwit, + required _i14.Amount? amount, + Map? args, + }) => + (super.noSuchMethod( + Invocation.method( + #preparePaymentCodeSend, + [], + { + #paymentCode: paymentCode, + #isSegwit: isSegwit, + #amount: amount, + #args: args, + }, + ), + returnValue: + _i22.Future>.value({}), + ) as _i22.Future>); + @override + _i22.Future<_i17.Address> nextUnusedSendAddressFrom({ + required _i18.PaymentCode? pCode, + required bool? isSegwit, + required _i16.BIP32? privateKeyNode, + int? startIndex = 0, + }) => + (super.noSuchMethod( + Invocation.method( + #nextUnusedSendAddressFrom, + [], + { + #pCode: pCode, + #isSegwit: isSegwit, + #privateKeyNode: privateKeyNode, + #startIndex: startIndex, + }, + ), + returnValue: _i22.Future<_i17.Address>.value(_FakeAddress_15( + this, + Invocation.method( + #nextUnusedSendAddressFrom, + [], + { + #pCode: pCode, + #isSegwit: isSegwit, + #privateKeyNode: privateKeyNode, + #startIndex: startIndex, + }, + ), + )), + ) as _i22.Future<_i17.Address>); + @override + _i22.Future> prepareNotificationTx({ + required int? selectedTxFeeRate, + required String? targetPaymentCodeString, + int? additionalOutputs = 0, + List<_i17.UTXO>? utxos, + }) => + (super.noSuchMethod( + Invocation.method( + #prepareNotificationTx, + [], + { + #selectedTxFeeRate: selectedTxFeeRate, + #targetPaymentCodeString: targetPaymentCodeString, + #additionalOutputs: additionalOutputs, + #utxos: utxos, + }, + ), + returnValue: + _i22.Future>.value({}), + ) as _i22.Future>); + @override + _i22.Future broadcastNotificationTx( + {required Map? preparedTx}) => + (super.noSuchMethod( + Invocation.method( + #broadcastNotificationTx, + [], + {#preparedTx: preparedTx}, + ), + returnValue: _i22.Future.value(''), + ) as _i22.Future); + @override + _i22.Future hasConnected(String? paymentCodeString) => + (super.noSuchMethod( + Invocation.method( + #hasConnected, + [paymentCodeString], + ), + returnValue: _i22.Future.value(false), + ) as _i22.Future); + @override + _i22.Future<_i18.PaymentCode?> unBlindedPaymentCodeFromTransaction( + {required _i17.Transaction? transaction}) => + (super.noSuchMethod( + Invocation.method( + #unBlindedPaymentCodeFromTransaction, + [], + {#transaction: transaction}, + ), + returnValue: _i22.Future<_i18.PaymentCode?>.value(), + ) as _i22.Future<_i18.PaymentCode?>); + @override + _i22.Future<_i18.PaymentCode?> unBlindedPaymentCodeFromTransactionBad( + {required _i17.Transaction? transaction}) => + (super.noSuchMethod( + Invocation.method( + #unBlindedPaymentCodeFromTransactionBad, + [], + {#transaction: transaction}, + ), + returnValue: _i22.Future<_i18.PaymentCode?>.value(), + ) as _i22.Future<_i18.PaymentCode?>); + @override + _i22.Future> + getAllPaymentCodesFromNotificationTransactions() => (super.noSuchMethod( + Invocation.method( + #getAllPaymentCodesFromNotificationTransactions, + [], + ), + returnValue: + _i22.Future>.value(<_i18.PaymentCode>[]), + ) as _i22.Future>); + @override + _i22.Future checkForNotificationTransactionsTo( + Set? otherCodeStrings) => + (super.noSuchMethod( + Invocation.method( + #checkForNotificationTransactionsTo, + [otherCodeStrings], + ), + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); + @override + _i22.Future restoreAllHistory({ + required int? maxUnusedAddressGap, + required int? maxNumberOfIndexesToCheck, + required Set? paymentCodeStrings, + }) => + (super.noSuchMethod( + Invocation.method( + #restoreAllHistory, + [], + { + #maxUnusedAddressGap: maxUnusedAddressGap, + #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + #paymentCodeStrings: paymentCodeStrings, + }, + ), + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); + @override + _i22.Future restoreHistoryWith({ + required _i18.PaymentCode? other, + required bool? checkSegwitAsWell, + required int? maxUnusedAddressGap, + required int? maxNumberOfIndexesToCheck, + }) => + (super.noSuchMethod( + Invocation.method( + #restoreHistoryWith, + [], + { + #other: other, + #checkSegwitAsWell: checkSegwitAsWell, + #maxUnusedAddressGap: maxUnusedAddressGap, + #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + }, + ), + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); + @override + _i22.Future<_i17.Address> getMyNotificationAddress() => (super.noSuchMethod( + Invocation.method( + #getMyNotificationAddress, + [], + ), + returnValue: _i22.Future<_i17.Address>.value(_FakeAddress_15( + this, + Invocation.method( + #getMyNotificationAddress, + [], + ), + )), + ) as _i22.Future<_i17.Address>); + @override + _i22.Future> lookupKey(String? paymentCodeString) => + (super.noSuchMethod( + Invocation.method( + #lookupKey, + [paymentCodeString], + ), + returnValue: _i22.Future>.value([]), + ) as _i22.Future>); + @override + _i22.Future paymentCodeStringByKey(String? key) => + (super.noSuchMethod( + Invocation.method( + #paymentCodeStringByKey, + [key], + ), + returnValue: _i22.Future.value(), + ) as _i22.Future); + @override + _i22.Future storeCode(String? paymentCodeString) => + (super.noSuchMethod( + Invocation.method( + #storeCode, + [paymentCodeString], + ), + returnValue: _i22.Future.value(''), + ) as _i22.Future); + @override + void initCoinControlInterface({ + required String? walletId, + required String? walletName, + required _i21.Coin? coin, + required _i7.MainDB? db, + required _i22.Future Function()? getChainHeight, + required _i22.Future Function(_i12.Balance)? refreshedBalanceCallback, + }) => + super.noSuchMethod( + Invocation.method( + #initCoinControlInterface, + [], + { + #walletId: walletId, + #walletName: walletName, + #coin: coin, + #db: db, + #getChainHeight: getChainHeight, + #refreshedBalanceCallback: refreshedBalanceCallback, + }, + ), + returnValueForMissingStub: null, + ); + @override + _i22.Future refreshBalance({bool? notify = false}) => + (super.noSuchMethod( + Invocation.method( + #refreshBalance, + [], + {#notify: notify}, + ), + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); } /// A class which mocks [Manager]. @@ -1334,23 +2054,23 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValueForMissingStub: null, ); @override - _i12.CoinServiceAPI get wallet => (super.noSuchMethod( + _i19.CoinServiceAPI get wallet => (super.noSuchMethod( Invocation.getter(#wallet), - returnValue: _FakeCoinServiceAPI_12( + returnValue: _FakeCoinServiceAPI_17( this, Invocation.getter(#wallet), ), - ) as _i12.CoinServiceAPI); + ) as _i19.CoinServiceAPI); @override bool get hasBackgroundRefreshListener => (super.noSuchMethod( Invocation.getter(#hasBackgroundRefreshListener), returnValue: false, ) as bool); @override - _i14.Coin get coin => (super.noSuchMethod( + _i21.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i14.Coin.bitcoin, - ) as _i14.Coin); + returnValue: _i21.Coin.bitcoin, + ) as _i21.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -1383,91 +2103,42 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValueForMissingStub: null, ); @override - _i15.Future<_i8.FeeObject> get fees => (super.noSuchMethod( + _i22.Future<_i9.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i15.Future<_i8.FeeObject>.value(_FakeFeeObject_7( + returnValue: _i22.Future<_i9.FeeObject>.value(_FakeFeeObject_6( this, Invocation.getter(#fees), )), - ) as _i15.Future<_i8.FeeObject>); + ) as _i22.Future<_i9.FeeObject>); @override - _i15.Future get maxFee => (super.noSuchMethod( + _i22.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i15.Future.value(0), - ) as _i15.Future); + returnValue: _i22.Future.value(0), + ) as _i22.Future); @override - _i15.Future get currentReceivingAddress => (super.noSuchMethod( + _i22.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i15.Future.value(''), - ) as _i15.Future); + returnValue: _i22.Future.value(''), + ) as _i22.Future); @override - _i15.Future<_i9.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i15.Future<_i9.Decimal>.value(_FakeDecimal_6( + _i12.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_9( this, - Invocation.getter(#availableBalance), - )), - ) as _i15.Future<_i9.Decimal>); - @override - _i9.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_6( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i9.Decimal); + ) as _i12.Balance); @override - _i15.Future<_i9.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i15.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i15.Future<_i9.Decimal>); - @override - _i15.Future<_i9.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i15.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i15.Future<_i9.Decimal>); - @override - _i15.Future<_i9.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i15.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#totalBalance), - )), - ) as _i15.Future<_i9.Decimal>); - @override - _i9.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_6( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i9.Decimal); - @override - _i15.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i15.Future>.value([]), - ) as _i15.Future>); - @override - _i15.Future<_i8.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i22.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i15.Future<_i8.TransactionData>.value(_FakeTransactionData_8( - this, - Invocation.getter(#transactionData), - )), - ) as _i15.Future<_i8.TransactionData>); + _i22.Future>.value(<_i17.Transaction>[]), + ) as _i22.Future>); @override - _i15.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: - _i15.Future>.value(<_i8.UtxoObject>[]), - ) as _i15.Future>); + _i22.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i22.Future>.value(<_i17.UTXO>[]), + ) as _i22.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -1487,29 +2158,74 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValue: '', ) as String); @override - _i15.Future> get mnemonic => (super.noSuchMethod( + _i22.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i15.Future>.value([]), - ) as _i15.Future>); + returnValue: _i22.Future>.value([]), + ) as _i22.Future>); + @override + _i22.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i22.Future.value(), + ) as _i22.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i22.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i22.Future.value(''), + ) as _i22.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i15.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i22.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -1519,9 +2235,9 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValueForMissingStub: null, ); @override - _i15.Future> prepareSend({ + _i22.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i14.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -1530,50 +2246,32 @@ class MockManager extends _i1.Mock implements _i6.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i15.Future>.value({}), - ) as _i15.Future>); + _i22.Future>.value({}), + ) as _i22.Future>); @override - _i15.Future confirmSend({required Map? txData}) => + _i22.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i15.Future.value(''), - ) as _i15.Future); + returnValue: _i22.Future.value(''), + ) as _i22.Future); @override - _i15.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i15.Future.value(''), - ) as _i15.Future); - @override - _i15.Future refresh() => (super.noSuchMethod( + _i22.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -1583,34 +2281,35 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValue: false, ) as bool); @override - _i15.Future testNetworkConnection() => (super.noSuchMethod( + _i22.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i15.Future.value(false), - ) as _i15.Future); + returnValue: _i22.Future.value(false), + ) as _i22.Future); @override - _i15.Future initializeNew() => (super.noSuchMethod( + _i22.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future initializeExisting() => (super.noSuchMethod( + _i22.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future recoverFromMnemonic({ + _i22.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -1621,25 +2320,26 @@ class MockManager extends _i1.Mock implements _i6.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future exitCurrentWallet() => (super.noSuchMethod( + _i22.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future fullRescan( + _i22.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -1651,42 +2351,52 @@ class MockManager extends _i1.Mock implements _i6.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i15.Future.value(false), - ) as _i15.Future); - @override - _i15.Future estimateFeeFor( - int? satoshiAmount, + _i22.Future<_i14.Amount> estimateFeeFor( + _i14.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i15.Future.value(0), - ) as _i15.Future); + returnValue: _i22.Future<_i14.Amount>.value(_FakeAmount_12( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i22.Future<_i14.Amount>); @override - _i15.Future generateNewAddress() => (super.noSuchMethod( + _i22.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i15.Future.value(false), - ) as _i15.Future); + returnValue: _i22.Future.value(false), + ) as _i22.Future); @override - void addListener(_i17.VoidCallback? listener) => super.noSuchMethod( + _i22.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); + @override + void addListener(_i24.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -1694,7 +2404,7 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i17.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i24.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -1714,7 +2424,7 @@ class MockManager extends _i1.Mock implements _i6.Manager { /// A class which mocks [CoinServiceAPI]. /// /// See the documentation for Mockito's code generation for more information. -class MockCoinServiceAPI extends _i1.Mock implements _i12.CoinServiceAPI { +class MockCoinServiceAPI extends _i1.Mock implements _i19.CoinServiceAPI { @override set onIsActiveWalletChanged(void Function(bool)? _onIsActiveWalletChanged) => super.noSuchMethod( @@ -1725,10 +2435,10 @@ class MockCoinServiceAPI extends _i1.Mock implements _i12.CoinServiceAPI { returnValueForMissingStub: null, ); @override - _i14.Coin get coin => (super.noSuchMethod( + _i21.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i14.Coin.bitcoin, - ) as _i14.Coin); + returnValue: _i21.Coin.bitcoin, + ) as _i21.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -1761,75 +2471,42 @@ class MockCoinServiceAPI extends _i1.Mock implements _i12.CoinServiceAPI { returnValueForMissingStub: null, ); @override - _i15.Future<_i8.FeeObject> get fees => (super.noSuchMethod( + _i22.Future<_i9.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i15.Future<_i8.FeeObject>.value(_FakeFeeObject_7( + returnValue: _i22.Future<_i9.FeeObject>.value(_FakeFeeObject_6( this, Invocation.getter(#fees), )), - ) as _i15.Future<_i8.FeeObject>); + ) as _i22.Future<_i9.FeeObject>); @override - _i15.Future get maxFee => (super.noSuchMethod( + _i22.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i15.Future.value(0), - ) as _i15.Future); + returnValue: _i22.Future.value(0), + ) as _i22.Future); @override - _i15.Future get currentReceivingAddress => (super.noSuchMethod( + _i22.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i15.Future.value(''), - ) as _i15.Future); + returnValue: _i22.Future.value(''), + ) as _i22.Future); @override - _i15.Future<_i9.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i15.Future<_i9.Decimal>.value(_FakeDecimal_6( + _i12.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_9( this, - Invocation.getter(#availableBalance), - )), - ) as _i15.Future<_i9.Decimal>); + Invocation.getter(#balance), + ), + ) as _i12.Balance); @override - _i15.Future<_i9.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i15.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i15.Future<_i9.Decimal>); - @override - _i15.Future<_i9.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i15.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#totalBalance), - )), - ) as _i15.Future<_i9.Decimal>); - @override - _i15.Future<_i9.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i15.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i15.Future<_i9.Decimal>); - @override - _i15.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i15.Future>.value([]), - ) as _i15.Future>); - @override - _i15.Future<_i8.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i22.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i15.Future<_i8.TransactionData>.value(_FakeTransactionData_8( - this, - Invocation.getter(#transactionData), - )), - ) as _i15.Future<_i8.TransactionData>); + _i22.Future>.value(<_i17.Transaction>[]), + ) as _i22.Future>); @override - _i15.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: - _i15.Future>.value(<_i8.UtxoObject>[]), - ) as _i15.Future>); + _i22.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i22.Future>.value(<_i17.UTXO>[]), + ) as _i22.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -1849,10 +2526,20 @@ class MockCoinServiceAPI extends _i1.Mock implements _i12.CoinServiceAPI { returnValue: '', ) as String); @override - _i15.Future> get mnemonic => (super.noSuchMethod( + _i22.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i15.Future>.value([]), - ) as _i15.Future>); + returnValue: _i22.Future>.value([]), + ) as _i22.Future>); + @override + _i22.Future get mnemonicString => (super.noSuchMethod( + Invocation.getter(#mnemonicString), + returnValue: _i22.Future.value(), + ) as _i22.Future); + @override + _i22.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i22.Future.value(), + ) as _i22.Future); @override bool get hasCalledExit => (super.noSuchMethod( Invocation.getter(#hasCalledExit), @@ -1864,9 +2551,14 @@ class MockCoinServiceAPI extends _i1.Mock implements _i12.CoinServiceAPI { returnValue: false, ) as bool); @override - _i15.Future> prepareSend({ + int get storedChainHeight => (super.noSuchMethod( + Invocation.getter(#storedChainHeight), + returnValue: 0, + ) as int); + @override + _i22.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i14.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -1875,59 +2567,41 @@ class MockCoinServiceAPI extends _i1.Mock implements _i12.CoinServiceAPI { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i15.Future>.value({}), - ) as _i15.Future>); + _i22.Future>.value({}), + ) as _i22.Future>); @override - _i15.Future confirmSend({required Map? txData}) => + _i22.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i15.Future.value(''), - ) as _i15.Future); + returnValue: _i22.Future.value(''), + ) as _i22.Future); @override - _i15.Future send({ - required String? toAddress, - required int? amount, - Map? args, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i15.Future.value(''), - ) as _i15.Future); - @override - _i15.Future refresh() => (super.noSuchMethod( + _i22.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i22.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -1937,16 +2611,17 @@ class MockCoinServiceAPI extends _i1.Mock implements _i12.CoinServiceAPI { returnValue: false, ) as bool); @override - _i15.Future testNetworkConnection() => (super.noSuchMethod( + _i22.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i15.Future.value(false), - ) as _i15.Future); + returnValue: _i22.Future.value(false), + ) as _i22.Future); @override - _i15.Future recoverFromMnemonic({ + _i22.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -1957,43 +2632,44 @@ class MockCoinServiceAPI extends _i1.Mock implements _i12.CoinServiceAPI { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future initializeNew() => (super.noSuchMethod( + _i22.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future initializeExisting() => (super.noSuchMethod( + _i22.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future exit() => (super.noSuchMethod( + _i22.Future exit() => (super.noSuchMethod( Invocation.method( #exit, [], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future fullRescan( + _i22.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -2005,40 +2681,49 @@ class MockCoinServiceAPI extends _i1.Mock implements _i12.CoinServiceAPI { maxNumberOfIndexesToCheck, ], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); @override - _i15.Future estimateFeeFor( - int? satoshiAmount, + _i22.Future<_i14.Amount> estimateFeeFor( + _i14.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i15.Future.value(0), - ) as _i15.Future); + returnValue: _i22.Future<_i14.Amount>.value(_FakeAmount_12( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i22.Future<_i14.Amount>); @override - _i15.Future generateNewAddress() => (super.noSuchMethod( + _i22.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i15.Future.value(false), - ) as _i15.Future); + returnValue: _i22.Future.value(false), + ) as _i22.Future); @override - _i15.Future updateSentCachedTxData(Map? txData) => + _i22.Future updateSentCachedTxData(Map? txData) => (super.noSuchMethod( Invocation.method( #updateSentCachedTxData, [txData], ), - returnValue: _i15.Future.value(), - returnValueForMissingStub: _i15.Future.value(), - ) as _i15.Future); + returnValue: _i22.Future.value(), + returnValueForMissingStub: _i22.Future.value(), + ) as _i22.Future); } diff --git a/test/widget_tests/table_view/table_view_test.dart b/test/widget_tests/table_view/table_view_test.dart index 2c33858d8..a52986985 100644 --- a/test/widget_tests/table_view/table_view_test.dart +++ b/test/widget_tests/table_view/table_view_test.dart @@ -1,18 +1,25 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/widgets/table_view/table_view.dart'; import 'package:stackwallet/widgets/table_view/table_view_cell.dart'; import 'package:stackwallet/widgets/table_view/table_view_row.dart'; +import '../../sample_data/theme_json.dart'; + void main() { testWidgets("Test create table row widget ", (widgetTester) async { await widgetTester.pumpWidget( MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: Material( diff --git a/test/widget_tests/textfield_icon_button_test.dart b/test/widget_tests/textfield_icon_button_test.dart index ee628064e..43be6ce11 100644 --- a/test/widget_tests/textfield_icon_button_test.dart +++ b/test/widget_tests/textfield_icon_button_test.dart @@ -1,17 +1,24 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; import 'package:stackwallet/widgets/icon_widgets/x_icon.dart'; import 'package:stackwallet/widgets/textfield_icon_button.dart'; +import '../sample_data/theme_json.dart'; + void main() { testWidgets("test", (widgetTester) async { await widgetTester.pumpWidget( MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: const Material( diff --git a/test/widget_tests/trade_card_test.dart b/test/widget_tests/trade_card_test.dart index b21881781..6e5a81288 100644 --- a/test/widget_tests/trade_card_test.dart +++ b/test/widget_tests/trade_card_test.dart @@ -1,13 +1,29 @@ import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; import 'package:stackwallet/models/exchange/response_objects/trade.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_service.dart'; import 'package:stackwallet/widgets/trade_card.dart'; +import '../sample_data/theme_json.dart'; +import 'trade_card_test.mocks.dart'; + +@GenerateMocks([ + ThemeService, +]) void main() { testWidgets("Test Trade card builds", (widgetTester) async { + final mockThemeService = MockThemeService(); + when(mockThemeService.getTheme(themeId: "light")).thenAnswer( + (_) => StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ); final trade = Trade( uuid: "uuid", tradeId: "trade id", @@ -34,12 +50,17 @@ void main() { await widgetTester.pumpWidget( ProviderScope( - overrides: [], + overrides: [ + pThemeService.overrideWithValue(mockThemeService), + ], child: MaterialApp( theme: ThemeData( extensions: [ StackColors.fromStackColorTheme( - LightColors(), + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test/sample_data", + ), ), ], ), diff --git a/test/widget_tests/trade_card_test.mocks.dart b/test/widget_tests/trade_card_test.mocks.dart new file mode 100644 index 000000000..b4092378c --- /dev/null +++ b/test/widget_tests/trade_card_test.mocks.dart @@ -0,0 +1,122 @@ +// Mocks generated by Mockito 5.3.2 from annotations +// in stackwallet/test/widget_tests/trade_card_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i5; +import 'dart:typed_data' as _i6; + +import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/db/isar/main_db.dart' as _i2; +import 'package:stackwallet/models/isar/stack_theme.dart' as _i4; +import 'package:stackwallet/themes/theme_service.dart' as _i3; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeMainDB_0 extends _i1.SmartFake implements _i2.MainDB { + _FakeMainDB_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [ThemeService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockThemeService extends _i1.Mock implements _i3.ThemeService { + MockThemeService() { + _i1.throwOnMissingStub(this); + } + + @override + _i2.MainDB get db => (super.noSuchMethod( + Invocation.getter(#db), + returnValue: _FakeMainDB_0( + this, + Invocation.getter(#db), + ), + ) as _i2.MainDB); + @override + List<_i4.StackTheme> get installedThemes => (super.noSuchMethod( + Invocation.getter(#installedThemes), + returnValue: <_i4.StackTheme>[], + ) as List<_i4.StackTheme>); + @override + void init(_i2.MainDB? db) => super.noSuchMethod( + Invocation.method( + #init, + [db], + ), + returnValueForMissingStub: null, + ); + @override + _i5.Future install({required _i6.Uint8List? themeArchiveData}) => + (super.noSuchMethod( + Invocation.method( + #install, + [], + {#themeArchiveData: themeArchiveData}, + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future remove({required String? themeId}) => (super.noSuchMethod( + Invocation.method( + #remove, + [], + {#themeId: themeId}, + ), + returnValue: _i5.Future.value(), + returnValueForMissingStub: _i5.Future.value(), + ) as _i5.Future); + @override + _i5.Future verifyInstalled({required String? themeId}) => + (super.noSuchMethod( + Invocation.method( + #verifyInstalled, + [], + {#themeId: themeId}, + ), + returnValue: _i5.Future.value(false), + ) as _i5.Future); + @override + _i5.Future> fetchThemes() => (super.noSuchMethod( + Invocation.method( + #fetchThemes, + [], + ), + returnValue: _i5.Future>.value( + <_i3.StackThemeMetaData>[]), + ) as _i5.Future>); + @override + _i5.Future<_i6.Uint8List> fetchTheme( + {required _i3.StackThemeMetaData? themeMetaData}) => + (super.noSuchMethod( + Invocation.method( + #fetchTheme, + [], + {#themeMetaData: themeMetaData}, + ), + returnValue: _i5.Future<_i6.Uint8List>.value(_i6.Uint8List(0)), + ) as _i5.Future<_i6.Uint8List>); + @override + _i4.StackTheme? getTheme({required String? themeId}) => + (super.noSuchMethod(Invocation.method( + #getTheme, + [], + {#themeId: themeId}, + )) as _i4.StackTheme?); +} diff --git a/test/widget_tests/transaction_card_test.dart b/test/widget_tests/transaction_card_test.dart index f28c5f81d..fcad532fa 100644 --- a/test/widget_tests/transaction_card_test.dart +++ b/test/widget_tests/transaction_card_test.dart @@ -6,7 +6,9 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:mockingjay/mockingjay.dart' as mockingjay; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; -import 'package:stackwallet/models/models.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/address.dart'; +import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; import 'package:stackwallet/pages/wallet_view/transaction_views/transaction_details_view.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/coins/coin_service.dart'; @@ -16,14 +18,16 @@ import 'package:stackwallet/services/locale_service.dart'; import 'package:stackwallet/services/notes_service.dart'; import 'package:stackwallet/services/price_service.dart'; import 'package:stackwallet/services/wallets.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_service.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/prefs.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/utilities/util.dart'; import 'package:stackwallet/widgets/transaction_card.dart'; import 'package:tuple/tuple.dart'; +import '../sample_data/theme_json.dart'; import 'transaction_card_test.mocks.dart'; @GenerateMocks([ @@ -34,7 +38,8 @@ import 'transaction_card_test.mocks.dart'; LocaleService, Prefs, PriceService, - NotesService + NotesService, + ThemeService, ], customMocks: []) void main() { TestWidgetsFlutterBinding.ensureInitialized(); @@ -44,29 +49,45 @@ void main() { final wallets = MockWallets(); final mockPrefs = MockPrefs(); final mockPriceService = MockPriceService(); + final mockThemeService = MockThemeService(); final tx = Transaction( - txid: "some txid", - confirmedStatus: true, - timestamp: 1648595998, - txType: "Sent", - amount: 100000000, - aliens: [], - worthNow: "0.01", - worthAtBlockTimestamp: "0.01", - fees: 3794, - inputSize: 1, - outputSize: 1, - inputs: [], - outputs: [], - address: "", - height: 450123, - subType: "", - confirmations: 10, - isCancelled: false); + txid: "some txid", + timestamp: 1648595998, + type: TransactionType.outgoing, + amount: 100000000, + amountString: Amount( + rawValue: BigInt.from(100000000), + fractionDigits: Coin.firo.decimals, + ).toJsonString(), + fee: 3794, + height: 450123, + subType: TransactionSubType.none, + isCancelled: false, + walletId: '', + isLelantus: null, + slateId: '', + otherData: '', + nonce: null, + inputs: [], + outputs: [], + )..address.value = Address( + walletId: "walletId", + value: "", + publicKey: [], + derivationIndex: 0, + derivationPath: null, + type: AddressType.p2pkh, + subType: AddressSubType.receiving); final CoinServiceAPI wallet = MockFiroWallet(); + when(mockThemeService.getTheme(themeId: "light")).thenAnswer( + (_) => StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ); when(wallet.coin.ticker).thenAnswer((_) => "FIRO"); when(mockLocaleService.locale).thenAnswer((_) => "en_US"); when(mockPrefs.currency).thenAnswer((_) => "USD"); @@ -78,6 +99,8 @@ void main() { when(wallets.getManager("wallet-id")) .thenAnswer((realInvocation) => Manager(wallet)); + + when(wallet.storedChainHeight).thenAnswer((_) => 6000000); // await tester.pumpWidget( ProviderScope( @@ -85,6 +108,7 @@ void main() { walletsChangeNotifierProvider.overrideWithValue(wallets), localeServiceChangeNotifierProvider .overrideWithValue(mockLocaleService), + pThemeService.overrideWithValue(mockThemeService), prefsChangeNotifierProvider.overrideWithValue(mockPrefs), priceAnd24hChangeNotifierProvider.overrideWithValue(mockPriceService) ], @@ -92,7 +116,10 @@ void main() { theme: ThemeData( extensions: [ StackColors.fromStackColorTheme( - LightColors(), + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), ), ], ), @@ -139,29 +166,45 @@ void main() { final wallets = MockWallets(); final mockPrefs = MockPrefs(); final mockPriceService = MockPriceService(); + final mockThemeService = MockThemeService(); final tx = Transaction( - txid: "some txid", - confirmedStatus: true, - timestamp: 1648595998, - txType: "Anonymized", - amount: 100000000, - aliens: [], - worthNow: "0.01", - worthAtBlockTimestamp: "0.01", - fees: 3794, - inputSize: 1, - outputSize: 1, - inputs: [], - outputs: [], - address: "", - height: 450123, - subType: "mint", - confirmations: 10, - isCancelled: false); + txid: "some txid", + timestamp: 1648595998, + type: TransactionType.outgoing, + amount: 9659, + amountString: Amount( + rawValue: BigInt.from(9659), + fractionDigits: Coin.firo.decimals, + ).toJsonString(), + fee: 3794, + height: 450123, + subType: TransactionSubType.mint, + isCancelled: false, + walletId: '', + isLelantus: null, + slateId: '', + otherData: '', + nonce: null, + inputs: [], + outputs: [], + )..address.value = Address( + walletId: "walletId", + value: "", + publicKey: [], + derivationIndex: 0, + derivationPath: null, + type: AddressType.p2pkh, + subType: AddressSubType.receiving); final CoinServiceAPI wallet = MockFiroWallet(); + when(mockThemeService.getTheme(themeId: "light")).thenAnswer( + (_) => StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ); when(wallet.coin.ticker).thenAnswer((_) => "FIRO"); when(mockLocaleService.locale).thenAnswer((_) => "en_US"); when(mockPrefs.currency).thenAnswer((_) => "USD"); @@ -170,6 +213,7 @@ void main() { .thenAnswer((realInvocation) => Tuple2(Decimal.ten, 0.00)); when(wallet.coin).thenAnswer((_) => Coin.firo); + when(wallet.storedChainHeight).thenAnswer((_) => 6000000); when(wallets.getManager("wallet-id")) .thenAnswer((realInvocation) => Manager(wallet)); @@ -181,13 +225,17 @@ void main() { localeServiceChangeNotifierProvider .overrideWithValue(mockLocaleService), prefsChangeNotifierProvider.overrideWithValue(mockPrefs), + pThemeService.overrideWithValue(mockThemeService), priceAnd24hChangeNotifierProvider.overrideWithValue(mockPriceService) ], child: MaterialApp( theme: ThemeData( extensions: [ StackColors.fromStackColorTheme( - LightColors(), + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), ), ], ), @@ -199,7 +247,7 @@ void main() { // final title = find.text("Anonymized"); // final price1 = find.text("0.00 USD"); - final amount = find.text("1.00000000 FIRO"); + final amount = find.text("-0.00009659 FIRO"); final icon = find.byIcon(FeatherIcons.arrowUp); @@ -232,29 +280,45 @@ void main() { final wallets = MockWallets(); final mockPrefs = MockPrefs(); final mockPriceService = MockPriceService(); + final mockThemeService = MockThemeService(); final tx = Transaction( txid: "some txid", - confirmedStatus: false, timestamp: 1648595998, - txType: "Received", + type: TransactionType.incoming, amount: 100000000, - aliens: [], - worthNow: "0.01", - worthAtBlockTimestamp: "0.01", - fees: 3794, - inputSize: 1, - outputSize: 1, + amountString: Amount( + rawValue: BigInt.from(100000000), + fractionDigits: Coin.firo.decimals, + ).toJsonString(), + fee: 3794, + height: 450123, + subType: TransactionSubType.none, + isCancelled: false, + walletId: '', + isLelantus: null, + slateId: '', + otherData: '', + nonce: null, inputs: [], outputs: [], - address: "", - height: 0, - subType: "", - confirmations: 0, - ); + )..address.value = Address( + walletId: "walletId", + value: "", + publicKey: [], + derivationIndex: 0, + derivationPath: null, + type: AddressType.p2pkh, + subType: AddressSubType.receiving); final CoinServiceAPI wallet = MockFiroWallet(); + when(mockThemeService.getTheme(themeId: "light")).thenAnswer( + (_) => StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ); when(wallet.coin.ticker).thenAnswer((_) => "FIRO"); when(mockLocaleService.locale).thenAnswer((_) => "en_US"); when(mockPrefs.currency).thenAnswer((_) => "USD"); @@ -267,6 +331,8 @@ void main() { when(wallets.getManager("wallet-id")) .thenAnswer((realInvocation) => Manager(wallet)); + when(wallet.storedChainHeight).thenAnswer((_) => 6000000); + await tester.pumpWidget( ProviderScope( overrides: [ @@ -274,13 +340,17 @@ void main() { localeServiceChangeNotifierProvider .overrideWithValue(mockLocaleService), prefsChangeNotifierProvider.overrideWithValue(mockPrefs), + pThemeService.overrideWithValue(mockThemeService), priceAnd24hChangeNotifierProvider.overrideWithValue(mockPriceService) ], child: MaterialApp( theme: ThemeData( extensions: [ StackColors.fromStackColorTheme( - LightColors(), + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), ), ], ), @@ -289,7 +359,7 @@ void main() { ), ); - final title = find.text("Receiving"); + final title = find.text("Received"); final amount = Util.isDesktop ? find.text("+1.00000000 FIRO") : find.text("1.00000000 FIRO"); @@ -317,30 +387,46 @@ void main() { final wallets = MockWallets(); final mockPrefs = MockPrefs(); final mockPriceService = MockPriceService(); + final mockThemeService = MockThemeService(); final navigator = mockingjay.MockNavigator(); final tx = Transaction( txid: "some txid", - confirmedStatus: false, timestamp: 1648595998, - txType: "Received", + type: TransactionType.outgoing, amount: 100000000, - aliens: [], - worthNow: "0.01", - worthAtBlockTimestamp: "0.01", - fees: 3794, - inputSize: 1, - outputSize: 1, + amountString: Amount( + rawValue: BigInt.from(100000000), + fractionDigits: Coin.firo.decimals, + ).toJsonString(), + fee: 3794, + height: 450123, + subType: TransactionSubType.none, + isCancelled: false, + walletId: '', + isLelantus: null, + slateId: '', + otherData: '', + nonce: null, inputs: [], outputs: [], - address: "", - height: 250, - subType: "", - confirmations: 10, - ); + )..address.value = Address( + walletId: "walletId", + value: "", + publicKey: [], + derivationIndex: 0, + derivationPath: null, + type: AddressType.p2pkh, + subType: AddressSubType.receiving); final CoinServiceAPI wallet = MockFiroWallet(); + when(mockThemeService.getTheme(themeId: "light")).thenAnswer( + (_) => StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ); when(wallet.coin.ticker).thenAnswer((_) => "FIRO"); when(mockLocaleService.locale).thenAnswer((_) => "en_US"); when(mockPrefs.currency).thenAnswer((_) => "USD"); @@ -353,6 +439,8 @@ void main() { when(wallets.getManager("wallet id")) .thenAnswer((realInvocation) => Manager(wallet)); + when(wallet.storedChainHeight).thenAnswer((_) => 6000000); + mockingjay .when(() => navigator.pushNamed("/transactionDetails", arguments: Tuple3(tx, Coin.firo, "wallet id"))) @@ -365,12 +453,18 @@ void main() { localeServiceChangeNotifierProvider .overrideWithValue(mockLocaleService), prefsChangeNotifierProvider.overrideWithValue(mockPrefs), + pThemeService.overrideWithValue(mockThemeService), priceAnd24hChangeNotifierProvider.overrideWithValue(mockPriceService) ], child: MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: mockingjay.MockNavigatorProvider( @@ -390,6 +484,7 @@ void main() { verify(mockPrefs.currency).called(2); verify(mockLocaleService.locale).called(4); verify(wallet.coin.ticker).called(1); + verify(wallet.storedChainHeight).called(2); verifyNoMoreInteractions(wallet); verifyNoMoreInteractions(mockLocaleService); diff --git a/test/widget_tests/transaction_card_test.mocks.dart b/test/widget_tests/transaction_card_test.mocks.dart index 2aa2e1dcb..67081a304 100644 --- a/test/widget_tests/transaction_card_test.mocks.dart +++ b/test/widget_tests/transaction_card_test.mocks.dart @@ -3,34 +3,40 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i16; -import 'dart:ui' as _i18; +import 'dart:async' as _i18; +import 'dart:typed_data' as _i32; +import 'dart:ui' as _i20; -import 'package:decimal/decimal.dart' as _i9; +import 'package:decimal/decimal.dart' as _i28; import 'package:flutter/foundation.dart' as _i4; import 'package:flutter_riverpod/flutter_riverpod.dart' as _i5; import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i12; -import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i11; +import 'package:stackwallet/db/isar/main_db.dart' as _i14; +import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i13; +import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i12; +import 'package:stackwallet/models/balance.dart' as _i9; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i21; +import 'package:stackwallet/models/isar/stack_theme.dart' as _i31; import 'package:stackwallet/models/models.dart' as _i8; -import 'package:stackwallet/pages/exchange_view/sub_widgets/exchange_rate_sheet.dart' - as _i22; +import 'package:stackwallet/models/signing_data.dart' as _i23; import 'package:stackwallet/services/coins/coin_service.dart' as _i7; -import 'package:stackwallet/services/coins/firo/firo_wallet.dart' as _i19; +import 'package:stackwallet/services/coins/firo/firo_wallet.dart' as _i22; import 'package:stackwallet/services/coins/manager.dart' as _i6; -import 'package:stackwallet/services/locale_service.dart' as _i20; +import 'package:stackwallet/services/locale_service.dart' as _i24; import 'package:stackwallet/services/node_service.dart' as _i3; -import 'package:stackwallet/services/notes_service.dart' as _i25; -import 'package:stackwallet/services/price_service.dart' as _i24; +import 'package:stackwallet/services/notes_service.dart' as _i29; +import 'package:stackwallet/services/price_service.dart' as _i27; import 'package:stackwallet/services/transaction_notification_tracker.dart' - as _i10; -import 'package:stackwallet/services/wallets.dart' as _i14; + as _i11; +import 'package:stackwallet/services/wallets.dart' as _i16; import 'package:stackwallet/services/wallets_service.dart' as _i2; -import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i23; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i15; -import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i21; -import 'package:stackwallet/utilities/prefs.dart' as _i17; -import 'package:tuple/tuple.dart' as _i13; +import 'package:stackwallet/themes/theme_service.dart' as _i30; +import 'package:stackwallet/utilities/amount/amount.dart' as _i10; +import 'package:stackwallet/utilities/enums/backup_frequency_type.dart' as _i26; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i17; +import 'package:stackwallet/utilities/enums/sync_type_enum.dart' as _i25; +import 'package:stackwallet/utilities/prefs.dart' as _i19; +import 'package:tuple/tuple.dart' as _i15; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -106,8 +112,8 @@ class _FakeFeeObject_5 extends _i1.SmartFake implements _i8.FeeObject { ); } -class _FakeDecimal_6 extends _i1.SmartFake implements _i9.Decimal { - _FakeDecimal_6( +class _FakeBalance_6 extends _i1.SmartFake implements _i9.Balance { + _FakeBalance_6( Object parent, Invocation parentInvocation, ) : super( @@ -116,9 +122,8 @@ class _FakeDecimal_6 extends _i1.SmartFake implements _i9.Decimal { ); } -class _FakeTransactionData_7 extends _i1.SmartFake - implements _i8.TransactionData { - _FakeTransactionData_7( +class _FakeAmount_7 extends _i1.SmartFake implements _i10.Amount { + _FakeAmount_7( Object parent, Invocation parentInvocation, ) : super( @@ -128,7 +133,7 @@ class _FakeTransactionData_7 extends _i1.SmartFake } class _FakeTransactionNotificationTracker_8 extends _i1.SmartFake - implements _i10.TransactionNotificationTracker { + implements _i11.TransactionNotificationTracker { _FakeTransactionNotificationTracker_8( Object parent, Invocation parentInvocation, @@ -138,8 +143,8 @@ class _FakeTransactionNotificationTracker_8 extends _i1.SmartFake ); } -class _FakeUtxoData_9 extends _i1.SmartFake implements _i8.UtxoData { - _FakeUtxoData_9( +class _FakeElectrumX_9 extends _i1.SmartFake implements _i12.ElectrumX { + _FakeElectrumX_9( Object parent, Invocation parentInvocation, ) : super( @@ -148,8 +153,9 @@ class _FakeUtxoData_9 extends _i1.SmartFake implements _i8.UtxoData { ); } -class _FakeElectrumX_10 extends _i1.SmartFake implements _i11.ElectrumX { - _FakeElectrumX_10( +class _FakeCachedElectrumX_10 extends _i1.SmartFake + implements _i13.CachedElectrumX { + _FakeCachedElectrumX_10( Object parent, Invocation parentInvocation, ) : super( @@ -158,9 +164,8 @@ class _FakeElectrumX_10 extends _i1.SmartFake implements _i11.ElectrumX { ); } -class _FakeCachedElectrumX_11 extends _i1.SmartFake - implements _i12.CachedElectrumX { - _FakeCachedElectrumX_11( +class _FakeMainDB_11 extends _i1.SmartFake implements _i14.MainDB { + _FakeMainDB_11( Object parent, Invocation parentInvocation, ) : super( @@ -180,7 +185,7 @@ class _FakeDuration_12 extends _i1.SmartFake implements Duration { } class _FakeTuple2_13 extends _i1.SmartFake - implements _i13.Tuple2 { + implements _i15.Tuple2 { _FakeTuple2_13( Object parent, Invocation parentInvocation, @@ -193,7 +198,7 @@ class _FakeTuple2_13 extends _i1.SmartFake /// A class which mocks [Wallets]. /// /// See the documentation for Mockito's code generation for more information. -class MockWallets extends _i1.Mock implements _i14.Wallets { +class MockWallets extends _i1.Mock implements _i16.Wallets { MockWallets() { _i1.throwOnMissingStub(this); } @@ -260,7 +265,7 @@ class MockWallets extends _i1.Mock implements _i14.Wallets { returnValueForMissingStub: null, ); @override - List getWalletIdsFor({required _i15.Coin? coin}) => + List getWalletIdsFor({required _i17.Coin? coin}) => (super.noSuchMethod( Invocation.method( #getWalletIdsFor, @@ -270,15 +275,28 @@ class MockWallets extends _i1.Mock implements _i14.Wallets { returnValue: [], ) as List); @override - Map<_i15.Coin, List<_i5.ChangeNotifierProvider<_i6.Manager>>> + List<_i15.Tuple2<_i17.Coin, List<_i5.ChangeNotifierProvider<_i6.Manager>>>> getManagerProvidersByCoin() => (super.noSuchMethod( Invocation.method( #getManagerProvidersByCoin, [], ), - returnValue: <_i15.Coin, - List<_i5.ChangeNotifierProvider<_i6.Manager>>>{}, - ) as Map<_i15.Coin, List<_i5.ChangeNotifierProvider<_i6.Manager>>>); + returnValue: < + _i15.Tuple2<_i17.Coin, + List<_i5.ChangeNotifierProvider<_i6.Manager>>>>[], + ) as List< + _i15.Tuple2<_i17.Coin, + List<_i5.ChangeNotifierProvider<_i6.Manager>>>>); + @override + List<_i5.ChangeNotifierProvider<_i6.Manager>> getManagerProvidersForCoin( + _i17.Coin? coin) => + (super.noSuchMethod( + Invocation.method( + #getManagerProvidersForCoin, + [coin], + ), + returnValue: <_i5.ChangeNotifierProvider<_i6.Manager>>[], + ) as List<_i5.ChangeNotifierProvider<_i6.Manager>>); @override _i5.ChangeNotifierProvider<_i6.Manager> getManagerProvider( String? walletId) => @@ -335,17 +353,17 @@ class MockWallets extends _i1.Mock implements _i14.Wallets { returnValueForMissingStub: null, ); @override - _i16.Future load(_i17.Prefs? prefs) => (super.noSuchMethod( + _i18.Future load(_i19.Prefs? prefs) => (super.noSuchMethod( Invocation.method( #load, [prefs], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future loadAfterStackRestore( - _i17.Prefs? prefs, + _i18.Future loadAfterStackRestore( + _i19.Prefs? prefs, List<_i6.Manager>? managers, ) => (super.noSuchMethod( @@ -356,11 +374,11 @@ class MockWallets extends _i1.Mock implements _i14.Wallets { managers, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i20.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -368,7 +386,7 @@ class MockWallets extends _i1.Mock implements _i14.Wallets { returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i20.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -420,10 +438,10 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValue: false, ) as bool); @override - _i15.Coin get coin => (super.noSuchMethod( + _i17.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i15.Coin.bitcoin, - ) as _i15.Coin); + returnValue: _i17.Coin.bitcoin, + ) as _i17.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -456,91 +474,42 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValueForMissingStub: null, ); @override - _i16.Future<_i8.FeeObject> get fees => (super.noSuchMethod( + _i18.Future<_i8.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i16.Future<_i8.FeeObject>.value(_FakeFeeObject_5( + returnValue: _i18.Future<_i8.FeeObject>.value(_FakeFeeObject_5( this, Invocation.getter(#fees), )), - ) as _i16.Future<_i8.FeeObject>); + ) as _i18.Future<_i8.FeeObject>); @override - _i16.Future get maxFee => (super.noSuchMethod( + _i18.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i18.Future.value(0), + ) as _i18.Future); @override - _i16.Future get currentReceivingAddress => (super.noSuchMethod( + _i18.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i18.Future.value(''), + ) as _i18.Future); @override - _i16.Future<_i9.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( + _i9.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_6( this, - Invocation.getter(#availableBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i9.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_6( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i9.Decimal); + ) as _i9.Balance); @override - _i16.Future<_i9.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i9.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i9.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#totalBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i9.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_6( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i9.Decimal); - @override - _i16.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); - @override - _i16.Future<_i8.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i18.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i16.Future<_i8.TransactionData>.value(_FakeTransactionData_7( - this, - Invocation.getter(#transactionData), - )), - ) as _i16.Future<_i8.TransactionData>); + _i18.Future>.value(<_i21.Transaction>[]), + ) as _i18.Future>); @override - _i16.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: - _i16.Future>.value(<_i8.UtxoObject>[]), - ) as _i16.Future>); + _i18.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i18.Future>.value(<_i21.UTXO>[]), + ) as _i18.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -560,29 +529,74 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValue: '', ) as String); @override - _i16.Future> get mnemonic => (super.noSuchMethod( + _i18.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); + returnValue: _i18.Future>.value([]), + ) as _i18.Future>); + @override + _i18.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i18.Future.value(), + ) as _i18.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i18.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i18.Future.value(''), + ) as _i18.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i16.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i18.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -592,9 +606,9 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValueForMissingStub: null, ); @override - _i16.Future> prepareSend({ + _i18.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i10.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -603,50 +617,32 @@ class MockManager extends _i1.Mock implements _i6.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i18.Future>.value({}), + ) as _i18.Future>); @override - _i16.Future confirmSend({required Map? txData}) => + _i18.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i18.Future.value(''), + ) as _i18.Future); @override - _i16.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); - @override - _i16.Future refresh() => (super.noSuchMethod( + _i18.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -656,34 +652,35 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValue: false, ) as bool); @override - _i16.Future testNetworkConnection() => (super.noSuchMethod( + _i18.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i18.Future.value(false), + ) as _i18.Future); @override - _i16.Future initializeNew() => (super.noSuchMethod( + _i18.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future initializeExisting() => (super.noSuchMethod( + _i18.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future recoverFromMnemonic({ + _i18.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -694,25 +691,26 @@ class MockManager extends _i1.Mock implements _i6.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future exitCurrentWallet() => (super.noSuchMethod( + _i18.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future fullRescan( + _i18.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -724,42 +722,52 @@ class MockManager extends _i1.Mock implements _i6.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); - @override - _i16.Future estimateFeeFor( - int? satoshiAmount, + _i18.Future<_i10.Amount> estimateFeeFor( + _i10.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i18.Future<_i10.Amount>.value(_FakeAmount_7( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i18.Future<_i10.Amount>); @override - _i16.Future generateNewAddress() => (super.noSuchMethod( + _i18.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i18.Future.value(false), + ) as _i18.Future); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + _i18.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); + @override + void addListener(_i20.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -767,7 +775,7 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i20.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -802,10 +810,10 @@ class MockCoinServiceAPI extends _i1.Mock implements _i7.CoinServiceAPI { returnValueForMissingStub: null, ); @override - _i15.Coin get coin => (super.noSuchMethod( + _i17.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i15.Coin.bitcoin, - ) as _i15.Coin); + returnValue: _i17.Coin.bitcoin, + ) as _i17.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -838,75 +846,42 @@ class MockCoinServiceAPI extends _i1.Mock implements _i7.CoinServiceAPI { returnValueForMissingStub: null, ); @override - _i16.Future<_i8.FeeObject> get fees => (super.noSuchMethod( + _i18.Future<_i8.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i16.Future<_i8.FeeObject>.value(_FakeFeeObject_5( + returnValue: _i18.Future<_i8.FeeObject>.value(_FakeFeeObject_5( this, Invocation.getter(#fees), )), - ) as _i16.Future<_i8.FeeObject>); + ) as _i18.Future<_i8.FeeObject>); @override - _i16.Future get maxFee => (super.noSuchMethod( + _i18.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i18.Future.value(0), + ) as _i18.Future); @override - _i16.Future get currentReceivingAddress => (super.noSuchMethod( + _i18.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i18.Future.value(''), + ) as _i18.Future); @override - _i16.Future<_i9.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( + _i9.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_6( this, - Invocation.getter(#availableBalance), - )), - ) as _i16.Future<_i9.Decimal>); + Invocation.getter(#balance), + ), + ) as _i9.Balance); @override - _i16.Future<_i9.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i9.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#totalBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i9.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); - @override - _i16.Future<_i8.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i18.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i16.Future<_i8.TransactionData>.value(_FakeTransactionData_7( - this, - Invocation.getter(#transactionData), - )), - ) as _i16.Future<_i8.TransactionData>); + _i18.Future>.value(<_i21.Transaction>[]), + ) as _i18.Future>); @override - _i16.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: - _i16.Future>.value(<_i8.UtxoObject>[]), - ) as _i16.Future>); + _i18.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i18.Future>.value(<_i21.UTXO>[]), + ) as _i18.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -926,10 +901,20 @@ class MockCoinServiceAPI extends _i1.Mock implements _i7.CoinServiceAPI { returnValue: '', ) as String); @override - _i16.Future> get mnemonic => (super.noSuchMethod( + _i18.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); + returnValue: _i18.Future>.value([]), + ) as _i18.Future>); + @override + _i18.Future get mnemonicString => (super.noSuchMethod( + Invocation.getter(#mnemonicString), + returnValue: _i18.Future.value(), + ) as _i18.Future); + @override + _i18.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i18.Future.value(), + ) as _i18.Future); @override bool get hasCalledExit => (super.noSuchMethod( Invocation.getter(#hasCalledExit), @@ -941,9 +926,14 @@ class MockCoinServiceAPI extends _i1.Mock implements _i7.CoinServiceAPI { returnValue: false, ) as bool); @override - _i16.Future> prepareSend({ + int get storedChainHeight => (super.noSuchMethod( + Invocation.getter(#storedChainHeight), + returnValue: 0, + ) as int); + @override + _i18.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i10.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -952,59 +942,41 @@ class MockCoinServiceAPI extends _i1.Mock implements _i7.CoinServiceAPI { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i18.Future>.value({}), + ) as _i18.Future>); @override - _i16.Future confirmSend({required Map? txData}) => + _i18.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i18.Future.value(''), + ) as _i18.Future); @override - _i16.Future send({ - required String? toAddress, - required int? amount, - Map? args, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); - @override - _i16.Future refresh() => (super.noSuchMethod( + _i18.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i18.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -1014,16 +986,17 @@ class MockCoinServiceAPI extends _i1.Mock implements _i7.CoinServiceAPI { returnValue: false, ) as bool); @override - _i16.Future testNetworkConnection() => (super.noSuchMethod( + _i18.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i18.Future.value(false), + ) as _i18.Future); @override - _i16.Future recoverFromMnemonic({ + _i18.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -1034,43 +1007,44 @@ class MockCoinServiceAPI extends _i1.Mock implements _i7.CoinServiceAPI { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future initializeNew() => (super.noSuchMethod( + _i18.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future initializeExisting() => (super.noSuchMethod( + _i18.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future exit() => (super.noSuchMethod( + _i18.Future exit() => (super.noSuchMethod( Invocation.method( #exit, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future fullRescan( + _i18.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -1082,54 +1056,63 @@ class MockCoinServiceAPI extends _i1.Mock implements _i7.CoinServiceAPI { maxNumberOfIndexesToCheck, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future estimateFeeFor( - int? satoshiAmount, + _i18.Future<_i10.Amount> estimateFeeFor( + _i10.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i18.Future<_i10.Amount>.value(_FakeAmount_7( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i18.Future<_i10.Amount>); @override - _i16.Future generateNewAddress() => (super.noSuchMethod( + _i18.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i18.Future.value(false), + ) as _i18.Future); @override - _i16.Future updateSentCachedTxData(Map? txData) => + _i18.Future updateSentCachedTxData(Map? txData) => (super.noSuchMethod( Invocation.method( #updateSentCachedTxData, [txData], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); } /// A class which mocks [FiroWallet]. /// /// See the documentation for Mockito's code generation for more information. -class MockFiroWallet extends _i1.Mock implements _i19.FiroWallet { +class MockFiroWallet extends _i1.Mock implements _i22.FiroWallet { MockFiroWallet() { _i1.throwOnMissingStub(this); } @override - set timer(_i16.Timer? _timer) => super.noSuchMethod( + set timer(_i18.Timer? _timer) => super.noSuchMethod( Invocation.setter( #timer, _timer, @@ -1137,23 +1120,15 @@ class MockFiroWallet extends _i1.Mock implements _i19.FiroWallet { returnValueForMissingStub: null, ); @override - set cachedTxData(_i8.TransactionData? _cachedTxData) => super.noSuchMethod( - Invocation.setter( - #cachedTxData, - _cachedTxData, - ), - returnValueForMissingStub: null, - ); - @override - _i10.TransactionNotificationTracker get txTracker => (super.noSuchMethod( + _i11.TransactionNotificationTracker get txTracker => (super.noSuchMethod( Invocation.getter(#txTracker), returnValue: _FakeTransactionNotificationTracker_8( this, Invocation.getter(#txTracker), ), - ) as _i10.TransactionNotificationTracker); + ) as _i11.TransactionNotificationTracker); @override - set txTracker(_i10.TransactionNotificationTracker? _txTracker) => + set txTracker(_i11.TransactionNotificationTracker? _txTracker) => super.noSuchMethod( Invocation.setter( #txTracker, @@ -1227,111 +1202,48 @@ class MockFiroWallet extends _i1.Mock implements _i19.FiroWallet { returnValue: false, ) as bool); @override - _i15.Coin get coin => (super.noSuchMethod( + _i17.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i15.Coin.bitcoin, - ) as _i15.Coin); + returnValue: _i17.Coin.bitcoin, + ) as _i17.Coin); @override - _i16.Future> get mnemonic => (super.noSuchMethod( + _i18.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); + returnValue: _i18.Future>.value([]), + ) as _i18.Future>); @override - _i16.Future<_i9.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#availableBalance), - )), - ) as _i16.Future<_i9.Decimal>); + _i18.Future get mnemonicString => (super.noSuchMethod( + Invocation.getter(#mnemonicString), + returnValue: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future<_i9.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i16.Future<_i9.Decimal>); + _i18.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future<_i9.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#totalBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i9.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i8.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), - returnValue: - _i16.Future<_i8.TransactionData>.value(_FakeTransactionData_7( - this, - Invocation.getter(#transactionData), - )), - ) as _i16.Future<_i8.TransactionData>); - @override - _i16.Future<_i8.UtxoData> get utxoData => (super.noSuchMethod( - Invocation.getter(#utxoData), - returnValue: _i16.Future<_i8.UtxoData>.value(_FakeUtxoData_9( - this, - Invocation.getter(#utxoData), - )), - ) as _i16.Future<_i8.UtxoData>); - @override - _i16.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: - _i16.Future>.value(<_i8.UtxoObject>[]), - ) as _i16.Future>); - @override - _i16.Future<_i8.TransactionData> get lelantusTransactionData => - (super.noSuchMethod( - Invocation.getter(#lelantusTransactionData), - returnValue: - _i16.Future<_i8.TransactionData>.value(_FakeTransactionData_7( - this, - Invocation.getter(#lelantusTransactionData), - )), - ) as _i16.Future<_i8.TransactionData>); - @override - _i16.Future get maxFee => (super.noSuchMethod( + _i18.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i18.Future.value(0), + ) as _i18.Future); @override - _i16.Future> get balances => (super.noSuchMethod( - Invocation.getter(#balances), - returnValue: _i16.Future>.value(<_i9.Decimal>[]), - ) as _i16.Future>); - @override - _i16.Future<_i9.Decimal> get firoPrice => (super.noSuchMethod( - Invocation.getter(#firoPrice), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#firoPrice), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i8.FeeObject> get fees => (super.noSuchMethod( + _i18.Future<_i8.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i16.Future<_i8.FeeObject>.value(_FakeFeeObject_5( + returnValue: _i18.Future<_i8.FeeObject>.value(_FakeFeeObject_5( this, Invocation.getter(#fees), )), - ) as _i16.Future<_i8.FeeObject>); + ) as _i18.Future<_i8.FeeObject>); @override - _i16.Future get currentReceivingAddress => (super.noSuchMethod( + _i18.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i18.Future.value(''), + ) as _i18.Future); + @override + _i18.Future get currentChangeAddress => (super.noSuchMethod( + Invocation.getter(#currentChangeAddress), + returnValue: _i18.Future.value(''), + ) as _i18.Future); @override String get walletName => (super.noSuchMethod( Invocation.getter(#walletName), @@ -1351,31 +1263,26 @@ class MockFiroWallet extends _i1.Mock implements _i19.FiroWallet { returnValue: '', ) as String); @override - _i16.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); - @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override - _i11.ElectrumX get electrumXClient => (super.noSuchMethod( + _i12.ElectrumX get electrumXClient => (super.noSuchMethod( Invocation.getter(#electrumXClient), - returnValue: _FakeElectrumX_10( + returnValue: _FakeElectrumX_9( this, Invocation.getter(#electrumXClient), ), - ) as _i11.ElectrumX); + ) as _i12.ElectrumX); @override - _i12.CachedElectrumX get cachedElectrumXClient => (super.noSuchMethod( + _i13.CachedElectrumX get cachedElectrumXClient => (super.noSuchMethod( Invocation.getter(#cachedElectrumXClient), - returnValue: _FakeCachedElectrumX_11( + returnValue: _FakeCachedElectrumX_10( this, Invocation.getter(#cachedElectrumXClient), ), - ) as _i12.CachedElectrumX); + ) as _i13.CachedElectrumX); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -1387,6 +1294,48 @@ class MockFiroWallet extends _i1.Mock implements _i19.FiroWallet { returnValue: false, ) as bool); @override + _i18.Future get chainHeight => (super.noSuchMethod( + Invocation.getter(#chainHeight), + returnValue: _i18.Future.value(0), + ) as _i18.Future); + @override + int get storedChainHeight => (super.noSuchMethod( + Invocation.getter(#storedChainHeight), + returnValue: 0, + ) as int); + @override + _i9.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_6( + this, + Invocation.getter(#balance), + ), + ) as _i9.Balance); + @override + _i9.Balance get balancePrivate => (super.noSuchMethod( + Invocation.getter(#balancePrivate), + returnValue: _FakeBalance_6( + this, + Invocation.getter(#balancePrivate), + ), + ) as _i9.Balance); + @override + _i18.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i18.Future>.value(<_i21.UTXO>[]), + ) as _i18.Future>); + @override + _i18.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), + returnValue: + _i18.Future>.value(<_i21.Transaction>[]), + ) as _i18.Future>); + @override + _i18.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i18.Future.value(''), + ) as _i18.Future); + @override set onIsActiveWalletChanged(void Function(bool)? _onIsActiveWalletChanged) => super.noSuchMethod( Invocation.setter( @@ -1396,6 +1345,14 @@ class MockFiroWallet extends _i1.Mock implements _i19.FiroWallet { returnValueForMissingStub: null, ); @override + _i14.MainDB get db => (super.noSuchMethod( + Invocation.getter(#db), + returnValue: _FakeMainDB_11( + this, + Invocation.getter(#db), + ), + ) as _i14.MainDB); + @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( #validateAddress, @@ -1404,23 +1361,23 @@ class MockFiroWallet extends _i1.Mock implements _i19.FiroWallet { returnValue: false, ) as bool); @override - _i16.Future updateSentCachedTxData(Map? txData) => + _i18.Future updateSentCachedTxData(Map? txData) => (super.noSuchMethod( Invocation.method( #updateSentCachedTxData, [txData], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future testNetworkConnection() => (super.noSuchMethod( + _i18.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i18.Future.value(false), + ) as _i18.Future); @override void startNetworkAlivePinging() => super.noSuchMethod( Invocation.method( @@ -1438,9 +1395,9 @@ class MockFiroWallet extends _i1.Mock implements _i19.FiroWallet { returnValueForMissingStub: null, ); @override - _i16.Future> prepareSendPublic({ + _i18.Future> prepareSendPublic({ required String? address, - required int? satoshiAmount, + required _i10.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -1449,27 +1406,27 @@ class MockFiroWallet extends _i1.Mock implements _i19.FiroWallet { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i18.Future>.value({}), + ) as _i18.Future>); @override - _i16.Future confirmSendPublic({dynamic txData}) => + _i18.Future confirmSendPublic({dynamic txData}) => (super.noSuchMethod( Invocation.method( #confirmSendPublic, [], {#txData: txData}, ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i18.Future.value(''), + ) as _i18.Future); @override - _i16.Future> prepareSend({ + _i18.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i10.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -1478,41 +1435,23 @@ class MockFiroWallet extends _i1.Mock implements _i19.FiroWallet { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i18.Future>.value({}), + ) as _i18.Future>); @override - _i16.Future confirmSend({required Map? txData}) => + _i18.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); - @override - _i16.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i18.Future.value(''), + ) as _i18.Future); @override int estimateTxFee({ required int? vSize, @@ -1536,7 +1475,7 @@ class MockFiroWallet extends _i1.Mock implements _i19.FiroWallet { String? _recipientAddress, bool? isSendAll, { int? additionalOutputs = 0, - List<_i8.UtxoObject>? utxos, + List<_i21.UTXO>? utxos, }) => super.noSuchMethod(Invocation.method( #coinSelection, @@ -1552,20 +1491,19 @@ class MockFiroWallet extends _i1.Mock implements _i19.FiroWallet { }, )); @override - _i16.Future> fetchBuildTxData( - List<_i8.UtxoObject>? utxosToUse) => + _i18.Future> fetchBuildTxData( + List<_i21.UTXO>? utxosToUse) => (super.noSuchMethod( Invocation.method( #fetchBuildTxData, [utxosToUse], ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i18.Future>.value(<_i23.SigningData>[]), + ) as _i18.Future>); @override - _i16.Future> buildTransaction({ - required List<_i8.UtxoObject>? utxosToUse, - required Map? utxoSigningData, + _i18.Future> buildTransaction({ + required List<_i23.SigningData>? utxoSigningData, required List? recipients, required List? satoshiAmounts, }) => @@ -1574,75 +1512,67 @@ class MockFiroWallet extends _i1.Mock implements _i19.FiroWallet { #buildTransaction, [], { - #utxosToUse: utxosToUse, #utxoSigningData: utxoSigningData, #recipients: recipients, #satoshiAmounts: satoshiAmounts, }, ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i18.Future>.value({}), + ) as _i18.Future>); @override - _i16.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i18.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future initializeNew() => (super.noSuchMethod( + _i18.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future initializeExisting() => (super.noSuchMethod( + _i18.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future refreshIfThereIsNewData() => (super.noSuchMethod( + _i18.Future refreshIfThereIsNewData() => (super.noSuchMethod( Invocation.method( #refreshIfThereIsNewData, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i18.Future.value(false), + ) as _i18.Future); @override - _i16.Future getAllTxsToWatch( - _i8.TransactionData? txData, - _i8.TransactionData? lTxData, - ) => - (super.noSuchMethod( + _i18.Future getAllTxsToWatch() => (super.noSuchMethod( Invocation.method( #getAllTxsToWatch, - [ - txData, - lTxData, - ], + [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future refresh() => (super.noSuchMethod( + _i18.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override List> getLelantusCoinMap() => (super.noSuchMethod( @@ -1653,35 +1583,35 @@ class MockFiroWallet extends _i1.Mock implements _i19.FiroWallet { returnValue: >[], ) as List>); @override - _i16.Future anonymizeAllPublicFunds() => (super.noSuchMethod( + _i18.Future anonymizeAllPublicFunds() => (super.noSuchMethod( Invocation.method( #anonymizeAllPublicFunds, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future>> createMintsFromAmount(int? total) => + _i18.Future>> createMintsFromAmount(int? total) => (super.noSuchMethod( Invocation.method( #createMintsFromAmount, [total], ), - returnValue: _i16.Future>>.value( + returnValue: _i18.Future>>.value( >[]), - ) as _i16.Future>>); + ) as _i18.Future>>); @override - _i16.Future submitHexToNetwork(String? hex) => (super.noSuchMethod( + _i18.Future submitHexToNetwork(String? hex) => (super.noSuchMethod( Invocation.method( #submitHexToNetwork, [hex], ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i18.Future.value(''), + ) as _i18.Future); @override - _i16.Future> buildMintTransaction( - List<_i8.UtxoObject>? utxosToUse, + _i18.Future> buildMintTransaction( + List<_i21.UTXO>? utxosToUse, int? satoshisPerRecipient, List>? mintsMap, ) => @@ -1695,73 +1625,29 @@ class MockFiroWallet extends _i1.Mock implements _i19.FiroWallet { ], ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i18.Future>.value({}), + ) as _i18.Future>); @override - _i16.Future checkReceivingAddressForTransactions() => + _i18.Future checkReceivingAddressForTransactions() => (super.noSuchMethod( Invocation.method( #checkReceivingAddressForTransactions, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future checkChangeAddressForTransactions() => (super.noSuchMethod( + _i18.Future checkChangeAddressForTransactions() => (super.noSuchMethod( Invocation.method( #checkChangeAddressForTransactions, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future fillAddresses( - String? suppliedMnemonic, { - int? perBatch = 50, - int? numberOfThreads = 4, - }) => - (super.noSuchMethod( - Invocation.method( - #fillAddresses, - [suppliedMnemonic], - { - #perBatch: perBatch, - #numberOfThreads: numberOfThreads, - }, - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i16.Future incrementAddressIndexForChain(int? chain) => - (super.noSuchMethod( - Invocation.method( - #incrementAddressIndexForChain, - [chain], - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i16.Future addToAddressesArrayForChain( - String? address, - int? chain, - ) => - (super.noSuchMethod( - Invocation.method( - #addToAddressesArrayForChain, - [ - address, - chain, - ], - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i16.Future fullRescan( + _i18.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -1773,12 +1659,13 @@ class MockFiroWallet extends _i1.Mock implements _i19.FiroWallet { maxNumberOfIndexesToCheck, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future recoverFromMnemonic({ + _i18.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -1789,108 +1676,137 @@ class MockFiroWallet extends _i1.Mock implements _i19.FiroWallet { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future> getSetDataMap(int? latestSetId) => + _i18.Future> getSetDataMap(int? latestSetId) => (super.noSuchMethod( Invocation.method( #getSetDataMap, [latestSetId], ), - returnValue: _i16.Future>.value({}), - ) as _i16.Future>); + returnValue: _i18.Future>.value({}), + ) as _i18.Future>); @override - _i16.Future>> fetchAnonymitySets() => + _i18.Future getTransactionCacheEarly(List? allAddresses) => + (super.noSuchMethod( + Invocation.method( + #getTransactionCacheEarly, + [allAddresses], + ), + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); + @override + _i18.Future>> fetchAnonymitySets() => (super.noSuchMethod( Invocation.method( #fetchAnonymitySets, [], ), - returnValue: _i16.Future>>.value( + returnValue: _i18.Future>>.value( >[]), - ) as _i16.Future>>); + ) as _i18.Future>>); @override - _i16.Future getLatestSetId() => (super.noSuchMethod( + _i18.Future getLatestSetId() => (super.noSuchMethod( Invocation.method( #getLatestSetId, [], ), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i18.Future.value(0), + ) as _i18.Future); @override - _i16.Future> getUsedCoinSerials() => (super.noSuchMethod( + _i18.Future> getUsedCoinSerials() => (super.noSuchMethod( Invocation.method( #getUsedCoinSerials, [], ), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); + returnValue: _i18.Future>.value([]), + ) as _i18.Future>); @override - _i16.Future exit() => (super.noSuchMethod( + _i18.Future exit() => (super.noSuchMethod( Invocation.method( #exit, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future getCoinsToJoinSplit(int? required) => + _i18.Future getCoinsToJoinSplit(int? required) => (super.noSuchMethod( Invocation.method( #getCoinsToJoinSplit, [required], ), - returnValue: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future estimateJoinSplitFee(int? spendAmount) => + _i18.Future estimateJoinSplitFee(int? spendAmount) => (super.noSuchMethod( Invocation.method( #estimateJoinSplitFee, [spendAmount], ), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i18.Future.value(0), + ) as _i18.Future); @override - _i16.Future estimateFeeFor( - int? satoshiAmount, + _i18.Future<_i10.Amount> estimateFeeFor( + _i10.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i18.Future<_i10.Amount>.value(_FakeAmount_7( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i18.Future<_i10.Amount>); @override - _i16.Future estimateFeeForPublic( - int? satoshiAmount, + _i18.Future<_i10.Amount> estimateFeeForPublic( + _i10.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeForPublic, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i18.Future<_i10.Amount>.value(_FakeAmount_7( + this, + Invocation.method( + #estimateFeeForPublic, + [ + amount, + feeRate, + ], + ), + )), + ) as _i18.Future<_i10.Amount>); @override - int roughFeeEstimate( + _i10.Amount roughFeeEstimate( int? inputCount, int? outputCount, int? feeRatePerKB, @@ -1904,35 +1820,49 @@ class MockFiroWallet extends _i1.Mock implements _i19.FiroWallet { feeRatePerKB, ], ), - returnValue: 0, - ) as int); + returnValue: _FakeAmount_7( + this, + Invocation.method( + #roughFeeEstimate, + [ + inputCount, + outputCount, + feeRatePerKB, + ], + ), + ), + ) as _i10.Amount); @override - int sweepAllEstimate(int? feeRate) => (super.noSuchMethod( + _i18.Future<_i10.Amount> sweepAllEstimate(int? feeRate) => + (super.noSuchMethod( Invocation.method( #sweepAllEstimate, [feeRate], ), - returnValue: 0, - ) as int); + returnValue: _i18.Future<_i10.Amount>.value(_FakeAmount_7( + this, + Invocation.method( + #sweepAllEstimate, + [feeRate], + ), + )), + ) as _i18.Future<_i10.Amount>); @override - _i16.Future>> fastFetch( + _i18.Future>> fastFetch( List? allTxHashes) => (super.noSuchMethod( Invocation.method( #fastFetch, [allTxHashes], ), - returnValue: _i16.Future>>.value( + returnValue: _i18.Future>>.value( >[]), - ) as _i16.Future>>); + ) as _i18.Future>>); @override - _i16.Future> getJMintTransactions( - _i12.CachedElectrumX? cachedClient, + _i18.Future> getJMintTransactions( + _i13.CachedElectrumX? cachedClient, List? transactions, - String? currency, - _i15.Coin? coin, - _i9.Decimal? currentPrice, - String? locale, + _i17.Coin? coin, ) => (super.noSuchMethod( Invocation.method( @@ -1940,57 +1870,226 @@ class MockFiroWallet extends _i1.Mock implements _i19.FiroWallet { [ cachedClient, transactions, - currency, coin, - currentPrice, - locale, ], ), - returnValue: - _i16.Future>.value(<_i8.Transaction>[]), - ) as _i16.Future>); + returnValue: _i18.Future>.value( + <_i21.Address, _i21.Transaction>{}), + ) as _i18.Future>); @override - _i16.Future generateNewAddress() => (super.noSuchMethod( + _i18.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i18.Future.value(false), + ) as _i18.Future); @override - _i16.Future<_i9.Decimal> availablePrivateBalance() => (super.noSuchMethod( + _i10.Amount availablePrivateBalance() => (super.noSuchMethod( Invocation.method( #availablePrivateBalance, [], ), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( + returnValue: _FakeAmount_7( this, Invocation.method( #availablePrivateBalance, [], ), - )), - ) as _i16.Future<_i9.Decimal>); + ), + ) as _i10.Amount); @override - _i16.Future<_i9.Decimal> availablePublicBalance() => (super.noSuchMethod( + _i10.Amount availablePublicBalance() => (super.noSuchMethod( Invocation.method( #availablePublicBalance, [], ), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( + returnValue: _FakeAmount_7( this, Invocation.method( #availablePublicBalance, [], ), - )), - ) as _i16.Future<_i9.Decimal>); + ), + ) as _i10.Amount); + @override + void initCache( + String? walletId, + _i17.Coin? coin, + ) => + super.noSuchMethod( + Invocation.method( + #initCache, + [ + walletId, + coin, + ], + ), + returnValueForMissingStub: null, + ); + @override + _i18.Future updateCachedId(String? id) => (super.noSuchMethod( + Invocation.method( + #updateCachedId, + [id], + ), + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); + @override + int getCachedChainHeight() => (super.noSuchMethod( + Invocation.method( + #getCachedChainHeight, + [], + ), + returnValue: 0, + ) as int); + @override + _i18.Future updateCachedChainHeight(int? height) => (super.noSuchMethod( + Invocation.method( + #updateCachedChainHeight, + [height], + ), + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); + @override + bool getCachedIsFavorite() => (super.noSuchMethod( + Invocation.method( + #getCachedIsFavorite, + [], + ), + returnValue: false, + ) as bool); + @override + _i18.Future updateCachedIsFavorite(bool? isFavorite) => + (super.noSuchMethod( + Invocation.method( + #updateCachedIsFavorite, + [isFavorite], + ), + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); + @override + _i9.Balance getCachedBalance() => (super.noSuchMethod( + Invocation.method( + #getCachedBalance, + [], + ), + returnValue: _FakeBalance_6( + this, + Invocation.method( + #getCachedBalance, + [], + ), + ), + ) as _i9.Balance); + @override + _i18.Future updateCachedBalance(_i9.Balance? balance) => + (super.noSuchMethod( + Invocation.method( + #updateCachedBalance, + [balance], + ), + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); + @override + _i9.Balance getCachedBalanceSecondary() => (super.noSuchMethod( + Invocation.method( + #getCachedBalanceSecondary, + [], + ), + returnValue: _FakeBalance_6( + this, + Invocation.method( + #getCachedBalanceSecondary, + [], + ), + ), + ) as _i9.Balance); + @override + _i18.Future updateCachedBalanceSecondary(_i9.Balance? balance) => + (super.noSuchMethod( + Invocation.method( + #updateCachedBalanceSecondary, + [balance], + ), + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); + @override + List getWalletTokenContractAddresses() => (super.noSuchMethod( + Invocation.method( + #getWalletTokenContractAddresses, + [], + ), + returnValue: [], + ) as List); + @override + _i18.Future updateWalletTokenContractAddresses( + List? contractAddresses) => + (super.noSuchMethod( + Invocation.method( + #updateWalletTokenContractAddresses, + [contractAddresses], + ), + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); + @override + void initWalletDB({_i14.MainDB? mockableOverride}) => super.noSuchMethod( + Invocation.method( + #initWalletDB, + [], + {#mockableOverride: mockableOverride}, + ), + returnValueForMissingStub: null, + ); + @override + void initFiroHive(String? walletId) => super.noSuchMethod( + Invocation.method( + #initFiroHive, + [walletId], + ), + returnValueForMissingStub: null, + ); + @override + _i18.Future firoUpdateJIndex(List? jIndex) => + (super.noSuchMethod( + Invocation.method( + #firoUpdateJIndex, + [jIndex], + ), + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); + @override + _i18.Future firoUpdateLelantusCoins(List? lelantusCoins) => + (super.noSuchMethod( + Invocation.method( + #firoUpdateLelantusCoins, + [lelantusCoins], + ), + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); + @override + _i18.Future firoUpdateMintIndex(int? mintIndex) => (super.noSuchMethod( + Invocation.method( + #firoUpdateMintIndex, + [mintIndex], + ), + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); } /// A class which mocks [LocaleService]. /// /// See the documentation for Mockito's code generation for more information. -class MockLocaleService extends _i1.Mock implements _i20.LocaleService { +class MockLocaleService extends _i1.Mock implements _i24.LocaleService { MockLocaleService() { _i1.throwOnMissingStub(this); } @@ -2006,17 +2105,17 @@ class MockLocaleService extends _i1.Mock implements _i20.LocaleService { returnValue: false, ) as bool); @override - _i16.Future loadLocale({bool? notify = true}) => (super.noSuchMethod( + _i18.Future loadLocale({bool? notify = true}) => (super.noSuchMethod( Invocation.method( #loadLocale, [], {#notify: notify}, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i20.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -2024,7 +2123,7 @@ class MockLocaleService extends _i1.Mock implements _i20.LocaleService { returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i20.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -2052,7 +2151,7 @@ class MockLocaleService extends _i1.Mock implements _i20.LocaleService { /// A class which mocks [Prefs]. /// /// See the documentation for Mockito's code generation for more information. -class MockPrefs extends _i1.Mock implements _i17.Prefs { +class MockPrefs extends _i1.Mock implements _i19.Prefs { MockPrefs() { _i1.throwOnMissingStub(this); } @@ -2108,12 +2207,12 @@ class MockPrefs extends _i1.Mock implements _i17.Prefs { returnValueForMissingStub: null, ); @override - _i21.SyncingType get syncType => (super.noSuchMethod( + _i25.SyncingType get syncType => (super.noSuchMethod( Invocation.getter(#syncType), - returnValue: _i21.SyncingType.currentWalletOnly, - ) as _i21.SyncingType); + returnValue: _i25.SyncingType.currentWalletOnly, + ) as _i25.SyncingType); @override - set syncType(_i21.SyncingType? syncType) => super.noSuchMethod( + set syncType(_i25.SyncingType? syncType) => super.noSuchMethod( Invocation.setter( #syncType, syncType, @@ -2173,16 +2272,15 @@ class MockPrefs extends _i1.Mock implements _i17.Prefs { returnValueForMissingStub: null, ); @override - _i22.ExchangeRateType get exchangeRateType => (super.noSuchMethod( - Invocation.getter(#exchangeRateType), - returnValue: _i22.ExchangeRateType.estimated, - ) as _i22.ExchangeRateType); + bool get randomizePIN => (super.noSuchMethod( + Invocation.getter(#randomizePIN), + returnValue: false, + ) as bool); @override - set exchangeRateType(_i22.ExchangeRateType? exchangeRateType) => - super.noSuchMethod( + set randomizePIN(bool? randomizePIN) => super.noSuchMethod( Invocation.setter( - #exchangeRateType, - exchangeRateType, + #randomizePIN, + randomizePIN, ), returnValueForMissingStub: null, ); @@ -2260,12 +2358,12 @@ class MockPrefs extends _i1.Mock implements _i17.Prefs { returnValueForMissingStub: null, ); @override - _i23.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod( + _i26.BackupFrequencyType get backupFrequencyType => (super.noSuchMethod( Invocation.getter(#backupFrequencyType), - returnValue: _i23.BackupFrequencyType.everyTenMinutes, - ) as _i23.BackupFrequencyType); + returnValue: _i26.BackupFrequencyType.everyTenMinutes, + ) as _i26.BackupFrequencyType); @override - set backupFrequencyType(_i23.BackupFrequencyType? backupFrequencyType) => + set backupFrequencyType(_i26.BackupFrequencyType? backupFrequencyType) => super.noSuchMethod( Invocation.setter( #backupFrequencyType, @@ -2330,38 +2428,124 @@ class MockPrefs extends _i1.Mock implements _i17.Prefs { returnValueForMissingStub: null, ); @override + bool get enableCoinControl => (super.noSuchMethod( + Invocation.getter(#enableCoinControl), + returnValue: false, + ) as bool); + @override + set enableCoinControl(bool? enableCoinControl) => super.noSuchMethod( + Invocation.setter( + #enableCoinControl, + enableCoinControl, + ), + returnValueForMissingStub: null, + ); + @override + bool get enableSystemBrightness => (super.noSuchMethod( + Invocation.getter(#enableSystemBrightness), + returnValue: false, + ) as bool); + @override + set enableSystemBrightness(bool? enableSystemBrightness) => + super.noSuchMethod( + Invocation.setter( + #enableSystemBrightness, + enableSystemBrightness, + ), + returnValueForMissingStub: null, + ); + @override + String get themeId => (super.noSuchMethod( + Invocation.getter(#themeId), + returnValue: '', + ) as String); + @override + set themeId(String? themeId) => super.noSuchMethod( + Invocation.setter( + #themeId, + themeId, + ), + returnValueForMissingStub: null, + ); + @override + String get systemBrightnessLightThemeId => (super.noSuchMethod( + Invocation.getter(#systemBrightnessLightThemeId), + returnValue: '', + ) as String); + @override + set systemBrightnessLightThemeId(String? systemBrightnessLightThemeId) => + super.noSuchMethod( + Invocation.setter( + #systemBrightnessLightThemeId, + systemBrightnessLightThemeId, + ), + returnValueForMissingStub: null, + ); + @override + String get systemBrightnessDarkThemeId => (super.noSuchMethod( + Invocation.getter(#systemBrightnessDarkThemeId), + returnValue: '', + ) as String); + @override + set systemBrightnessDarkThemeId(String? systemBrightnessDarkThemeId) => + super.noSuchMethod( + Invocation.setter( + #systemBrightnessDarkThemeId, + systemBrightnessDarkThemeId, + ), + returnValueForMissingStub: null, + ); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i16.Future init() => (super.noSuchMethod( + _i18.Future init() => (super.noSuchMethod( Invocation.method( #init, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future incrementCurrentNotificationIndex() => (super.noSuchMethod( + _i18.Future incrementCurrentNotificationIndex() => (super.noSuchMethod( Invocation.method( #incrementCurrentNotificationIndex, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future isExternalCallsSet() => (super.noSuchMethod( + _i18.Future isExternalCallsSet() => (super.noSuchMethod( Invocation.method( #isExternalCallsSet, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i18.Future.value(false), + ) as _i18.Future); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + _i18.Future saveUserID(String? userId) => (super.noSuchMethod( + Invocation.method( + #saveUserID, + [userId], + ), + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); + @override + _i18.Future saveSignupEpoch(int? signupEpoch) => (super.noSuchMethod( + Invocation.method( + #saveSignupEpoch, + [signupEpoch], + ), + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); + @override + void addListener(_i20.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -2369,7 +2553,7 @@ class MockPrefs extends _i1.Mock implements _i17.Prefs { returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i20.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -2397,7 +2581,7 @@ class MockPrefs extends _i1.Mock implements _i17.Prefs { /// A class which mocks [PriceService]. /// /// See the documentation for Mockito's code generation for more information. -class MockPriceService extends _i1.Mock implements _i24.PriceService { +class MockPriceService extends _i1.Mock implements _i27.PriceService { MockPriceService() { _i1.throwOnMissingStub(this); } @@ -2416,6 +2600,11 @@ class MockPriceService extends _i1.Mock implements _i24.PriceService { returnValueForMissingStub: null, ); @override + Set get tokenContractAddressesToCheck => (super.noSuchMethod( + Invocation.getter(#tokenContractAddressesToCheck), + returnValue: {}, + ) as Set); + @override Duration get updateInterval => (super.noSuchMethod( Invocation.getter(#updateInterval), returnValue: _FakeDuration_12( @@ -2429,29 +2618,44 @@ class MockPriceService extends _i1.Mock implements _i24.PriceService { returnValue: false, ) as bool); @override - _i13.Tuple2<_i9.Decimal, double> getPrice(_i15.Coin? coin) => + _i15.Tuple2<_i28.Decimal, double> getPrice(_i17.Coin? coin) => (super.noSuchMethod( Invocation.method( #getPrice, [coin], ), - returnValue: _FakeTuple2_13<_i9.Decimal, double>( + returnValue: _FakeTuple2_13<_i28.Decimal, double>( this, Invocation.method( #getPrice, [coin], ), ), - ) as _i13.Tuple2<_i9.Decimal, double>); + ) as _i15.Tuple2<_i28.Decimal, double>); @override - _i16.Future updatePrice() => (super.noSuchMethod( + _i15.Tuple2<_i28.Decimal, double> getTokenPrice(String? contractAddress) => + (super.noSuchMethod( + Invocation.method( + #getTokenPrice, + [contractAddress], + ), + returnValue: _FakeTuple2_13<_i28.Decimal, double>( + this, + Invocation.method( + #getTokenPrice, + [contractAddress], + ), + ), + ) as _i15.Tuple2<_i28.Decimal, double>); + @override + _i18.Future updatePrice() => (super.noSuchMethod( Invocation.method( #updatePrice, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override void cancel() => super.noSuchMethod( Invocation.method( @@ -2477,7 +2681,7 @@ class MockPriceService extends _i1.Mock implements _i24.PriceService { returnValueForMissingStub: null, ); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i20.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -2485,7 +2689,7 @@ class MockPriceService extends _i1.Mock implements _i24.PriceService { returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i20.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -2505,7 +2709,7 @@ class MockPriceService extends _i1.Mock implements _i24.PriceService { /// A class which mocks [NotesService]. /// /// See the documentation for Mockito's code generation for more information. -class MockNotesService extends _i1.Mock implements _i25.NotesService { +class MockNotesService extends _i1.Mock implements _i29.NotesService { MockNotesService() { _i1.throwOnMissingStub(this); } @@ -2521,35 +2725,35 @@ class MockNotesService extends _i1.Mock implements _i25.NotesService { returnValue: {}, ) as Map); @override - _i16.Future> get notes => (super.noSuchMethod( + _i18.Future> get notes => (super.noSuchMethod( Invocation.getter(#notes), - returnValue: _i16.Future>.value({}), - ) as _i16.Future>); + returnValue: _i18.Future>.value({}), + ) as _i18.Future>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i16.Future> search(String? text) => (super.noSuchMethod( + _i18.Future> search(String? text) => (super.noSuchMethod( Invocation.method( #search, [text], ), - returnValue: _i16.Future>.value({}), - ) as _i16.Future>); + returnValue: _i18.Future>.value({}), + ) as _i18.Future>); @override - _i16.Future getNoteFor({required String? txid}) => + _i18.Future getNoteFor({required String? txid}) => (super.noSuchMethod( Invocation.method( #getNoteFor, [], {#txid: txid}, ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i18.Future.value(''), + ) as _i18.Future); @override - _i16.Future editOrAddNote({ + _i18.Future editOrAddNote({ required String? txid, required String? note, }) => @@ -2562,21 +2766,21 @@ class MockNotesService extends _i1.Mock implements _i25.NotesService { #note: note, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - _i16.Future deleteNote({required String? txid}) => (super.noSuchMethod( + _i18.Future deleteNote({required String? txid}) => (super.noSuchMethod( Invocation.method( #deleteNote, [], {#txid: txid}, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i20.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -2584,7 +2788,7 @@ class MockNotesService extends _i1.Mock implements _i25.NotesService { returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i20.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -2608,3 +2812,93 @@ class MockNotesService extends _i1.Mock implements _i25.NotesService { returnValueForMissingStub: null, ); } + +/// A class which mocks [ThemeService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockThemeService extends _i1.Mock implements _i30.ThemeService { + MockThemeService() { + _i1.throwOnMissingStub(this); + } + + @override + _i14.MainDB get db => (super.noSuchMethod( + Invocation.getter(#db), + returnValue: _FakeMainDB_11( + this, + Invocation.getter(#db), + ), + ) as _i14.MainDB); + @override + List<_i31.StackTheme> get installedThemes => (super.noSuchMethod( + Invocation.getter(#installedThemes), + returnValue: <_i31.StackTheme>[], + ) as List<_i31.StackTheme>); + @override + void init(_i14.MainDB? db) => super.noSuchMethod( + Invocation.method( + #init, + [db], + ), + returnValueForMissingStub: null, + ); + @override + _i18.Future install({required _i32.Uint8List? themeArchiveData}) => + (super.noSuchMethod( + Invocation.method( + #install, + [], + {#themeArchiveData: themeArchiveData}, + ), + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); + @override + _i18.Future remove({required String? themeId}) => (super.noSuchMethod( + Invocation.method( + #remove, + [], + {#themeId: themeId}, + ), + returnValue: _i18.Future.value(), + returnValueForMissingStub: _i18.Future.value(), + ) as _i18.Future); + @override + _i18.Future verifyInstalled({required String? themeId}) => + (super.noSuchMethod( + Invocation.method( + #verifyInstalled, + [], + {#themeId: themeId}, + ), + returnValue: _i18.Future.value(false), + ) as _i18.Future); + @override + _i18.Future> fetchThemes() => + (super.noSuchMethod( + Invocation.method( + #fetchThemes, + [], + ), + returnValue: _i18.Future>.value( + <_i30.StackThemeMetaData>[]), + ) as _i18.Future>); + @override + _i18.Future<_i32.Uint8List> fetchTheme( + {required _i30.StackThemeMetaData? themeMetaData}) => + (super.noSuchMethod( + Invocation.method( + #fetchTheme, + [], + {#themeMetaData: themeMetaData}, + ), + returnValue: _i18.Future<_i32.Uint8List>.value(_i32.Uint8List(0)), + ) as _i18.Future<_i32.Uint8List>); + @override + _i31.StackTheme? getTheme({required String? themeId}) => + (super.noSuchMethod(Invocation.method( + #getTheme, + [], + {#themeId: themeId}, + )) as _i31.StackTheme?); +} diff --git a/test/widget_tests/wallet_card_test.dart b/test/widget_tests/wallet_card_test.dart index 834122da8..8598b382e 100644 --- a/test/widget_tests/wallet_card_test.dart +++ b/test/widget_tests/wallet_card_test.dart @@ -1,87 +1,63 @@ +import 'package:decimal/decimal.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockingjay/mockingjay.dart' as mockingjay; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart' as mockito; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/coins/bitcoin/bitcoin_wallet.dart'; import 'package:stackwallet/services/coins/coin_service.dart'; import 'package:stackwallet/services/coins/manager.dart'; import 'package:stackwallet/services/locale_service.dart'; import 'package:stackwallet/services/wallets.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_service.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; import 'package:stackwallet/widgets/wallet_card.dart'; -import 'package:tuple/tuple.dart'; +import '../sample_data/theme_json.dart'; import 'wallet_card_test.mocks.dart'; -// class MockNavigatorObserver extends Mock implements NavigatorObserver {} - -@GenerateMocks([Wallets, BitcoinWallet, LocaleService]) -void main() { - testWidgets("Test button pressed", (widgetTester) async { - final CoinServiceAPI wallet = MockBitcoinWallet(); - mockito.when(wallet.walletId).thenAnswer((realInvocation) => "wallet id"); - mockito.when(wallet.coin).thenAnswer((realInvocation) => Coin.bitcoin); - mockito - .when(wallet.walletName) - .thenAnswer((realInvocation) => "wallet name"); - - final wallets = MockWallets(); - final locale = MockLocaleService(); - final manager = Manager(wallet); - final managerProvider = ChangeNotifierProvider((ref) => manager); - - mockito - .when(wallets.getManagerProvider("wallet id")) - .thenAnswer((realInvocation) => managerProvider); - mockito.when(locale.locale).thenAnswer((_) => "en_US"); - - mockito - .when(wallets.getManagerProvider("wallet id")) - .thenAnswer((realInvocation) => managerProvider); - - final navigator = mockingjay.MockNavigator(); - mockingjay - .when(() => navigator.pushNamed("/wallet", - arguments: Tuple2("wallet id", managerProvider))) - .thenAnswer((_) async => {}); - - await widgetTester.pumpWidget( - ProviderScope( - overrides: [ - walletsChangeNotifierProvider.overrideWithValue(wallets), - localeServiceChangeNotifierProvider.overrideWithValue(locale), - ], - child: MaterialApp( - theme: ThemeData( - extensions: [ - StackColors.fromStackColorTheme(LightColors()), - ], - ), - home: mockingjay.MockNavigatorProvider( - navigator: navigator, - child: const WalletSheetCard( - walletId: "wallet id", - )), - ), - ), +/// quick amount constructor wrapper. Using an int is bad practice but for +/// testing with small amounts this should be fine +Amount _a(int i) => Amount.fromDecimal( + Decimal.fromInt(i), + fractionDigits: 8, ); - expect(find.byType(MaterialButton), findsOneWidget); - await widgetTester.tap(find.byType(MaterialButton)); - }); - +@GenerateMocks([ + Wallets, + BitcoinWallet, + LocaleService, + ThemeService, +]) +void main() { testWidgets('test widget loads correctly', (widgetTester) async { final CoinServiceAPI wallet = MockBitcoinWallet(); + final mockThemeService = MockThemeService(); + + mockito.when(mockThemeService.getTheme(themeId: "light")).thenAnswer( + (_) => StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ); mockito.when(wallet.walletId).thenAnswer((realInvocation) => "wallet id"); mockito.when(wallet.coin).thenAnswer((realInvocation) => Coin.bitcoin); mockito .when(wallet.walletName) .thenAnswer((realInvocation) => "wallet name"); + mockito.when(wallet.balance).thenAnswer( + (_) => Balance( + total: _a(0), + spendable: _a(0), + blockedTotal: _a(0), + pendingSpendable: _a(0), + ), + ); final wallets = MockWallets(); final manager = Manager(wallet); @@ -89,7 +65,7 @@ void main() { mockito.when(wallets.getManagerProvider("wallet id")).thenAnswer( (realInvocation) => ChangeNotifierProvider((ref) => manager)); - const walletSheetCard = WalletSheetCard( + const walletSheetCard = SimpleWalletCard( walletId: "wallet id", ); @@ -97,11 +73,17 @@ void main() { ProviderScope( overrides: [ walletsChangeNotifierProvider.overrideWithValue(wallets), + pThemeService.overrideWithValue(mockThemeService), ], child: MaterialApp( theme: ThemeData( extensions: [ - StackColors.fromStackColorTheme(LightColors()), + StackColors.fromStackColorTheme( + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ), ], ), home: const Material( @@ -110,6 +92,9 @@ void main() { ), ), ); + + await widgetTester.pumpAndSettle(); + expect(find.byWidget(walletSheetCard), findsOneWidget); }); } diff --git a/test/widget_tests/wallet_card_test.mocks.dart b/test/widget_tests/wallet_card_test.mocks.dart index e323911d4..af1d78fdd 100644 --- a/test/widget_tests/wallet_card_test.mocks.dart +++ b/test/widget_tests/wallet_card_test.mocks.dart @@ -3,26 +3,40 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i14; -import 'dart:ui' as _i16; +import 'dart:async' as _i21; +import 'dart:typed_data' as _i28; +import 'dart:ui' as _i23; -import 'package:decimal/decimal.dart' as _i9; +import 'package:bip32/bip32.dart' as _i16; +import 'package:bip47/bip47.dart' as _i18; +import 'package:bitcoindart/bitcoindart.dart' as _i13; import 'package:flutter/foundation.dart' as _i4; import 'package:flutter_riverpod/flutter_riverpod.dart' as _i5; import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i11; -import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i10; -import 'package:stackwallet/models/models.dart' as _i8; -import 'package:stackwallet/services/coins/bitcoin/bitcoin_wallet.dart' as _i17; +import 'package:stackwallet/db/isar/main_db.dart' as _i12; +import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i10; +import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i9; +import 'package:stackwallet/models/balance.dart' as _i11; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i17; +import 'package:stackwallet/models/isar/stack_theme.dart' as _i31; +import 'package:stackwallet/models/paymint/fee_object_model.dart' as _i8; +import 'package:stackwallet/models/signing_data.dart' as _i26; +import 'package:stackwallet/services/coins/bitcoin/bitcoin_wallet.dart' as _i24; import 'package:stackwallet/services/coins/manager.dart' as _i6; -import 'package:stackwallet/services/locale_service.dart' as _i18; +import 'package:stackwallet/services/locale_service.dart' as _i29; import 'package:stackwallet/services/node_service.dart' as _i3; import 'package:stackwallet/services/transaction_notification_tracker.dart' as _i7; -import 'package:stackwallet/services/wallets.dart' as _i12; +import 'package:stackwallet/services/wallets.dart' as _i19; import 'package:stackwallet/services/wallets_service.dart' as _i2; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i13; -import 'package:stackwallet/utilities/prefs.dart' as _i15; +import 'package:stackwallet/themes/theme_service.dart' as _i30; +import 'package:stackwallet/utilities/amount/amount.dart' as _i14; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i20; +import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart' as _i25; +import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' + as _i27; +import 'package:stackwallet/utilities/prefs.dart' as _i22; +import 'package:tuple/tuple.dart' as _i15; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -88,8 +102,8 @@ class _FakeTransactionNotificationTracker_4 extends _i1.SmartFake ); } -class _FakeUtxoData_5 extends _i1.SmartFake implements _i8.UtxoData { - _FakeUtxoData_5( +class _FakeFeeObject_5 extends _i1.SmartFake implements _i8.FeeObject { + _FakeFeeObject_5( Object parent, Invocation parentInvocation, ) : super( @@ -98,8 +112,8 @@ class _FakeUtxoData_5 extends _i1.SmartFake implements _i8.UtxoData { ); } -class _FakeDecimal_6 extends _i1.SmartFake implements _i9.Decimal { - _FakeDecimal_6( +class _FakeElectrumX_6 extends _i1.SmartFake implements _i9.ElectrumX { + _FakeElectrumX_6( Object parent, Invocation parentInvocation, ) : super( @@ -108,8 +122,9 @@ class _FakeDecimal_6 extends _i1.SmartFake implements _i9.Decimal { ); } -class _FakeFeeObject_7 extends _i1.SmartFake implements _i8.FeeObject { - _FakeFeeObject_7( +class _FakeCachedElectrumX_7 extends _i1.SmartFake + implements _i10.CachedElectrumX { + _FakeCachedElectrumX_7( Object parent, Invocation parentInvocation, ) : super( @@ -118,9 +133,8 @@ class _FakeFeeObject_7 extends _i1.SmartFake implements _i8.FeeObject { ); } -class _FakeTransactionData_8 extends _i1.SmartFake - implements _i8.TransactionData { - _FakeTransactionData_8( +class _FakeBalance_8 extends _i1.SmartFake implements _i11.Balance { + _FakeBalance_8( Object parent, Invocation parentInvocation, ) : super( @@ -129,8 +143,8 @@ class _FakeTransactionData_8 extends _i1.SmartFake ); } -class _FakeElectrumX_9 extends _i1.SmartFake implements _i10.ElectrumX { - _FakeElectrumX_9( +class _FakeMainDB_9 extends _i1.SmartFake implements _i12.MainDB { + _FakeMainDB_9( Object parent, Invocation parentInvocation, ) : super( @@ -139,9 +153,8 @@ class _FakeElectrumX_9 extends _i1.SmartFake implements _i10.ElectrumX { ); } -class _FakeCachedElectrumX_10 extends _i1.SmartFake - implements _i11.CachedElectrumX { - _FakeCachedElectrumX_10( +class _FakeNetworkType_10 extends _i1.SmartFake implements _i13.NetworkType { + _FakeNetworkType_10( Object parent, Invocation parentInvocation, ) : super( @@ -150,8 +163,7 @@ class _FakeCachedElectrumX_10 extends _i1.SmartFake ); } -class _FakeElectrumXNode_11 extends _i1.SmartFake - implements _i10.ElectrumXNode { +class _FakeElectrumXNode_11 extends _i1.SmartFake implements _i9.ElectrumXNode { _FakeElectrumXNode_11( Object parent, Invocation parentInvocation, @@ -161,10 +173,61 @@ class _FakeElectrumXNode_11 extends _i1.SmartFake ); } +class _FakeAmount_12 extends _i1.SmartFake implements _i14.Amount { + _FakeAmount_12( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeTuple2_13 extends _i1.SmartFake + implements _i15.Tuple2 { + _FakeTuple2_13( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeBIP32_14 extends _i1.SmartFake implements _i16.BIP32 { + _FakeBIP32_14( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAddress_15 extends _i1.SmartFake implements _i17.Address { + _FakeAddress_15( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePaymentCode_16 extends _i1.SmartFake implements _i18.PaymentCode { + _FakePaymentCode_16( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + /// A class which mocks [Wallets]. /// /// See the documentation for Mockito's code generation for more information. -class MockWallets extends _i1.Mock implements _i12.Wallets { +class MockWallets extends _i1.Mock implements _i19.Wallets { MockWallets() { _i1.throwOnMissingStub(this); } @@ -231,7 +294,7 @@ class MockWallets extends _i1.Mock implements _i12.Wallets { returnValueForMissingStub: null, ); @override - List getWalletIdsFor({required _i13.Coin? coin}) => + List getWalletIdsFor({required _i20.Coin? coin}) => (super.noSuchMethod( Invocation.method( #getWalletIdsFor, @@ -241,15 +304,28 @@ class MockWallets extends _i1.Mock implements _i12.Wallets { returnValue: [], ) as List); @override - Map<_i13.Coin, List<_i5.ChangeNotifierProvider<_i6.Manager>>> + List<_i15.Tuple2<_i20.Coin, List<_i5.ChangeNotifierProvider<_i6.Manager>>>> getManagerProvidersByCoin() => (super.noSuchMethod( Invocation.method( #getManagerProvidersByCoin, [], ), - returnValue: <_i13.Coin, - List<_i5.ChangeNotifierProvider<_i6.Manager>>>{}, - ) as Map<_i13.Coin, List<_i5.ChangeNotifierProvider<_i6.Manager>>>); + returnValue: < + _i15.Tuple2<_i20.Coin, + List<_i5.ChangeNotifierProvider<_i6.Manager>>>>[], + ) as List< + _i15.Tuple2<_i20.Coin, + List<_i5.ChangeNotifierProvider<_i6.Manager>>>>); + @override + List<_i5.ChangeNotifierProvider<_i6.Manager>> getManagerProvidersForCoin( + _i20.Coin? coin) => + (super.noSuchMethod( + Invocation.method( + #getManagerProvidersForCoin, + [coin], + ), + returnValue: <_i5.ChangeNotifierProvider<_i6.Manager>>[], + ) as List<_i5.ChangeNotifierProvider<_i6.Manager>>); @override _i5.ChangeNotifierProvider<_i6.Manager> getManagerProvider( String? walletId) => @@ -306,17 +382,17 @@ class MockWallets extends _i1.Mock implements _i12.Wallets { returnValueForMissingStub: null, ); @override - _i14.Future load(_i15.Prefs? prefs) => (super.noSuchMethod( + _i21.Future load(_i22.Prefs? prefs) => (super.noSuchMethod( Invocation.method( #load, [prefs], ), - returnValue: _i14.Future.value(), - returnValueForMissingStub: _i14.Future.value(), - ) as _i14.Future); + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); @override - _i14.Future loadAfterStackRestore( - _i15.Prefs? prefs, + _i21.Future loadAfterStackRestore( + _i22.Prefs? prefs, List<_i6.Manager>? managers, ) => (super.noSuchMethod( @@ -327,11 +403,11 @@ class MockWallets extends _i1.Mock implements _i12.Wallets { managers, ], ), - returnValue: _i14.Future.value(), - returnValueForMissingStub: _i14.Future.value(), - ) as _i14.Future); + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); @override - void addListener(_i16.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i23.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -339,7 +415,7 @@ class MockWallets extends _i1.Mock implements _i12.Wallets { returnValueForMissingStub: null, ); @override - void removeListener(_i16.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i23.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -359,13 +435,13 @@ class MockWallets extends _i1.Mock implements _i12.Wallets { /// A class which mocks [BitcoinWallet]. /// /// See the documentation for Mockito's code generation for more information. -class MockBitcoinWallet extends _i1.Mock implements _i17.BitcoinWallet { +class MockBitcoinWallet extends _i1.Mock implements _i24.BitcoinWallet { MockBitcoinWallet() { _i1.throwOnMissingStub(this); } @override - set timer(_i14.Timer? _timer) => super.noSuchMethod( + set timer(_i21.Timer? _timer) => super.noSuchMethod( Invocation.setter( #timer, _timer, @@ -390,19 +466,6 @@ class MockBitcoinWallet extends _i1.Mock implements _i17.BitcoinWallet { returnValueForMissingStub: null, ); @override - List<_i8.UtxoObject> get outputsList => (super.noSuchMethod( - Invocation.getter(#outputsList), - returnValue: <_i8.UtxoObject>[], - ) as List<_i8.UtxoObject>); - @override - set outputsList(List<_i8.UtxoObject>? _outputsList) => super.noSuchMethod( - Invocation.setter( - #outputsList, - _outputsList, - ), - returnValueForMissingStub: null, - ); - @override bool get longMutex => (super.noSuchMethod( Invocation.getter(#longMutex), returnValue: false, @@ -429,14 +492,6 @@ class MockBitcoinWallet extends _i1.Mock implements _i17.BitcoinWallet { returnValueForMissingStub: null, ); @override - set cachedTxData(_i8.TransactionData? _cachedTxData) => super.noSuchMethod( - Invocation.setter( - #cachedTxData, - _cachedTxData, - ), - returnValueForMissingStub: null, - ); - @override bool get isActive => (super.noSuchMethod( Invocation.getter(#isActive), returnValue: false, @@ -463,104 +518,74 @@ class MockBitcoinWallet extends _i1.Mock implements _i17.BitcoinWallet { returnValue: false, ) as bool); @override - _i13.Coin get coin => (super.noSuchMethod( + _i20.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i13.Coin.bitcoin, - ) as _i13.Coin); + returnValue: _i20.Coin.bitcoin, + ) as _i20.Coin); @override - _i14.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i14.Future>.value([]), - ) as _i14.Future>); + _i21.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i21.Future>.value(<_i17.UTXO>[]), + ) as _i21.Future>); @override - _i14.Future<_i8.UtxoData> get utxoData => (super.noSuchMethod( - Invocation.getter(#utxoData), - returnValue: _i14.Future<_i8.UtxoData>.value(_FakeUtxoData_5( - this, - Invocation.getter(#utxoData), - )), - ) as _i14.Future<_i8.UtxoData>); - @override - _i14.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), + _i21.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i14.Future>.value(<_i8.UtxoObject>[]), - ) as _i14.Future>); + _i21.Future>.value(<_i17.Transaction>[]), + ) as _i21.Future>); @override - _i14.Future<_i9.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i14.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#availableBalance), - )), - ) as _i14.Future<_i9.Decimal>); - @override - _i14.Future<_i9.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i14.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i14.Future<_i9.Decimal>); - @override - _i14.Future<_i9.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i14.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i14.Future<_i9.Decimal>); - @override - _i14.Future<_i9.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i14.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#totalBalance), - )), - ) as _i14.Future<_i9.Decimal>); - @override - _i14.Future get currentReceivingAddress => (super.noSuchMethod( + _i21.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i14.Future.value(''), - ) as _i14.Future); + returnValue: _i21.Future.value(''), + ) as _i21.Future); @override - _i14.Future get currentLegacyReceivingAddress => (super.noSuchMethod( - Invocation.getter(#currentLegacyReceivingAddress), - returnValue: _i14.Future.value(''), - ) as _i14.Future); + _i21.Future get currentChangeAddress => (super.noSuchMethod( + Invocation.getter(#currentChangeAddress), + returnValue: _i21.Future.value(''), + ) as _i21.Future); @override - _i14.Future get currentReceivingAddressP2SH => (super.noSuchMethod( - Invocation.getter(#currentReceivingAddressP2SH), - returnValue: _i14.Future.value(''), - ) as _i14.Future); + _i21.Future get currentChangeAddressP2PKH => (super.noSuchMethod( + Invocation.getter(#currentChangeAddressP2PKH), + returnValue: _i21.Future.value(''), + ) as _i21.Future); @override bool get hasCalledExit => (super.noSuchMethod( Invocation.getter(#hasCalledExit), returnValue: false, ) as bool); @override - _i14.Future<_i8.FeeObject> get fees => (super.noSuchMethod( + _i21.Future<_i8.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i14.Future<_i8.FeeObject>.value(_FakeFeeObject_7( + returnValue: _i21.Future<_i8.FeeObject>.value(_FakeFeeObject_5( this, Invocation.getter(#fees), )), - ) as _i14.Future<_i8.FeeObject>); + ) as _i21.Future<_i8.FeeObject>); @override - _i14.Future get maxFee => (super.noSuchMethod( + _i21.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i14.Future.value(0), - ) as _i14.Future); + returnValue: _i21.Future.value(0), + ) as _i21.Future); @override - _i14.Future> get mnemonic => (super.noSuchMethod( + _i21.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i14.Future>.value([]), - ) as _i14.Future>); + returnValue: _i21.Future>.value([]), + ) as _i21.Future>); @override - _i14.Future get chainHeight => (super.noSuchMethod( + _i21.Future get mnemonicString => (super.noSuchMethod( + Invocation.getter(#mnemonicString), + returnValue: _i21.Future.value(), + ) as _i21.Future); + @override + _i21.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i21.Future.value(), + ) as _i21.Future); + @override + _i21.Future get chainHeight => (super.noSuchMethod( Invocation.getter(#chainHeight), - returnValue: _i14.Future.value(0), - ) as _i14.Future); + returnValue: _i21.Future.value(0), + ) as _i21.Future); @override int get storedChainHeight => (super.noSuchMethod( Invocation.getter(#storedChainHeight), @@ -590,15 +615,6 @@ class MockBitcoinWallet extends _i1.Mock implements _i17.BitcoinWallet { returnValue: false, ) as bool); @override - _i14.Future<_i8.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), - returnValue: - _i14.Future<_i8.TransactionData>.value(_FakeTransactionData_8( - this, - Invocation.getter(#transactionData), - )), - ) as _i14.Future<_i8.TransactionData>); - @override String get walletId => (super.noSuchMethod( Invocation.getter(#walletId), returnValue: '', @@ -617,21 +633,34 @@ class MockBitcoinWallet extends _i1.Mock implements _i17.BitcoinWallet { returnValueForMissingStub: null, ); @override - _i10.ElectrumX get electrumXClient => (super.noSuchMethod( + _i9.ElectrumX get electrumXClient => (super.noSuchMethod( Invocation.getter(#electrumXClient), - returnValue: _FakeElectrumX_9( + returnValue: _FakeElectrumX_6( this, Invocation.getter(#electrumXClient), ), - ) as _i10.ElectrumX); + ) as _i9.ElectrumX); @override - _i11.CachedElectrumX get cachedElectrumXClient => (super.noSuchMethod( + _i10.CachedElectrumX get cachedElectrumXClient => (super.noSuchMethod( Invocation.getter(#cachedElectrumXClient), - returnValue: _FakeCachedElectrumX_10( + returnValue: _FakeCachedElectrumX_7( this, Invocation.getter(#cachedElectrumXClient), ), - ) as _i11.CachedElectrumX); + ) as _i10.CachedElectrumX); + @override + _i11.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_8( + this, + Invocation.getter(#balance), + ), + ) as _i11.Balance); + @override + _i21.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i21.Future.value(''), + ) as _i21.Future); @override set onIsActiveWalletChanged(void Function(bool)? _onIsActiveWalletChanged) => super.noSuchMethod( @@ -642,38 +671,44 @@ class MockBitcoinWallet extends _i1.Mock implements _i17.BitcoinWallet { returnValueForMissingStub: null, ); @override - _i14.Future exit() => (super.noSuchMethod( + _i12.MainDB get db => (super.noSuchMethod( + Invocation.getter(#db), + returnValue: _FakeMainDB_9( + this, + Invocation.getter(#db), + ), + ) as _i12.MainDB); + @override + _i13.NetworkType get networkType => (super.noSuchMethod( + Invocation.getter(#networkType), + returnValue: _FakeNetworkType_10( + this, + Invocation.getter(#networkType), + ), + ) as _i13.NetworkType); + @override + _i21.Future exit() => (super.noSuchMethod( Invocation.method( #exit, [], ), - returnValue: _i14.Future.value(), - returnValueForMissingStub: _i14.Future.value(), - ) as _i14.Future); + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); @override - _i14.Future updateStoredChainHeight({required int? newHeight}) => - (super.noSuchMethod( - Invocation.method( - #updateStoredChainHeight, - [], - {#newHeight: newHeight}, - ), - returnValue: _i14.Future.value(), - returnValueForMissingStub: _i14.Future.value(), - ) as _i14.Future); - @override - _i17.DerivePathType addressType({required String? address}) => + _i25.DerivePathType addressType({required String? address}) => (super.noSuchMethod( Invocation.method( #addressType, [], {#address: address}, ), - returnValue: _i17.DerivePathType.bip44, - ) as _i17.DerivePathType); + returnValue: _i25.DerivePathType.bip44, + ) as _i25.DerivePathType); @override - _i14.Future recoverFromMnemonic({ + _i21.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -684,55 +719,55 @@ class MockBitcoinWallet extends _i1.Mock implements _i17.BitcoinWallet { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i14.Future.value(), - returnValueForMissingStub: _i14.Future.value(), - ) as _i14.Future); + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); @override - _i14.Future getTransactionCacheEarly(List? allAddresses) => + _i21.Future getTransactionCacheEarly(List? allAddresses) => (super.noSuchMethod( Invocation.method( #getTransactionCacheEarly, [allAddresses], ), - returnValue: _i14.Future.value(), - returnValueForMissingStub: _i14.Future.value(), - ) as _i14.Future); + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); @override - _i14.Future refreshIfThereIsNewData() => (super.noSuchMethod( + _i21.Future refreshIfThereIsNewData() => (super.noSuchMethod( Invocation.method( #refreshIfThereIsNewData, [], ), - returnValue: _i14.Future.value(false), - ) as _i14.Future); + returnValue: _i21.Future.value(false), + ) as _i21.Future); @override - _i14.Future getAllTxsToWatch(_i8.TransactionData? txData) => - (super.noSuchMethod( + _i21.Future getAllTxsToWatch() => (super.noSuchMethod( Invocation.method( #getAllTxsToWatch, - [txData], + [], ), - returnValue: _i14.Future.value(), - returnValueForMissingStub: _i14.Future.value(), - ) as _i14.Future); + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); @override - _i14.Future refresh() => (super.noSuchMethod( + _i21.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i14.Future.value(), - returnValueForMissingStub: _i14.Future.value(), - ) as _i14.Future); + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); @override - _i14.Future> prepareSend({ + _i21.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i14.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -741,49 +776,31 @@ class MockBitcoinWallet extends _i1.Mock implements _i17.BitcoinWallet { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i14.Future>.value({}), - ) as _i14.Future>); + _i21.Future>.value({}), + ) as _i21.Future>); @override - _i14.Future confirmSend({required Map? txData}) => + _i21.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i14.Future.value(''), - ) as _i14.Future); + returnValue: _i21.Future.value(''), + ) as _i21.Future); @override - _i14.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i14.Future.value(''), - ) as _i14.Future); - @override - _i14.Future testNetworkConnection() => (super.noSuchMethod( + _i21.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i14.Future.value(false), - ) as _i14.Future); + returnValue: _i21.Future.value(false), + ) as _i21.Future); @override void startNetworkAlivePinging() => super.noSuchMethod( Invocation.method( @@ -801,33 +818,33 @@ class MockBitcoinWallet extends _i1.Mock implements _i17.BitcoinWallet { returnValueForMissingStub: null, ); @override - _i14.Future initializeNew() => (super.noSuchMethod( + _i21.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i14.Future.value(), - returnValueForMissingStub: _i14.Future.value(), - ) as _i14.Future); + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); @override - _i14.Future initializeExisting() => (super.noSuchMethod( + _i21.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i14.Future.value(), - returnValueForMissingStub: _i14.Future.value(), - ) as _i14.Future); + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); @override - _i14.Future updateSentCachedTxData(Map? txData) => + _i21.Future updateSentCachedTxData(Map? txData) => (super.noSuchMethod( Invocation.method( #updateSentCachedTxData, [txData], ), - returnValue: _i14.Future.value(), - returnValueForMissingStub: _i14.Future.value(), - ) as _i14.Future); + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -837,112 +854,69 @@ class MockBitcoinWallet extends _i1.Mock implements _i17.BitcoinWallet { returnValue: false, ) as bool); @override - _i14.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i21.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i14.Future.value(), - returnValueForMissingStub: _i14.Future.value(), - ) as _i14.Future); + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); @override - _i14.Future<_i10.ElectrumXNode> getCurrentNode() => (super.noSuchMethod( + _i21.Future<_i9.ElectrumXNode> getCurrentNode() => (super.noSuchMethod( Invocation.method( #getCurrentNode, [], ), - returnValue: - _i14.Future<_i10.ElectrumXNode>.value(_FakeElectrumXNode_11( + returnValue: _i21.Future<_i9.ElectrumXNode>.value(_FakeElectrumXNode_11( this, Invocation.method( #getCurrentNode, [], ), )), - ) as _i14.Future<_i10.ElectrumXNode>); + ) as _i21.Future<_i9.ElectrumXNode>); @override - _i14.Future addDerivation({ - required int? chain, - required String? address, - required String? pubKey, - required String? wif, - required _i17.DerivePathType? derivePathType, - }) => - (super.noSuchMethod( - Invocation.method( - #addDerivation, - [], - { - #chain: chain, - #address: address, - #pubKey: pubKey, - #wif: wif, - #derivePathType: derivePathType, - }, - ), - returnValue: _i14.Future.value(), - returnValueForMissingStub: _i14.Future.value(), - ) as _i14.Future); - @override - _i14.Future addDerivations({ - required int? chain, - required _i17.DerivePathType? derivePathType, - required Map? derivationsToAdd, - }) => - (super.noSuchMethod( - Invocation.method( - #addDerivations, - [], - { - #chain: chain, - #derivePathType: derivePathType, - #derivationsToAdd: derivationsToAdd, - }, - ), - returnValue: _i14.Future.value(), - returnValueForMissingStub: _i14.Future.value(), - ) as _i14.Future); - @override - _i14.Future getTxCount({required String? address}) => - (super.noSuchMethod( - Invocation.method( - #getTxCount, - [], - {#address: address}, - ), - returnValue: _i14.Future.value(0), - ) as _i14.Future); - @override - _i14.Future checkCurrentReceivingAddressesForTransactions() => - (super.noSuchMethod( - Invocation.method( - #checkCurrentReceivingAddressesForTransactions, - [], - ), - returnValue: _i14.Future.value(), - returnValueForMissingStub: _i14.Future.value(), - ) as _i14.Future); - @override - _i14.Future checkCurrentChangeAddressesForTransactions() => - (super.noSuchMethod( - Invocation.method( - #checkCurrentChangeAddressesForTransactions, - [], - ), - returnValue: _i14.Future.value(), - returnValueForMissingStub: _i14.Future.value(), - ) as _i14.Future); - @override - _i14.Future>> fastFetch( + _i21.Future>> fastFetch( List? allTxHashes) => (super.noSuchMethod( Invocation.method( #fastFetch, [allTxHashes], ), - returnValue: _i14.Future>>.value( + returnValue: _i21.Future>>.value( >[]), - ) as _i14.Future>>); + ) as _i21.Future>>); + @override + _i21.Future getTxCount({required String? address}) => + (super.noSuchMethod( + Invocation.method( + #getTxCount, + [], + {#address: address}, + ), + returnValue: _i21.Future.value(0), + ) as _i21.Future); + @override + _i21.Future checkCurrentReceivingAddressesForTransactions() => + (super.noSuchMethod( + Invocation.method( + #checkCurrentReceivingAddressesForTransactions, + [], + ), + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); + @override + _i21.Future checkCurrentChangeAddressesForTransactions() => + (super.noSuchMethod( + Invocation.method( + #checkCurrentChangeAddressesForTransactions, + [], + ), + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); @override int estimateTxFee({ required int? vSize, @@ -960,42 +934,42 @@ class MockBitcoinWallet extends _i1.Mock implements _i17.BitcoinWallet { returnValue: 0, ) as int); @override - dynamic coinSelection( - int? satoshiAmountToSend, - int? selectedTxFeeRate, - String? _recipientAddress, - bool? isSendAll, { + dynamic coinSelection({ + required int? satoshiAmountToSend, + required int? selectedTxFeeRate, + required String? recipientAddress, + required bool? coinControl, + required bool? isSendAll, int? additionalOutputs = 0, - List<_i8.UtxoObject>? utxos, + List<_i17.UTXO>? utxos, }) => super.noSuchMethod(Invocation.method( #coinSelection, - [ - satoshiAmountToSend, - selectedTxFeeRate, - _recipientAddress, - isSendAll, - ], + [], { + #satoshiAmountToSend: satoshiAmountToSend, + #selectedTxFeeRate: selectedTxFeeRate, + #recipientAddress: recipientAddress, + #coinControl: coinControl, + #isSendAll: isSendAll, #additionalOutputs: additionalOutputs, #utxos: utxos, }, )); @override - _i14.Future> fetchBuildTxData( - List<_i8.UtxoObject>? utxosToUse) => + _i21.Future> fetchBuildTxData( + List<_i17.UTXO>? utxosToUse) => (super.noSuchMethod( Invocation.method( #fetchBuildTxData, [utxosToUse], ), returnValue: - _i14.Future>.value({}), - ) as _i14.Future>); + _i21.Future>.value(<_i26.SigningData>[]), + ) as _i21.Future>); @override - _i14.Future> buildTransaction({ - required List<_i8.UtxoObject>? utxosToUse, - required Map? utxoSigningData, + _i21.Future> buildTransaction({ + required List<_i26.SigningData>? utxoSigningData, required List? recipients, required List? satoshiAmounts, }) => @@ -1004,17 +978,16 @@ class MockBitcoinWallet extends _i1.Mock implements _i17.BitcoinWallet { #buildTransaction, [], { - #utxosToUse: utxosToUse, #utxoSigningData: utxoSigningData, #recipients: recipients, #satoshiAmounts: satoshiAmounts, }, ), returnValue: - _i14.Future>.value({}), - ) as _i14.Future>); + _i21.Future>.value({}), + ) as _i21.Future>); @override - _i14.Future fullRescan( + _i21.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -1026,26 +999,35 @@ class MockBitcoinWallet extends _i1.Mock implements _i17.BitcoinWallet { maxNumberOfIndexesToCheck, ], ), - returnValue: _i14.Future.value(), - returnValueForMissingStub: _i14.Future.value(), - ) as _i14.Future); + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); @override - _i14.Future estimateFeeFor( - int? satoshiAmount, + _i21.Future<_i14.Amount> estimateFeeFor( + _i14.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i14.Future.value(0), - ) as _i14.Future); + returnValue: _i21.Future<_i14.Amount>.value(_FakeAmount_12( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i21.Future<_i14.Amount>); @override - int roughFeeEstimate( + _i14.Amount roughFeeEstimate( int? inputCount, int? outputCount, int? feeRatePerKB, @@ -1059,30 +1041,668 @@ class MockBitcoinWallet extends _i1.Mock implements _i17.BitcoinWallet { feeRatePerKB, ], ), - returnValue: 0, - ) as int); + returnValue: _FakeAmount_12( + this, + Invocation.method( + #roughFeeEstimate, + [ + inputCount, + outputCount, + feeRatePerKB, + ], + ), + ), + ) as _i14.Amount); @override - int sweepAllEstimate(int? feeRate) => (super.noSuchMethod( + _i21.Future<_i14.Amount> sweepAllEstimate(int? feeRate) => + (super.noSuchMethod( Invocation.method( #sweepAllEstimate, [feeRate], ), - returnValue: 0, - ) as int); + returnValue: _i21.Future<_i14.Amount>.value(_FakeAmount_12( + this, + Invocation.method( + #sweepAllEstimate, + [feeRate], + ), + )), + ) as _i21.Future<_i14.Amount>); @override - _i14.Future generateNewAddress() => (super.noSuchMethod( + _i21.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i14.Future.value(false), - ) as _i14.Future); + returnValue: _i21.Future.value(false), + ) as _i21.Future); + @override + void initCache( + String? walletId, + _i20.Coin? coin, + ) => + super.noSuchMethod( + Invocation.method( + #initCache, + [ + walletId, + coin, + ], + ), + returnValueForMissingStub: null, + ); + @override + _i21.Future updateCachedId(String? id) => (super.noSuchMethod( + Invocation.method( + #updateCachedId, + [id], + ), + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); + @override + int getCachedChainHeight() => (super.noSuchMethod( + Invocation.method( + #getCachedChainHeight, + [], + ), + returnValue: 0, + ) as int); + @override + _i21.Future updateCachedChainHeight(int? height) => (super.noSuchMethod( + Invocation.method( + #updateCachedChainHeight, + [height], + ), + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); + @override + bool getCachedIsFavorite() => (super.noSuchMethod( + Invocation.method( + #getCachedIsFavorite, + [], + ), + returnValue: false, + ) as bool); + @override + _i21.Future updateCachedIsFavorite(bool? isFavorite) => + (super.noSuchMethod( + Invocation.method( + #updateCachedIsFavorite, + [isFavorite], + ), + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); + @override + _i11.Balance getCachedBalance() => (super.noSuchMethod( + Invocation.method( + #getCachedBalance, + [], + ), + returnValue: _FakeBalance_8( + this, + Invocation.method( + #getCachedBalance, + [], + ), + ), + ) as _i11.Balance); + @override + _i21.Future updateCachedBalance(_i11.Balance? balance) => + (super.noSuchMethod( + Invocation.method( + #updateCachedBalance, + [balance], + ), + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); + @override + _i11.Balance getCachedBalanceSecondary() => (super.noSuchMethod( + Invocation.method( + #getCachedBalanceSecondary, + [], + ), + returnValue: _FakeBalance_8( + this, + Invocation.method( + #getCachedBalanceSecondary, + [], + ), + ), + ) as _i11.Balance); + @override + _i21.Future updateCachedBalanceSecondary(_i11.Balance? balance) => + (super.noSuchMethod( + Invocation.method( + #updateCachedBalanceSecondary, + [balance], + ), + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); + @override + List getWalletTokenContractAddresses() => (super.noSuchMethod( + Invocation.method( + #getWalletTokenContractAddresses, + [], + ), + returnValue: [], + ) as List); + @override + _i21.Future updateWalletTokenContractAddresses( + List? contractAddresses) => + (super.noSuchMethod( + Invocation.method( + #updateWalletTokenContractAddresses, + [contractAddresses], + ), + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); + @override + void initWalletDB({_i12.MainDB? mockableOverride}) => super.noSuchMethod( + Invocation.method( + #initWalletDB, + [], + {#mockableOverride: mockableOverride}, + ), + returnValueForMissingStub: null, + ); + @override + _i21.Future<_i15.Tuple2<_i17.Transaction, _i17.Address>> parseTransaction( + Map? txData, + dynamic electrumxClient, + List<_i17.Address>? myAddresses, + _i20.Coin? coin, + int? minConfirms, + String? walletId, + ) => + (super.noSuchMethod( + Invocation.method( + #parseTransaction, + [ + txData, + electrumxClient, + myAddresses, + coin, + minConfirms, + walletId, + ], + ), + returnValue: + _i21.Future<_i15.Tuple2<_i17.Transaction, _i17.Address>>.value( + _FakeTuple2_13<_i17.Transaction, _i17.Address>( + this, + Invocation.method( + #parseTransaction, + [ + txData, + electrumxClient, + myAddresses, + coin, + minConfirms, + walletId, + ], + ), + )), + ) as _i21.Future<_i15.Tuple2<_i17.Transaction, _i17.Address>>); + @override + void initPaynymWalletInterface({ + required String? walletId, + required String? walletName, + required _i13.NetworkType? network, + required _i20.Coin? coin, + required _i12.MainDB? db, + required _i9.ElectrumX? electrumXClient, + required _i27.SecureStorageInterface? secureStorage, + required int? dustLimit, + required int? dustLimitP2PKH, + required int? minConfirms, + required _i21.Future Function()? getMnemonicString, + required _i21.Future Function()? getMnemonicPassphrase, + required _i21.Future Function()? getChainHeight, + required _i21.Future Function()? getCurrentChangeAddress, + required int Function({ + required int feeRatePerKB, + required int vSize, + })? + estimateTxFee, + required _i21.Future> Function({ + required String address, + required _i14.Amount amount, + Map? args, + })? + prepareSend, + required _i21.Future Function({required String address})? getTxCount, + required _i21.Future> Function(List<_i17.UTXO>)? + fetchBuildTxData, + required _i21.Future Function()? refresh, + required _i21.Future Function()? checkChangeAddressForTransactions, + }) => + super.noSuchMethod( + Invocation.method( + #initPaynymWalletInterface, + [], + { + #walletId: walletId, + #walletName: walletName, + #network: network, + #coin: coin, + #db: db, + #electrumXClient: electrumXClient, + #secureStorage: secureStorage, + #dustLimit: dustLimit, + #dustLimitP2PKH: dustLimitP2PKH, + #minConfirms: minConfirms, + #getMnemonicString: getMnemonicString, + #getMnemonicPassphrase: getMnemonicPassphrase, + #getChainHeight: getChainHeight, + #getCurrentChangeAddress: getCurrentChangeAddress, + #estimateTxFee: estimateTxFee, + #prepareSend: prepareSend, + #getTxCount: getTxCount, + #fetchBuildTxData: fetchBuildTxData, + #refresh: refresh, + #checkChangeAddressForTransactions: + checkChangeAddressForTransactions, + }, + ), + returnValueForMissingStub: null, + ); + @override + _i21.Future<_i16.BIP32> getBip47BaseNode() => (super.noSuchMethod( + Invocation.method( + #getBip47BaseNode, + [], + ), + returnValue: _i21.Future<_i16.BIP32>.value(_FakeBIP32_14( + this, + Invocation.method( + #getBip47BaseNode, + [], + ), + )), + ) as _i21.Future<_i16.BIP32>); + @override + _i21.Future<_i28.Uint8List> getPrivateKeyForPaynymReceivingAddress({ + required String? paymentCodeString, + required int? index, + }) => + (super.noSuchMethod( + Invocation.method( + #getPrivateKeyForPaynymReceivingAddress, + [], + { + #paymentCodeString: paymentCodeString, + #index: index, + }, + ), + returnValue: _i21.Future<_i28.Uint8List>.value(_i28.Uint8List(0)), + ) as _i21.Future<_i28.Uint8List>); + @override + _i21.Future<_i17.Address> currentReceivingPaynymAddress({ + required _i18.PaymentCode? sender, + required bool? isSegwit, + }) => + (super.noSuchMethod( + Invocation.method( + #currentReceivingPaynymAddress, + [], + { + #sender: sender, + #isSegwit: isSegwit, + }, + ), + returnValue: _i21.Future<_i17.Address>.value(_FakeAddress_15( + this, + Invocation.method( + #currentReceivingPaynymAddress, + [], + { + #sender: sender, + #isSegwit: isSegwit, + }, + ), + )), + ) as _i21.Future<_i17.Address>); + @override + _i21.Future checkCurrentPaynymReceivingAddressForTransactions({ + required _i18.PaymentCode? sender, + required bool? isSegwit, + }) => + (super.noSuchMethod( + Invocation.method( + #checkCurrentPaynymReceivingAddressForTransactions, + [], + { + #sender: sender, + #isSegwit: isSegwit, + }, + ), + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); + @override + _i21.Future checkAllCurrentReceivingPaynymAddressesForTransactions() => + (super.noSuchMethod( + Invocation.method( + #checkAllCurrentReceivingPaynymAddressesForTransactions, + [], + ), + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); + @override + _i21.Future<_i16.BIP32> deriveNotificationBip32Node() => (super.noSuchMethod( + Invocation.method( + #deriveNotificationBip32Node, + [], + ), + returnValue: _i21.Future<_i16.BIP32>.value(_FakeBIP32_14( + this, + Invocation.method( + #deriveNotificationBip32Node, + [], + ), + )), + ) as _i21.Future<_i16.BIP32>); + @override + _i21.Future<_i18.PaymentCode> getPaymentCode({required bool? isSegwit}) => + (super.noSuchMethod( + Invocation.method( + #getPaymentCode, + [], + {#isSegwit: isSegwit}, + ), + returnValue: _i21.Future<_i18.PaymentCode>.value(_FakePaymentCode_16( + this, + Invocation.method( + #getPaymentCode, + [], + {#isSegwit: isSegwit}, + ), + )), + ) as _i21.Future<_i18.PaymentCode>); + @override + _i21.Future<_i28.Uint8List> signWithNotificationKey(_i28.Uint8List? data) => + (super.noSuchMethod( + Invocation.method( + #signWithNotificationKey, + [data], + ), + returnValue: _i21.Future<_i28.Uint8List>.value(_i28.Uint8List(0)), + ) as _i21.Future<_i28.Uint8List>); + @override + _i21.Future signStringWithNotificationKey(String? data) => + (super.noSuchMethod( + Invocation.method( + #signStringWithNotificationKey, + [data], + ), + returnValue: _i21.Future.value(''), + ) as _i21.Future); + @override + _i21.Future> preparePaymentCodeSend({ + required _i18.PaymentCode? paymentCode, + required bool? isSegwit, + required _i14.Amount? amount, + Map? args, + }) => + (super.noSuchMethod( + Invocation.method( + #preparePaymentCodeSend, + [], + { + #paymentCode: paymentCode, + #isSegwit: isSegwit, + #amount: amount, + #args: args, + }, + ), + returnValue: + _i21.Future>.value({}), + ) as _i21.Future>); + @override + _i21.Future<_i17.Address> nextUnusedSendAddressFrom({ + required _i18.PaymentCode? pCode, + required bool? isSegwit, + required _i16.BIP32? privateKeyNode, + int? startIndex = 0, + }) => + (super.noSuchMethod( + Invocation.method( + #nextUnusedSendAddressFrom, + [], + { + #pCode: pCode, + #isSegwit: isSegwit, + #privateKeyNode: privateKeyNode, + #startIndex: startIndex, + }, + ), + returnValue: _i21.Future<_i17.Address>.value(_FakeAddress_15( + this, + Invocation.method( + #nextUnusedSendAddressFrom, + [], + { + #pCode: pCode, + #isSegwit: isSegwit, + #privateKeyNode: privateKeyNode, + #startIndex: startIndex, + }, + ), + )), + ) as _i21.Future<_i17.Address>); + @override + _i21.Future> prepareNotificationTx({ + required int? selectedTxFeeRate, + required String? targetPaymentCodeString, + int? additionalOutputs = 0, + List<_i17.UTXO>? utxos, + }) => + (super.noSuchMethod( + Invocation.method( + #prepareNotificationTx, + [], + { + #selectedTxFeeRate: selectedTxFeeRate, + #targetPaymentCodeString: targetPaymentCodeString, + #additionalOutputs: additionalOutputs, + #utxos: utxos, + }, + ), + returnValue: + _i21.Future>.value({}), + ) as _i21.Future>); + @override + _i21.Future broadcastNotificationTx( + {required Map? preparedTx}) => + (super.noSuchMethod( + Invocation.method( + #broadcastNotificationTx, + [], + {#preparedTx: preparedTx}, + ), + returnValue: _i21.Future.value(''), + ) as _i21.Future); + @override + _i21.Future hasConnected(String? paymentCodeString) => + (super.noSuchMethod( + Invocation.method( + #hasConnected, + [paymentCodeString], + ), + returnValue: _i21.Future.value(false), + ) as _i21.Future); + @override + _i21.Future<_i18.PaymentCode?> unBlindedPaymentCodeFromTransaction( + {required _i17.Transaction? transaction}) => + (super.noSuchMethod( + Invocation.method( + #unBlindedPaymentCodeFromTransaction, + [], + {#transaction: transaction}, + ), + returnValue: _i21.Future<_i18.PaymentCode?>.value(), + ) as _i21.Future<_i18.PaymentCode?>); + @override + _i21.Future<_i18.PaymentCode?> unBlindedPaymentCodeFromTransactionBad( + {required _i17.Transaction? transaction}) => + (super.noSuchMethod( + Invocation.method( + #unBlindedPaymentCodeFromTransactionBad, + [], + {#transaction: transaction}, + ), + returnValue: _i21.Future<_i18.PaymentCode?>.value(), + ) as _i21.Future<_i18.PaymentCode?>); + @override + _i21.Future> + getAllPaymentCodesFromNotificationTransactions() => (super.noSuchMethod( + Invocation.method( + #getAllPaymentCodesFromNotificationTransactions, + [], + ), + returnValue: + _i21.Future>.value(<_i18.PaymentCode>[]), + ) as _i21.Future>); + @override + _i21.Future checkForNotificationTransactionsTo( + Set? otherCodeStrings) => + (super.noSuchMethod( + Invocation.method( + #checkForNotificationTransactionsTo, + [otherCodeStrings], + ), + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); + @override + _i21.Future restoreAllHistory({ + required int? maxUnusedAddressGap, + required int? maxNumberOfIndexesToCheck, + required Set? paymentCodeStrings, + }) => + (super.noSuchMethod( + Invocation.method( + #restoreAllHistory, + [], + { + #maxUnusedAddressGap: maxUnusedAddressGap, + #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + #paymentCodeStrings: paymentCodeStrings, + }, + ), + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); + @override + _i21.Future restoreHistoryWith({ + required _i18.PaymentCode? other, + required bool? checkSegwitAsWell, + required int? maxUnusedAddressGap, + required int? maxNumberOfIndexesToCheck, + }) => + (super.noSuchMethod( + Invocation.method( + #restoreHistoryWith, + [], + { + #other: other, + #checkSegwitAsWell: checkSegwitAsWell, + #maxUnusedAddressGap: maxUnusedAddressGap, + #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + }, + ), + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); + @override + _i21.Future<_i17.Address> getMyNotificationAddress() => (super.noSuchMethod( + Invocation.method( + #getMyNotificationAddress, + [], + ), + returnValue: _i21.Future<_i17.Address>.value(_FakeAddress_15( + this, + Invocation.method( + #getMyNotificationAddress, + [], + ), + )), + ) as _i21.Future<_i17.Address>); + @override + _i21.Future> lookupKey(String? paymentCodeString) => + (super.noSuchMethod( + Invocation.method( + #lookupKey, + [paymentCodeString], + ), + returnValue: _i21.Future>.value([]), + ) as _i21.Future>); + @override + _i21.Future paymentCodeStringByKey(String? key) => + (super.noSuchMethod( + Invocation.method( + #paymentCodeStringByKey, + [key], + ), + returnValue: _i21.Future.value(), + ) as _i21.Future); + @override + _i21.Future storeCode(String? paymentCodeString) => + (super.noSuchMethod( + Invocation.method( + #storeCode, + [paymentCodeString], + ), + returnValue: _i21.Future.value(''), + ) as _i21.Future); + @override + void initCoinControlInterface({ + required String? walletId, + required String? walletName, + required _i20.Coin? coin, + required _i12.MainDB? db, + required _i21.Future Function()? getChainHeight, + required _i21.Future Function(_i11.Balance)? refreshedBalanceCallback, + }) => + super.noSuchMethod( + Invocation.method( + #initCoinControlInterface, + [], + { + #walletId: walletId, + #walletName: walletName, + #coin: coin, + #db: db, + #getChainHeight: getChainHeight, + #refreshedBalanceCallback: refreshedBalanceCallback, + }, + ), + returnValueForMissingStub: null, + ); + @override + _i21.Future refreshBalance({bool? notify = false}) => + (super.noSuchMethod( + Invocation.method( + #refreshBalance, + [], + {#notify: notify}, + ), + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); } /// A class which mocks [LocaleService]. /// /// See the documentation for Mockito's code generation for more information. -class MockLocaleService extends _i1.Mock implements _i18.LocaleService { +class MockLocaleService extends _i1.Mock implements _i29.LocaleService { MockLocaleService() { _i1.throwOnMissingStub(this); } @@ -1098,17 +1718,17 @@ class MockLocaleService extends _i1.Mock implements _i18.LocaleService { returnValue: false, ) as bool); @override - _i14.Future loadLocale({bool? notify = true}) => (super.noSuchMethod( + _i21.Future loadLocale({bool? notify = true}) => (super.noSuchMethod( Invocation.method( #loadLocale, [], {#notify: notify}, ), - returnValue: _i14.Future.value(), - returnValueForMissingStub: _i14.Future.value(), - ) as _i14.Future); + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); @override - void addListener(_i16.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i23.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -1116,7 +1736,7 @@ class MockLocaleService extends _i1.Mock implements _i18.LocaleService { returnValueForMissingStub: null, ); @override - void removeListener(_i16.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i23.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -1140,3 +1760,93 @@ class MockLocaleService extends _i1.Mock implements _i18.LocaleService { returnValueForMissingStub: null, ); } + +/// A class which mocks [ThemeService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockThemeService extends _i1.Mock implements _i30.ThemeService { + MockThemeService() { + _i1.throwOnMissingStub(this); + } + + @override + _i12.MainDB get db => (super.noSuchMethod( + Invocation.getter(#db), + returnValue: _FakeMainDB_9( + this, + Invocation.getter(#db), + ), + ) as _i12.MainDB); + @override + List<_i31.StackTheme> get installedThemes => (super.noSuchMethod( + Invocation.getter(#installedThemes), + returnValue: <_i31.StackTheme>[], + ) as List<_i31.StackTheme>); + @override + void init(_i12.MainDB? db) => super.noSuchMethod( + Invocation.method( + #init, + [db], + ), + returnValueForMissingStub: null, + ); + @override + _i21.Future install({required _i28.Uint8List? themeArchiveData}) => + (super.noSuchMethod( + Invocation.method( + #install, + [], + {#themeArchiveData: themeArchiveData}, + ), + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); + @override + _i21.Future remove({required String? themeId}) => (super.noSuchMethod( + Invocation.method( + #remove, + [], + {#themeId: themeId}, + ), + returnValue: _i21.Future.value(), + returnValueForMissingStub: _i21.Future.value(), + ) as _i21.Future); + @override + _i21.Future verifyInstalled({required String? themeId}) => + (super.noSuchMethod( + Invocation.method( + #verifyInstalled, + [], + {#themeId: themeId}, + ), + returnValue: _i21.Future.value(false), + ) as _i21.Future); + @override + _i21.Future> fetchThemes() => + (super.noSuchMethod( + Invocation.method( + #fetchThemes, + [], + ), + returnValue: _i21.Future>.value( + <_i30.StackThemeMetaData>[]), + ) as _i21.Future>); + @override + _i21.Future<_i28.Uint8List> fetchTheme( + {required _i30.StackThemeMetaData? themeMetaData}) => + (super.noSuchMethod( + Invocation.method( + #fetchTheme, + [], + {#themeMetaData: themeMetaData}, + ), + returnValue: _i21.Future<_i28.Uint8List>.value(_i28.Uint8List(0)), + ) as _i21.Future<_i28.Uint8List>); + @override + _i31.StackTheme? getTheme({required String? themeId}) => + (super.noSuchMethod(Invocation.method( + #getTheme, + [], + {#themeId: themeId}, + )) as _i31.StackTheme?); +} diff --git a/test/widget_tests/wallet_info_row/sub_widgets/wallet_info_row_balance_future_test.dart b/test/widget_tests/wallet_info_row/sub_widgets/wallet_info_row_balance_future_test.dart index ba8e11e47..1368e8277 100644 --- a/test/widget_tests/wallet_info_row/sub_widgets/wallet_info_row_balance_future_test.dart +++ b/test/widget_tests/wallet_info_row/sub_widgets/wallet_info_row_balance_future_test.dart @@ -3,6 +3,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/coins/bitcoin/bitcoin_wallet.dart'; import 'package:stackwallet/services/coins/coin_service.dart'; @@ -10,11 +12,12 @@ import 'package:stackwallet/services/coins/manager.dart'; import 'package:stackwallet/services/node_service.dart'; import 'package:stackwallet/services/wallets.dart'; import 'package:stackwallet/services/wallets_service.dart'; -import 'package:stackwallet/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance_future.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance.dart'; +import '../../../sample_data/theme_json.dart'; import 'wallet_info_row_balance_future_test.mocks.dart'; @GenerateMocks([ @@ -35,13 +38,21 @@ void main() { when(wallet.coin).thenAnswer((_) => Coin.bitcoin); when(wallet.walletName).thenAnswer((_) => "some wallet"); when(wallet.walletId).thenAnswer((_) => "some-wallet-id"); + when(wallet.balance).thenAnswer( + (_) => Balance( + total: Amount.zero, + spendable: Amount.zero, + blockedTotal: Amount.zero, + pendingSpendable: Amount.zero, + ), + ); final manager = Manager(wallet); when(wallets.getManagerProvider("some-wallet-id")).thenAnswer( (realInvocation) => ChangeNotifierProvider((ref) => manager)); const walletInfoRowBalance = - WalletInfoRowBalanceFuture(walletId: "some-wallet-id"); + WalletInfoRowBalance(walletId: "some-wallet-id"); await widgetTester.pumpWidget( ProviderScope( overrides: [ @@ -51,7 +62,10 @@ void main() { theme: ThemeData( extensions: [ StackColors.fromStackColorTheme( - LightColors(), + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), ), ], ), @@ -63,6 +77,9 @@ void main() { ); // // expect(find.text("some wallet"), findsOneWidget); - expect(find.byType(WalletInfoRowBalanceFuture), findsOneWidget); + + await widgetTester.pumpAndSettle(); + + expect(find.byType(WalletInfoRowBalance), findsOneWidget); }); } diff --git a/test/widget_tests/wallet_info_row/sub_widgets/wallet_info_row_balance_future_test.mocks.dart b/test/widget_tests/wallet_info_row/sub_widgets/wallet_info_row_balance_future_test.mocks.dart index fe5e0e8a2..70954c6db 100644 --- a/test/widget_tests/wallet_info_row/sub_widgets/wallet_info_row_balance_future_test.mocks.dart +++ b/test/widget_tests/wallet_info_row/sub_widgets/wallet_info_row_balance_future_test.mocks.dart @@ -3,29 +3,39 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i16; -import 'dart:ui' as _i18; +import 'dart:async' as _i23; +import 'dart:typed_data' as _i29; +import 'dart:ui' as _i25; -import 'package:decimal/decimal.dart' as _i9; +import 'package:bip32/bip32.dart' as _i16; +import 'package:bip47/bip47.dart' as _i18; +import 'package:bitcoindart/bitcoindart.dart' as _i13; import 'package:flutter/foundation.dart' as _i4; import 'package:flutter_riverpod/flutter_riverpod.dart' as _i5; import 'package:mockito/mockito.dart' as _i1; -import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i11; -import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i10; -import 'package:stackwallet/models/models.dart' as _i8; -import 'package:stackwallet/models/node_model.dart' as _i20; -import 'package:stackwallet/services/coins/bitcoin/bitcoin_wallet.dart' as _i19; -import 'package:stackwallet/services/coins/coin_service.dart' as _i13; +import 'package:stackwallet/db/isar/main_db.dart' as _i12; +import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i10; +import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i9; +import 'package:stackwallet/models/balance.dart' as _i11; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i17; +import 'package:stackwallet/models/node_model.dart' as _i30; +import 'package:stackwallet/models/paymint/fee_object_model.dart' as _i8; +import 'package:stackwallet/models/signing_data.dart' as _i28; +import 'package:stackwallet/services/coins/bitcoin/bitcoin_wallet.dart' as _i26; +import 'package:stackwallet/services/coins/coin_service.dart' as _i20; import 'package:stackwallet/services/coins/manager.dart' as _i6; import 'package:stackwallet/services/node_service.dart' as _i3; import 'package:stackwallet/services/transaction_notification_tracker.dart' as _i7; -import 'package:stackwallet/services/wallets.dart' as _i14; +import 'package:stackwallet/services/wallets.dart' as _i21; import 'package:stackwallet/services/wallets_service.dart' as _i2; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i15; +import 'package:stackwallet/utilities/amount/amount.dart' as _i14; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i22; +import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart' as _i27; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' - as _i12; -import 'package:stackwallet/utilities/prefs.dart' as _i17; + as _i19; +import 'package:stackwallet/utilities/prefs.dart' as _i24; +import 'package:tuple/tuple.dart' as _i15; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -91,8 +101,8 @@ class _FakeTransactionNotificationTracker_4 extends _i1.SmartFake ); } -class _FakeUtxoData_5 extends _i1.SmartFake implements _i8.UtxoData { - _FakeUtxoData_5( +class _FakeFeeObject_5 extends _i1.SmartFake implements _i8.FeeObject { + _FakeFeeObject_5( Object parent, Invocation parentInvocation, ) : super( @@ -101,8 +111,8 @@ class _FakeUtxoData_5 extends _i1.SmartFake implements _i8.UtxoData { ); } -class _FakeDecimal_6 extends _i1.SmartFake implements _i9.Decimal { - _FakeDecimal_6( +class _FakeElectrumX_6 extends _i1.SmartFake implements _i9.ElectrumX { + _FakeElectrumX_6( Object parent, Invocation parentInvocation, ) : super( @@ -111,8 +121,9 @@ class _FakeDecimal_6 extends _i1.SmartFake implements _i9.Decimal { ); } -class _FakeFeeObject_7 extends _i1.SmartFake implements _i8.FeeObject { - _FakeFeeObject_7( +class _FakeCachedElectrumX_7 extends _i1.SmartFake + implements _i10.CachedElectrumX { + _FakeCachedElectrumX_7( Object parent, Invocation parentInvocation, ) : super( @@ -121,9 +132,8 @@ class _FakeFeeObject_7 extends _i1.SmartFake implements _i8.FeeObject { ); } -class _FakeTransactionData_8 extends _i1.SmartFake - implements _i8.TransactionData { - _FakeTransactionData_8( +class _FakeBalance_8 extends _i1.SmartFake implements _i11.Balance { + _FakeBalance_8( Object parent, Invocation parentInvocation, ) : super( @@ -132,8 +142,8 @@ class _FakeTransactionData_8 extends _i1.SmartFake ); } -class _FakeElectrumX_9 extends _i1.SmartFake implements _i10.ElectrumX { - _FakeElectrumX_9( +class _FakeMainDB_9 extends _i1.SmartFake implements _i12.MainDB { + _FakeMainDB_9( Object parent, Invocation parentInvocation, ) : super( @@ -142,9 +152,8 @@ class _FakeElectrumX_9 extends _i1.SmartFake implements _i10.ElectrumX { ); } -class _FakeCachedElectrumX_10 extends _i1.SmartFake - implements _i11.CachedElectrumX { - _FakeCachedElectrumX_10( +class _FakeNetworkType_10 extends _i1.SmartFake implements _i13.NetworkType { + _FakeNetworkType_10( Object parent, Invocation parentInvocation, ) : super( @@ -153,8 +162,7 @@ class _FakeCachedElectrumX_10 extends _i1.SmartFake ); } -class _FakeElectrumXNode_11 extends _i1.SmartFake - implements _i10.ElectrumXNode { +class _FakeElectrumXNode_11 extends _i1.SmartFake implements _i9.ElectrumXNode { _FakeElectrumXNode_11( Object parent, Invocation parentInvocation, @@ -164,9 +172,8 @@ class _FakeElectrumXNode_11 extends _i1.SmartFake ); } -class _FakeSecureStorageInterface_12 extends _i1.SmartFake - implements _i12.SecureStorageInterface { - _FakeSecureStorageInterface_12( +class _FakeAmount_12 extends _i1.SmartFake implements _i14.Amount { + _FakeAmount_12( Object parent, Invocation parentInvocation, ) : super( @@ -175,9 +182,61 @@ class _FakeSecureStorageInterface_12 extends _i1.SmartFake ); } -class _FakeCoinServiceAPI_13 extends _i1.SmartFake - implements _i13.CoinServiceAPI { - _FakeCoinServiceAPI_13( +class _FakeTuple2_13 extends _i1.SmartFake + implements _i15.Tuple2 { + _FakeTuple2_13( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeBIP32_14 extends _i1.SmartFake implements _i16.BIP32 { + _FakeBIP32_14( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAddress_15 extends _i1.SmartFake implements _i17.Address { + _FakeAddress_15( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePaymentCode_16 extends _i1.SmartFake implements _i18.PaymentCode { + _FakePaymentCode_16( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeSecureStorageInterface_17 extends _i1.SmartFake + implements _i19.SecureStorageInterface { + _FakeSecureStorageInterface_17( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeCoinServiceAPI_18 extends _i1.SmartFake + implements _i20.CoinServiceAPI { + _FakeCoinServiceAPI_18( Object parent, Invocation parentInvocation, ) : super( @@ -189,7 +248,7 @@ class _FakeCoinServiceAPI_13 extends _i1.SmartFake /// A class which mocks [Wallets]. /// /// See the documentation for Mockito's code generation for more information. -class MockWallets extends _i1.Mock implements _i14.Wallets { +class MockWallets extends _i1.Mock implements _i21.Wallets { MockWallets() { _i1.throwOnMissingStub(this); } @@ -256,7 +315,7 @@ class MockWallets extends _i1.Mock implements _i14.Wallets { returnValueForMissingStub: null, ); @override - List getWalletIdsFor({required _i15.Coin? coin}) => + List getWalletIdsFor({required _i22.Coin? coin}) => (super.noSuchMethod( Invocation.method( #getWalletIdsFor, @@ -266,15 +325,28 @@ class MockWallets extends _i1.Mock implements _i14.Wallets { returnValue: [], ) as List); @override - Map<_i15.Coin, List<_i5.ChangeNotifierProvider<_i6.Manager>>> + List<_i15.Tuple2<_i22.Coin, List<_i5.ChangeNotifierProvider<_i6.Manager>>>> getManagerProvidersByCoin() => (super.noSuchMethod( Invocation.method( #getManagerProvidersByCoin, [], ), - returnValue: <_i15.Coin, - List<_i5.ChangeNotifierProvider<_i6.Manager>>>{}, - ) as Map<_i15.Coin, List<_i5.ChangeNotifierProvider<_i6.Manager>>>); + returnValue: < + _i15.Tuple2<_i22.Coin, + List<_i5.ChangeNotifierProvider<_i6.Manager>>>>[], + ) as List< + _i15.Tuple2<_i22.Coin, + List<_i5.ChangeNotifierProvider<_i6.Manager>>>>); + @override + List<_i5.ChangeNotifierProvider<_i6.Manager>> getManagerProvidersForCoin( + _i22.Coin? coin) => + (super.noSuchMethod( + Invocation.method( + #getManagerProvidersForCoin, + [coin], + ), + returnValue: <_i5.ChangeNotifierProvider<_i6.Manager>>[], + ) as List<_i5.ChangeNotifierProvider<_i6.Manager>>); @override _i5.ChangeNotifierProvider<_i6.Manager> getManagerProvider( String? walletId) => @@ -331,17 +403,17 @@ class MockWallets extends _i1.Mock implements _i14.Wallets { returnValueForMissingStub: null, ); @override - _i16.Future load(_i17.Prefs? prefs) => (super.noSuchMethod( + _i23.Future load(_i24.Prefs? prefs) => (super.noSuchMethod( Invocation.method( #load, [prefs], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future loadAfterStackRestore( - _i17.Prefs? prefs, + _i23.Future loadAfterStackRestore( + _i24.Prefs? prefs, List<_i6.Manager>? managers, ) => (super.noSuchMethod( @@ -352,11 +424,11 @@ class MockWallets extends _i1.Mock implements _i14.Wallets { managers, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -364,7 +436,7 @@ class MockWallets extends _i1.Mock implements _i14.Wallets { returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -390,19 +462,19 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { } @override - _i16.Future> get walletNames => + _i23.Future> get walletNames => (super.noSuchMethod( Invocation.getter(#walletNames), - returnValue: _i16.Future>.value( + returnValue: _i23.Future>.value( {}), - ) as _i16.Future>); + ) as _i23.Future>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i16.Future renameWallet({ + _i23.Future renameWallet({ required String? from, required String? to, required bool? shouldNotifyListeners, @@ -417,13 +489,21 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future addExistingStackWallet({ + Map fetchWalletsData() => (super.noSuchMethod( + Invocation.method( + #fetchWalletsData, + [], + ), + returnValue: {}, + ) as Map); + @override + _i23.Future addExistingStackWallet({ required String? name, required String? walletId, - required _i15.Coin? coin, + required _i22.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -437,13 +517,13 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future addNewWallet({ + _i23.Future addNewWallet({ required String? name, - required _i15.Coin? coin, + required _i22.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -456,46 +536,46 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future> getFavoriteWalletIds() => (super.noSuchMethod( + _i23.Future> getFavoriteWalletIds() => (super.noSuchMethod( Invocation.method( #getFavoriteWalletIds, [], ), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); + returnValue: _i23.Future>.value([]), + ) as _i23.Future>); @override - _i16.Future saveFavoriteWalletIds(List? walletIds) => + _i23.Future saveFavoriteWalletIds(List? walletIds) => (super.noSuchMethod( Invocation.method( #saveFavoriteWalletIds, [walletIds], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future addFavorite(String? walletId) => (super.noSuchMethod( + _i23.Future addFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #addFavorite, [walletId], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future removeFavorite(String? walletId) => (super.noSuchMethod( + _i23.Future removeFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #removeFavorite, [walletId], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future moveFavorite({ + _i23.Future moveFavorite({ required int? fromIndex, required int? toIndex, }) => @@ -508,48 +588,48 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { #toIndex: toIndex, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future checkForDuplicate(String? name) => (super.noSuchMethod( + _i23.Future checkForDuplicate(String? name) => (super.noSuchMethod( Invocation.method( #checkForDuplicate, [name], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future getWalletId(String? walletName) => (super.noSuchMethod( + _i23.Future getWalletId(String? walletName) => (super.noSuchMethod( Invocation.method( #getWalletId, [walletName], ), - returnValue: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future isMnemonicVerified({required String? walletId}) => + _i23.Future isMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #isMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future setMnemonicVerified({required String? walletId}) => + _i23.Future setMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #setMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future deleteWallet( + _i23.Future deleteWallet( String? name, bool? shouldNotifyListeners, ) => @@ -561,20 +641,20 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { shouldNotifyListeners, ], ), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future.value(0), + ) as _i23.Future); @override - _i16.Future refreshWallets(bool? shouldNotifyListeners) => + _i23.Future refreshWallets(bool? shouldNotifyListeners) => (super.noSuchMethod( Invocation.method( #refreshWallets, [shouldNotifyListeners], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -582,7 +662,7 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -610,13 +690,13 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { /// A class which mocks [BitcoinWallet]. /// /// See the documentation for Mockito's code generation for more information. -class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { +class MockBitcoinWallet extends _i1.Mock implements _i26.BitcoinWallet { MockBitcoinWallet() { _i1.throwOnMissingStub(this); } @override - set timer(_i16.Timer? _timer) => super.noSuchMethod( + set timer(_i23.Timer? _timer) => super.noSuchMethod( Invocation.setter( #timer, _timer, @@ -641,19 +721,6 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValueForMissingStub: null, ); @override - List<_i8.UtxoObject> get outputsList => (super.noSuchMethod( - Invocation.getter(#outputsList), - returnValue: <_i8.UtxoObject>[], - ) as List<_i8.UtxoObject>); - @override - set outputsList(List<_i8.UtxoObject>? _outputsList) => super.noSuchMethod( - Invocation.setter( - #outputsList, - _outputsList, - ), - returnValueForMissingStub: null, - ); - @override bool get longMutex => (super.noSuchMethod( Invocation.getter(#longMutex), returnValue: false, @@ -680,14 +747,6 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValueForMissingStub: null, ); @override - set cachedTxData(_i8.TransactionData? _cachedTxData) => super.noSuchMethod( - Invocation.setter( - #cachedTxData, - _cachedTxData, - ), - returnValueForMissingStub: null, - ); - @override bool get isActive => (super.noSuchMethod( Invocation.getter(#isActive), returnValue: false, @@ -714,104 +773,74 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValue: false, ) as bool); @override - _i15.Coin get coin => (super.noSuchMethod( + _i22.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i15.Coin.bitcoin, - ) as _i15.Coin); + returnValue: _i22.Coin.bitcoin, + ) as _i22.Coin); @override - _i16.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); + _i23.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i23.Future>.value(<_i17.UTXO>[]), + ) as _i23.Future>); @override - _i16.Future<_i8.UtxoData> get utxoData => (super.noSuchMethod( - Invocation.getter(#utxoData), - returnValue: _i16.Future<_i8.UtxoData>.value(_FakeUtxoData_5( - this, - Invocation.getter(#utxoData), - )), - ) as _i16.Future<_i8.UtxoData>); - @override - _i16.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), + _i23.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i16.Future>.value(<_i8.UtxoObject>[]), - ) as _i16.Future>); + _i23.Future>.value(<_i17.Transaction>[]), + ) as _i23.Future>); @override - _i16.Future<_i9.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#availableBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i9.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i9.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i9.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#totalBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future get currentReceivingAddress => (super.noSuchMethod( + _i23.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future get currentLegacyReceivingAddress => (super.noSuchMethod( - Invocation.getter(#currentLegacyReceivingAddress), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + _i23.Future get currentChangeAddress => (super.noSuchMethod( + Invocation.getter(#currentChangeAddress), + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future get currentReceivingAddressP2SH => (super.noSuchMethod( - Invocation.getter(#currentReceivingAddressP2SH), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + _i23.Future get currentChangeAddressP2PKH => (super.noSuchMethod( + Invocation.getter(#currentChangeAddressP2PKH), + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override bool get hasCalledExit => (super.noSuchMethod( Invocation.getter(#hasCalledExit), returnValue: false, ) as bool); @override - _i16.Future<_i8.FeeObject> get fees => (super.noSuchMethod( + _i23.Future<_i8.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i16.Future<_i8.FeeObject>.value(_FakeFeeObject_7( + returnValue: _i23.Future<_i8.FeeObject>.value(_FakeFeeObject_5( this, Invocation.getter(#fees), )), - ) as _i16.Future<_i8.FeeObject>); + ) as _i23.Future<_i8.FeeObject>); @override - _i16.Future get maxFee => (super.noSuchMethod( + _i23.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future.value(0), + ) as _i23.Future); @override - _i16.Future> get mnemonic => (super.noSuchMethod( + _i23.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); + returnValue: _i23.Future>.value([]), + ) as _i23.Future>); @override - _i16.Future get chainHeight => (super.noSuchMethod( + _i23.Future get mnemonicString => (super.noSuchMethod( + Invocation.getter(#mnemonicString), + returnValue: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future get chainHeight => (super.noSuchMethod( Invocation.getter(#chainHeight), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future.value(0), + ) as _i23.Future); @override int get storedChainHeight => (super.noSuchMethod( Invocation.getter(#storedChainHeight), @@ -841,15 +870,6 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValue: false, ) as bool); @override - _i16.Future<_i8.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), - returnValue: - _i16.Future<_i8.TransactionData>.value(_FakeTransactionData_8( - this, - Invocation.getter(#transactionData), - )), - ) as _i16.Future<_i8.TransactionData>); - @override String get walletId => (super.noSuchMethod( Invocation.getter(#walletId), returnValue: '', @@ -868,21 +888,34 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValueForMissingStub: null, ); @override - _i10.ElectrumX get electrumXClient => (super.noSuchMethod( + _i9.ElectrumX get electrumXClient => (super.noSuchMethod( Invocation.getter(#electrumXClient), - returnValue: _FakeElectrumX_9( + returnValue: _FakeElectrumX_6( this, Invocation.getter(#electrumXClient), ), - ) as _i10.ElectrumX); + ) as _i9.ElectrumX); @override - _i11.CachedElectrumX get cachedElectrumXClient => (super.noSuchMethod( + _i10.CachedElectrumX get cachedElectrumXClient => (super.noSuchMethod( Invocation.getter(#cachedElectrumXClient), - returnValue: _FakeCachedElectrumX_10( + returnValue: _FakeCachedElectrumX_7( this, Invocation.getter(#cachedElectrumXClient), ), - ) as _i11.CachedElectrumX); + ) as _i10.CachedElectrumX); + @override + _i11.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_8( + this, + Invocation.getter(#balance), + ), + ) as _i11.Balance); + @override + _i23.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override set onIsActiveWalletChanged(void Function(bool)? _onIsActiveWalletChanged) => super.noSuchMethod( @@ -893,38 +926,44 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValueForMissingStub: null, ); @override - _i16.Future exit() => (super.noSuchMethod( + _i12.MainDB get db => (super.noSuchMethod( + Invocation.getter(#db), + returnValue: _FakeMainDB_9( + this, + Invocation.getter(#db), + ), + ) as _i12.MainDB); + @override + _i13.NetworkType get networkType => (super.noSuchMethod( + Invocation.getter(#networkType), + returnValue: _FakeNetworkType_10( + this, + Invocation.getter(#networkType), + ), + ) as _i13.NetworkType); + @override + _i23.Future exit() => (super.noSuchMethod( Invocation.method( #exit, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future updateStoredChainHeight({required int? newHeight}) => - (super.noSuchMethod( - Invocation.method( - #updateStoredChainHeight, - [], - {#newHeight: newHeight}, - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i19.DerivePathType addressType({required String? address}) => + _i27.DerivePathType addressType({required String? address}) => (super.noSuchMethod( Invocation.method( #addressType, [], {#address: address}, ), - returnValue: _i19.DerivePathType.bip44, - ) as _i19.DerivePathType); + returnValue: _i27.DerivePathType.bip44, + ) as _i27.DerivePathType); @override - _i16.Future recoverFromMnemonic({ + _i23.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -935,55 +974,55 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future getTransactionCacheEarly(List? allAddresses) => + _i23.Future getTransactionCacheEarly(List? allAddresses) => (super.noSuchMethod( Invocation.method( #getTransactionCacheEarly, [allAddresses], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future refreshIfThereIsNewData() => (super.noSuchMethod( + _i23.Future refreshIfThereIsNewData() => (super.noSuchMethod( Invocation.method( #refreshIfThereIsNewData, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future getAllTxsToWatch(_i8.TransactionData? txData) => - (super.noSuchMethod( + _i23.Future getAllTxsToWatch() => (super.noSuchMethod( Invocation.method( #getAllTxsToWatch, - [txData], + [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future refresh() => (super.noSuchMethod( + _i23.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future> prepareSend({ + _i23.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i14.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -992,49 +1031,31 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i23.Future>.value({}), + ) as _i23.Future>); @override - _i16.Future confirmSend({required Map? txData}) => + _i23.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); - @override - _i16.Future testNetworkConnection() => (super.noSuchMethod( + _i23.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override void startNetworkAlivePinging() => super.noSuchMethod( Invocation.method( @@ -1052,33 +1073,33 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValueForMissingStub: null, ); @override - _i16.Future initializeNew() => (super.noSuchMethod( + _i23.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future initializeExisting() => (super.noSuchMethod( + _i23.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future updateSentCachedTxData(Map? txData) => + _i23.Future updateSentCachedTxData(Map? txData) => (super.noSuchMethod( Invocation.method( #updateSentCachedTxData, [txData], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -1088,112 +1109,69 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValue: false, ) as bool); @override - _i16.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i23.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future<_i10.ElectrumXNode> getCurrentNode() => (super.noSuchMethod( + _i23.Future<_i9.ElectrumXNode> getCurrentNode() => (super.noSuchMethod( Invocation.method( #getCurrentNode, [], ), - returnValue: - _i16.Future<_i10.ElectrumXNode>.value(_FakeElectrumXNode_11( + returnValue: _i23.Future<_i9.ElectrumXNode>.value(_FakeElectrumXNode_11( this, Invocation.method( #getCurrentNode, [], ), )), - ) as _i16.Future<_i10.ElectrumXNode>); + ) as _i23.Future<_i9.ElectrumXNode>); @override - _i16.Future addDerivation({ - required int? chain, - required String? address, - required String? pubKey, - required String? wif, - required _i19.DerivePathType? derivePathType, - }) => - (super.noSuchMethod( - Invocation.method( - #addDerivation, - [], - { - #chain: chain, - #address: address, - #pubKey: pubKey, - #wif: wif, - #derivePathType: derivePathType, - }, - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i16.Future addDerivations({ - required int? chain, - required _i19.DerivePathType? derivePathType, - required Map? derivationsToAdd, - }) => - (super.noSuchMethod( - Invocation.method( - #addDerivations, - [], - { - #chain: chain, - #derivePathType: derivePathType, - #derivationsToAdd: derivationsToAdd, - }, - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i16.Future getTxCount({required String? address}) => - (super.noSuchMethod( - Invocation.method( - #getTxCount, - [], - {#address: address}, - ), - returnValue: _i16.Future.value(0), - ) as _i16.Future); - @override - _i16.Future checkCurrentReceivingAddressesForTransactions() => - (super.noSuchMethod( - Invocation.method( - #checkCurrentReceivingAddressesForTransactions, - [], - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i16.Future checkCurrentChangeAddressesForTransactions() => - (super.noSuchMethod( - Invocation.method( - #checkCurrentChangeAddressesForTransactions, - [], - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i16.Future>> fastFetch( + _i23.Future>> fastFetch( List? allTxHashes) => (super.noSuchMethod( Invocation.method( #fastFetch, [allTxHashes], ), - returnValue: _i16.Future>>.value( + returnValue: _i23.Future>>.value( >[]), - ) as _i16.Future>>); + ) as _i23.Future>>); + @override + _i23.Future getTxCount({required String? address}) => + (super.noSuchMethod( + Invocation.method( + #getTxCount, + [], + {#address: address}, + ), + returnValue: _i23.Future.value(0), + ) as _i23.Future); + @override + _i23.Future checkCurrentReceivingAddressesForTransactions() => + (super.noSuchMethod( + Invocation.method( + #checkCurrentReceivingAddressesForTransactions, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future checkCurrentChangeAddressesForTransactions() => + (super.noSuchMethod( + Invocation.method( + #checkCurrentChangeAddressesForTransactions, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override int estimateTxFee({ required int? vSize, @@ -1211,42 +1189,42 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValue: 0, ) as int); @override - dynamic coinSelection( - int? satoshiAmountToSend, - int? selectedTxFeeRate, - String? _recipientAddress, - bool? isSendAll, { + dynamic coinSelection({ + required int? satoshiAmountToSend, + required int? selectedTxFeeRate, + required String? recipientAddress, + required bool? coinControl, + required bool? isSendAll, int? additionalOutputs = 0, - List<_i8.UtxoObject>? utxos, + List<_i17.UTXO>? utxos, }) => super.noSuchMethod(Invocation.method( #coinSelection, - [ - satoshiAmountToSend, - selectedTxFeeRate, - _recipientAddress, - isSendAll, - ], + [], { + #satoshiAmountToSend: satoshiAmountToSend, + #selectedTxFeeRate: selectedTxFeeRate, + #recipientAddress: recipientAddress, + #coinControl: coinControl, + #isSendAll: isSendAll, #additionalOutputs: additionalOutputs, #utxos: utxos, }, )); @override - _i16.Future> fetchBuildTxData( - List<_i8.UtxoObject>? utxosToUse) => + _i23.Future> fetchBuildTxData( + List<_i17.UTXO>? utxosToUse) => (super.noSuchMethod( Invocation.method( #fetchBuildTxData, [utxosToUse], ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i23.Future>.value(<_i28.SigningData>[]), + ) as _i23.Future>); @override - _i16.Future> buildTransaction({ - required List<_i8.UtxoObject>? utxosToUse, - required Map? utxoSigningData, + _i23.Future> buildTransaction({ + required List<_i28.SigningData>? utxoSigningData, required List? recipients, required List? satoshiAmounts, }) => @@ -1255,17 +1233,16 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { #buildTransaction, [], { - #utxosToUse: utxosToUse, #utxoSigningData: utxoSigningData, #recipients: recipients, #satoshiAmounts: satoshiAmounts, }, ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i23.Future>.value({}), + ) as _i23.Future>); @override - _i16.Future fullRescan( + _i23.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -1277,26 +1254,35 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { maxNumberOfIndexesToCheck, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future estimateFeeFor( - int? satoshiAmount, + _i23.Future<_i14.Amount> estimateFeeFor( + _i14.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future<_i14.Amount>.value(_FakeAmount_12( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i23.Future<_i14.Amount>); @override - int roughFeeEstimate( + _i14.Amount roughFeeEstimate( int? inputCount, int? outputCount, int? feeRatePerKB, @@ -1310,24 +1296,662 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { feeRatePerKB, ], ), - returnValue: 0, - ) as int); + returnValue: _FakeAmount_12( + this, + Invocation.method( + #roughFeeEstimate, + [ + inputCount, + outputCount, + feeRatePerKB, + ], + ), + ), + ) as _i14.Amount); @override - int sweepAllEstimate(int? feeRate) => (super.noSuchMethod( + _i23.Future<_i14.Amount> sweepAllEstimate(int? feeRate) => + (super.noSuchMethod( Invocation.method( #sweepAllEstimate, [feeRate], ), - returnValue: 0, - ) as int); + returnValue: _i23.Future<_i14.Amount>.value(_FakeAmount_12( + this, + Invocation.method( + #sweepAllEstimate, + [feeRate], + ), + )), + ) as _i23.Future<_i14.Amount>); @override - _i16.Future generateNewAddress() => (super.noSuchMethod( + _i23.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); + @override + void initCache( + String? walletId, + _i22.Coin? coin, + ) => + super.noSuchMethod( + Invocation.method( + #initCache, + [ + walletId, + coin, + ], + ), + returnValueForMissingStub: null, + ); + @override + _i23.Future updateCachedId(String? id) => (super.noSuchMethod( + Invocation.method( + #updateCachedId, + [id], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + int getCachedChainHeight() => (super.noSuchMethod( + Invocation.method( + #getCachedChainHeight, + [], + ), + returnValue: 0, + ) as int); + @override + _i23.Future updateCachedChainHeight(int? height) => (super.noSuchMethod( + Invocation.method( + #updateCachedChainHeight, + [height], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + bool getCachedIsFavorite() => (super.noSuchMethod( + Invocation.method( + #getCachedIsFavorite, + [], + ), + returnValue: false, + ) as bool); + @override + _i23.Future updateCachedIsFavorite(bool? isFavorite) => + (super.noSuchMethod( + Invocation.method( + #updateCachedIsFavorite, + [isFavorite], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i11.Balance getCachedBalance() => (super.noSuchMethod( + Invocation.method( + #getCachedBalance, + [], + ), + returnValue: _FakeBalance_8( + this, + Invocation.method( + #getCachedBalance, + [], + ), + ), + ) as _i11.Balance); + @override + _i23.Future updateCachedBalance(_i11.Balance? balance) => + (super.noSuchMethod( + Invocation.method( + #updateCachedBalance, + [balance], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i11.Balance getCachedBalanceSecondary() => (super.noSuchMethod( + Invocation.method( + #getCachedBalanceSecondary, + [], + ), + returnValue: _FakeBalance_8( + this, + Invocation.method( + #getCachedBalanceSecondary, + [], + ), + ), + ) as _i11.Balance); + @override + _i23.Future updateCachedBalanceSecondary(_i11.Balance? balance) => + (super.noSuchMethod( + Invocation.method( + #updateCachedBalanceSecondary, + [balance], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + List getWalletTokenContractAddresses() => (super.noSuchMethod( + Invocation.method( + #getWalletTokenContractAddresses, + [], + ), + returnValue: [], + ) as List); + @override + _i23.Future updateWalletTokenContractAddresses( + List? contractAddresses) => + (super.noSuchMethod( + Invocation.method( + #updateWalletTokenContractAddresses, + [contractAddresses], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + void initWalletDB({_i12.MainDB? mockableOverride}) => super.noSuchMethod( + Invocation.method( + #initWalletDB, + [], + {#mockableOverride: mockableOverride}, + ), + returnValueForMissingStub: null, + ); + @override + _i23.Future<_i15.Tuple2<_i17.Transaction, _i17.Address>> parseTransaction( + Map? txData, + dynamic electrumxClient, + List<_i17.Address>? myAddresses, + _i22.Coin? coin, + int? minConfirms, + String? walletId, + ) => + (super.noSuchMethod( + Invocation.method( + #parseTransaction, + [ + txData, + electrumxClient, + myAddresses, + coin, + minConfirms, + walletId, + ], + ), + returnValue: + _i23.Future<_i15.Tuple2<_i17.Transaction, _i17.Address>>.value( + _FakeTuple2_13<_i17.Transaction, _i17.Address>( + this, + Invocation.method( + #parseTransaction, + [ + txData, + electrumxClient, + myAddresses, + coin, + minConfirms, + walletId, + ], + ), + )), + ) as _i23.Future<_i15.Tuple2<_i17.Transaction, _i17.Address>>); + @override + void initPaynymWalletInterface({ + required String? walletId, + required String? walletName, + required _i13.NetworkType? network, + required _i22.Coin? coin, + required _i12.MainDB? db, + required _i9.ElectrumX? electrumXClient, + required _i19.SecureStorageInterface? secureStorage, + required int? dustLimit, + required int? dustLimitP2PKH, + required int? minConfirms, + required _i23.Future Function()? getMnemonicString, + required _i23.Future Function()? getMnemonicPassphrase, + required _i23.Future Function()? getChainHeight, + required _i23.Future Function()? getCurrentChangeAddress, + required int Function({ + required int feeRatePerKB, + required int vSize, + })? + estimateTxFee, + required _i23.Future> Function({ + required String address, + required _i14.Amount amount, + Map? args, + })? + prepareSend, + required _i23.Future Function({required String address})? getTxCount, + required _i23.Future> Function(List<_i17.UTXO>)? + fetchBuildTxData, + required _i23.Future Function()? refresh, + required _i23.Future Function()? checkChangeAddressForTransactions, + }) => + super.noSuchMethod( + Invocation.method( + #initPaynymWalletInterface, + [], + { + #walletId: walletId, + #walletName: walletName, + #network: network, + #coin: coin, + #db: db, + #electrumXClient: electrumXClient, + #secureStorage: secureStorage, + #dustLimit: dustLimit, + #dustLimitP2PKH: dustLimitP2PKH, + #minConfirms: minConfirms, + #getMnemonicString: getMnemonicString, + #getMnemonicPassphrase: getMnemonicPassphrase, + #getChainHeight: getChainHeight, + #getCurrentChangeAddress: getCurrentChangeAddress, + #estimateTxFee: estimateTxFee, + #prepareSend: prepareSend, + #getTxCount: getTxCount, + #fetchBuildTxData: fetchBuildTxData, + #refresh: refresh, + #checkChangeAddressForTransactions: + checkChangeAddressForTransactions, + }, + ), + returnValueForMissingStub: null, + ); + @override + _i23.Future<_i16.BIP32> getBip47BaseNode() => (super.noSuchMethod( + Invocation.method( + #getBip47BaseNode, + [], + ), + returnValue: _i23.Future<_i16.BIP32>.value(_FakeBIP32_14( + this, + Invocation.method( + #getBip47BaseNode, + [], + ), + )), + ) as _i23.Future<_i16.BIP32>); + @override + _i23.Future<_i29.Uint8List> getPrivateKeyForPaynymReceivingAddress({ + required String? paymentCodeString, + required int? index, + }) => + (super.noSuchMethod( + Invocation.method( + #getPrivateKeyForPaynymReceivingAddress, + [], + { + #paymentCodeString: paymentCodeString, + #index: index, + }, + ), + returnValue: _i23.Future<_i29.Uint8List>.value(_i29.Uint8List(0)), + ) as _i23.Future<_i29.Uint8List>); + @override + _i23.Future<_i17.Address> currentReceivingPaynymAddress({ + required _i18.PaymentCode? sender, + required bool? isSegwit, + }) => + (super.noSuchMethod( + Invocation.method( + #currentReceivingPaynymAddress, + [], + { + #sender: sender, + #isSegwit: isSegwit, + }, + ), + returnValue: _i23.Future<_i17.Address>.value(_FakeAddress_15( + this, + Invocation.method( + #currentReceivingPaynymAddress, + [], + { + #sender: sender, + #isSegwit: isSegwit, + }, + ), + )), + ) as _i23.Future<_i17.Address>); + @override + _i23.Future checkCurrentPaynymReceivingAddressForTransactions({ + required _i18.PaymentCode? sender, + required bool? isSegwit, + }) => + (super.noSuchMethod( + Invocation.method( + #checkCurrentPaynymReceivingAddressForTransactions, + [], + { + #sender: sender, + #isSegwit: isSegwit, + }, + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future checkAllCurrentReceivingPaynymAddressesForTransactions() => + (super.noSuchMethod( + Invocation.method( + #checkAllCurrentReceivingPaynymAddressesForTransactions, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future<_i16.BIP32> deriveNotificationBip32Node() => (super.noSuchMethod( + Invocation.method( + #deriveNotificationBip32Node, + [], + ), + returnValue: _i23.Future<_i16.BIP32>.value(_FakeBIP32_14( + this, + Invocation.method( + #deriveNotificationBip32Node, + [], + ), + )), + ) as _i23.Future<_i16.BIP32>); + @override + _i23.Future<_i18.PaymentCode> getPaymentCode({required bool? isSegwit}) => + (super.noSuchMethod( + Invocation.method( + #getPaymentCode, + [], + {#isSegwit: isSegwit}, + ), + returnValue: _i23.Future<_i18.PaymentCode>.value(_FakePaymentCode_16( + this, + Invocation.method( + #getPaymentCode, + [], + {#isSegwit: isSegwit}, + ), + )), + ) as _i23.Future<_i18.PaymentCode>); + @override + _i23.Future<_i29.Uint8List> signWithNotificationKey(_i29.Uint8List? data) => + (super.noSuchMethod( + Invocation.method( + #signWithNotificationKey, + [data], + ), + returnValue: _i23.Future<_i29.Uint8List>.value(_i29.Uint8List(0)), + ) as _i23.Future<_i29.Uint8List>); + @override + _i23.Future signStringWithNotificationKey(String? data) => + (super.noSuchMethod( + Invocation.method( + #signStringWithNotificationKey, + [data], + ), + returnValue: _i23.Future.value(''), + ) as _i23.Future); + @override + _i23.Future> preparePaymentCodeSend({ + required _i18.PaymentCode? paymentCode, + required bool? isSegwit, + required _i14.Amount? amount, + Map? args, + }) => + (super.noSuchMethod( + Invocation.method( + #preparePaymentCodeSend, + [], + { + #paymentCode: paymentCode, + #isSegwit: isSegwit, + #amount: amount, + #args: args, + }, + ), + returnValue: + _i23.Future>.value({}), + ) as _i23.Future>); + @override + _i23.Future<_i17.Address> nextUnusedSendAddressFrom({ + required _i18.PaymentCode? pCode, + required bool? isSegwit, + required _i16.BIP32? privateKeyNode, + int? startIndex = 0, + }) => + (super.noSuchMethod( + Invocation.method( + #nextUnusedSendAddressFrom, + [], + { + #pCode: pCode, + #isSegwit: isSegwit, + #privateKeyNode: privateKeyNode, + #startIndex: startIndex, + }, + ), + returnValue: _i23.Future<_i17.Address>.value(_FakeAddress_15( + this, + Invocation.method( + #nextUnusedSendAddressFrom, + [], + { + #pCode: pCode, + #isSegwit: isSegwit, + #privateKeyNode: privateKeyNode, + #startIndex: startIndex, + }, + ), + )), + ) as _i23.Future<_i17.Address>); + @override + _i23.Future> prepareNotificationTx({ + required int? selectedTxFeeRate, + required String? targetPaymentCodeString, + int? additionalOutputs = 0, + List<_i17.UTXO>? utxos, + }) => + (super.noSuchMethod( + Invocation.method( + #prepareNotificationTx, + [], + { + #selectedTxFeeRate: selectedTxFeeRate, + #targetPaymentCodeString: targetPaymentCodeString, + #additionalOutputs: additionalOutputs, + #utxos: utxos, + }, + ), + returnValue: + _i23.Future>.value({}), + ) as _i23.Future>); + @override + _i23.Future broadcastNotificationTx( + {required Map? preparedTx}) => + (super.noSuchMethod( + Invocation.method( + #broadcastNotificationTx, + [], + {#preparedTx: preparedTx}, + ), + returnValue: _i23.Future.value(''), + ) as _i23.Future); + @override + _i23.Future hasConnected(String? paymentCodeString) => + (super.noSuchMethod( + Invocation.method( + #hasConnected, + [paymentCodeString], + ), + returnValue: _i23.Future.value(false), + ) as _i23.Future); + @override + _i23.Future<_i18.PaymentCode?> unBlindedPaymentCodeFromTransaction( + {required _i17.Transaction? transaction}) => + (super.noSuchMethod( + Invocation.method( + #unBlindedPaymentCodeFromTransaction, + [], + {#transaction: transaction}, + ), + returnValue: _i23.Future<_i18.PaymentCode?>.value(), + ) as _i23.Future<_i18.PaymentCode?>); + @override + _i23.Future<_i18.PaymentCode?> unBlindedPaymentCodeFromTransactionBad( + {required _i17.Transaction? transaction}) => + (super.noSuchMethod( + Invocation.method( + #unBlindedPaymentCodeFromTransactionBad, + [], + {#transaction: transaction}, + ), + returnValue: _i23.Future<_i18.PaymentCode?>.value(), + ) as _i23.Future<_i18.PaymentCode?>); + @override + _i23.Future> + getAllPaymentCodesFromNotificationTransactions() => (super.noSuchMethod( + Invocation.method( + #getAllPaymentCodesFromNotificationTransactions, + [], + ), + returnValue: + _i23.Future>.value(<_i18.PaymentCode>[]), + ) as _i23.Future>); + @override + _i23.Future checkForNotificationTransactionsTo( + Set? otherCodeStrings) => + (super.noSuchMethod( + Invocation.method( + #checkForNotificationTransactionsTo, + [otherCodeStrings], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future restoreAllHistory({ + required int? maxUnusedAddressGap, + required int? maxNumberOfIndexesToCheck, + required Set? paymentCodeStrings, + }) => + (super.noSuchMethod( + Invocation.method( + #restoreAllHistory, + [], + { + #maxUnusedAddressGap: maxUnusedAddressGap, + #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + #paymentCodeStrings: paymentCodeStrings, + }, + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future restoreHistoryWith({ + required _i18.PaymentCode? other, + required bool? checkSegwitAsWell, + required int? maxUnusedAddressGap, + required int? maxNumberOfIndexesToCheck, + }) => + (super.noSuchMethod( + Invocation.method( + #restoreHistoryWith, + [], + { + #other: other, + #checkSegwitAsWell: checkSegwitAsWell, + #maxUnusedAddressGap: maxUnusedAddressGap, + #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + }, + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future<_i17.Address> getMyNotificationAddress() => (super.noSuchMethod( + Invocation.method( + #getMyNotificationAddress, + [], + ), + returnValue: _i23.Future<_i17.Address>.value(_FakeAddress_15( + this, + Invocation.method( + #getMyNotificationAddress, + [], + ), + )), + ) as _i23.Future<_i17.Address>); + @override + _i23.Future> lookupKey(String? paymentCodeString) => + (super.noSuchMethod( + Invocation.method( + #lookupKey, + [paymentCodeString], + ), + returnValue: _i23.Future>.value([]), + ) as _i23.Future>); + @override + _i23.Future paymentCodeStringByKey(String? key) => + (super.noSuchMethod( + Invocation.method( + #paymentCodeStringByKey, + [key], + ), + returnValue: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future storeCode(String? paymentCodeString) => + (super.noSuchMethod( + Invocation.method( + #storeCode, + [paymentCodeString], + ), + returnValue: _i23.Future.value(''), + ) as _i23.Future); + @override + void initCoinControlInterface({ + required String? walletId, + required String? walletName, + required _i22.Coin? coin, + required _i12.MainDB? db, + required _i23.Future Function()? getChainHeight, + required _i23.Future Function(_i11.Balance)? refreshedBalanceCallback, + }) => + super.noSuchMethod( + Invocation.method( + #initCoinControlInterface, + [], + { + #walletId: walletId, + #walletName: walletName, + #coin: coin, + #db: db, + #getChainHeight: getChainHeight, + #refreshedBalanceCallback: refreshedBalanceCallback, + }, + ), + returnValueForMissingStub: null, + ); + @override + _i23.Future refreshBalance({bool? notify = false}) => + (super.noSuchMethod( + Invocation.method( + #refreshBalance, + [], + {#notify: notify}, + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); } /// A class which mocks [NodeService]. @@ -1335,41 +1959,41 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { /// See the documentation for Mockito's code generation for more information. class MockNodeService extends _i1.Mock implements _i3.NodeService { @override - _i12.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod( + _i19.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod( Invocation.getter(#secureStorageInterface), - returnValue: _FakeSecureStorageInterface_12( + returnValue: _FakeSecureStorageInterface_17( this, Invocation.getter(#secureStorageInterface), ), - ) as _i12.SecureStorageInterface); + ) as _i19.SecureStorageInterface); @override - List<_i20.NodeModel> get primaryNodes => (super.noSuchMethod( + List<_i30.NodeModel> get primaryNodes => (super.noSuchMethod( Invocation.getter(#primaryNodes), - returnValue: <_i20.NodeModel>[], - ) as List<_i20.NodeModel>); + returnValue: <_i30.NodeModel>[], + ) as List<_i30.NodeModel>); @override - List<_i20.NodeModel> get nodes => (super.noSuchMethod( + List<_i30.NodeModel> get nodes => (super.noSuchMethod( Invocation.getter(#nodes), - returnValue: <_i20.NodeModel>[], - ) as List<_i20.NodeModel>); + returnValue: <_i30.NodeModel>[], + ) as List<_i30.NodeModel>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i16.Future updateDefaults() => (super.noSuchMethod( + _i23.Future updateDefaults() => (super.noSuchMethod( Invocation.method( #updateDefaults, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future setPrimaryNodeFor({ - required _i15.Coin? coin, - required _i20.NodeModel? node, + _i23.Future setPrimaryNodeFor({ + required _i22.Coin? coin, + required _i30.NodeModel? node, bool? shouldNotifyListeners = false, }) => (super.noSuchMethod( @@ -1382,44 +2006,44 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i20.NodeModel? getPrimaryNodeFor({required _i15.Coin? coin}) => + _i30.NodeModel? getPrimaryNodeFor({required _i22.Coin? coin}) => (super.noSuchMethod(Invocation.method( #getPrimaryNodeFor, [], {#coin: coin}, - )) as _i20.NodeModel?); + )) as _i30.NodeModel?); @override - List<_i20.NodeModel> getNodesFor(_i15.Coin? coin) => (super.noSuchMethod( + List<_i30.NodeModel> getNodesFor(_i22.Coin? coin) => (super.noSuchMethod( Invocation.method( #getNodesFor, [coin], ), - returnValue: <_i20.NodeModel>[], - ) as List<_i20.NodeModel>); + returnValue: <_i30.NodeModel>[], + ) as List<_i30.NodeModel>); @override - _i20.NodeModel? getNodeById({required String? id}) => + _i30.NodeModel? getNodeById({required String? id}) => (super.noSuchMethod(Invocation.method( #getNodeById, [], {#id: id}, - )) as _i20.NodeModel?); + )) as _i30.NodeModel?); @override - List<_i20.NodeModel> failoverNodesFor({required _i15.Coin? coin}) => + List<_i30.NodeModel> failoverNodesFor({required _i22.Coin? coin}) => (super.noSuchMethod( Invocation.method( #failoverNodesFor, [], {#coin: coin}, ), - returnValue: <_i20.NodeModel>[], - ) as List<_i20.NodeModel>); + returnValue: <_i30.NodeModel>[], + ) as List<_i30.NodeModel>); @override - _i16.Future add( - _i20.NodeModel? node, + _i23.Future add( + _i30.NodeModel? node, String? password, bool? shouldNotifyListeners, ) => @@ -1432,11 +2056,11 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { shouldNotifyListeners, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future delete( + _i23.Future delete( String? id, bool? shouldNotifyListeners, ) => @@ -1448,11 +2072,11 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { shouldNotifyListeners, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future setEnabledState( + _i23.Future setEnabledState( String? id, bool? enabled, bool? shouldNotifyListeners, @@ -1466,12 +2090,12 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { shouldNotifyListeners, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future edit( - _i20.NodeModel? editedNode, + _i23.Future edit( + _i30.NodeModel? editedNode, String? password, bool? shouldNotifyListeners, ) => @@ -1484,20 +2108,20 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { shouldNotifyListeners, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future updateCommunityNodes() => (super.noSuchMethod( + _i23.Future updateCommunityNodes() => (super.noSuchMethod( Invocation.method( #updateCommunityNodes, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -1505,7 +2129,7 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -1548,23 +2172,23 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValueForMissingStub: null, ); @override - _i13.CoinServiceAPI get wallet => (super.noSuchMethod( + _i20.CoinServiceAPI get wallet => (super.noSuchMethod( Invocation.getter(#wallet), - returnValue: _FakeCoinServiceAPI_13( + returnValue: _FakeCoinServiceAPI_18( this, Invocation.getter(#wallet), ), - ) as _i13.CoinServiceAPI); + ) as _i20.CoinServiceAPI); @override bool get hasBackgroundRefreshListener => (super.noSuchMethod( Invocation.getter(#hasBackgroundRefreshListener), returnValue: false, ) as bool); @override - _i15.Coin get coin => (super.noSuchMethod( + _i22.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i15.Coin.bitcoin, - ) as _i15.Coin); + returnValue: _i22.Coin.bitcoin, + ) as _i22.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -1597,91 +2221,42 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValueForMissingStub: null, ); @override - _i16.Future<_i8.FeeObject> get fees => (super.noSuchMethod( + _i23.Future<_i8.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i16.Future<_i8.FeeObject>.value(_FakeFeeObject_7( + returnValue: _i23.Future<_i8.FeeObject>.value(_FakeFeeObject_5( this, Invocation.getter(#fees), )), - ) as _i16.Future<_i8.FeeObject>); + ) as _i23.Future<_i8.FeeObject>); @override - _i16.Future get maxFee => (super.noSuchMethod( + _i23.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future.value(0), + ) as _i23.Future); @override - _i16.Future get currentReceivingAddress => (super.noSuchMethod( + _i23.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future<_i9.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( + _i11.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_8( this, - Invocation.getter(#availableBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i9.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_6( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i9.Decimal); + ) as _i11.Balance); @override - _i16.Future<_i9.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i9.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i9.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#totalBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i9.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_6( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i9.Decimal); - @override - _i16.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); - @override - _i16.Future<_i8.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i23.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i16.Future<_i8.TransactionData>.value(_FakeTransactionData_8( - this, - Invocation.getter(#transactionData), - )), - ) as _i16.Future<_i8.TransactionData>); + _i23.Future>.value(<_i17.Transaction>[]), + ) as _i23.Future>); @override - _i16.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: - _i16.Future>.value(<_i8.UtxoObject>[]), - ) as _i16.Future>); + _i23.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i23.Future>.value(<_i17.UTXO>[]), + ) as _i23.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -1701,29 +2276,74 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValue: '', ) as String); @override - _i16.Future> get mnemonic => (super.noSuchMethod( + _i23.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); + returnValue: _i23.Future>.value([]), + ) as _i23.Future>); + @override + _i23.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i23.Future.value(), + ) as _i23.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i23.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i23.Future.value(''), + ) as _i23.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i16.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i23.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -1733,9 +2353,9 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValueForMissingStub: null, ); @override - _i16.Future> prepareSend({ + _i23.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i14.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -1744,50 +2364,32 @@ class MockManager extends _i1.Mock implements _i6.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i23.Future>.value({}), + ) as _i23.Future>); @override - _i16.Future confirmSend({required Map? txData}) => + _i23.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); - @override - _i16.Future refresh() => (super.noSuchMethod( + _i23.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -1797,34 +2399,35 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValue: false, ) as bool); @override - _i16.Future testNetworkConnection() => (super.noSuchMethod( + _i23.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future initializeNew() => (super.noSuchMethod( + _i23.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future initializeExisting() => (super.noSuchMethod( + _i23.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future recoverFromMnemonic({ + _i23.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -1835,25 +2438,26 @@ class MockManager extends _i1.Mock implements _i6.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future exitCurrentWallet() => (super.noSuchMethod( + _i23.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future fullRescan( + _i23.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -1865,42 +2469,52 @@ class MockManager extends _i1.Mock implements _i6.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); - @override - _i16.Future estimateFeeFor( - int? satoshiAmount, + _i23.Future<_i14.Amount> estimateFeeFor( + _i14.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future<_i14.Amount>.value(_FakeAmount_12( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i23.Future<_i14.Amount>); @override - _i16.Future generateNewAddress() => (super.noSuchMethod( + _i23.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + _i23.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + void addListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -1908,7 +2522,7 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -1928,7 +2542,7 @@ class MockManager extends _i1.Mock implements _i6.Manager { /// A class which mocks [CoinServiceAPI]. /// /// See the documentation for Mockito's code generation for more information. -class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { +class MockCoinServiceAPI extends _i1.Mock implements _i20.CoinServiceAPI { @override set onIsActiveWalletChanged(void Function(bool)? _onIsActiveWalletChanged) => super.noSuchMethod( @@ -1939,10 +2553,10 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { returnValueForMissingStub: null, ); @override - _i15.Coin get coin => (super.noSuchMethod( + _i22.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i15.Coin.bitcoin, - ) as _i15.Coin); + returnValue: _i22.Coin.bitcoin, + ) as _i22.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -1975,75 +2589,42 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { returnValueForMissingStub: null, ); @override - _i16.Future<_i8.FeeObject> get fees => (super.noSuchMethod( + _i23.Future<_i8.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i16.Future<_i8.FeeObject>.value(_FakeFeeObject_7( + returnValue: _i23.Future<_i8.FeeObject>.value(_FakeFeeObject_5( this, Invocation.getter(#fees), )), - ) as _i16.Future<_i8.FeeObject>); + ) as _i23.Future<_i8.FeeObject>); @override - _i16.Future get maxFee => (super.noSuchMethod( + _i23.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future.value(0), + ) as _i23.Future); @override - _i16.Future get currentReceivingAddress => (super.noSuchMethod( + _i23.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future<_i9.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( + _i11.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_8( this, - Invocation.getter(#availableBalance), - )), - ) as _i16.Future<_i9.Decimal>); + Invocation.getter(#balance), + ), + ) as _i11.Balance); @override - _i16.Future<_i9.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i9.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#totalBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i9.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); - @override - _i16.Future<_i8.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i23.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i16.Future<_i8.TransactionData>.value(_FakeTransactionData_8( - this, - Invocation.getter(#transactionData), - )), - ) as _i16.Future<_i8.TransactionData>); + _i23.Future>.value(<_i17.Transaction>[]), + ) as _i23.Future>); @override - _i16.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: - _i16.Future>.value(<_i8.UtxoObject>[]), - ) as _i16.Future>); + _i23.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i23.Future>.value(<_i17.UTXO>[]), + ) as _i23.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -2063,10 +2644,20 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { returnValue: '', ) as String); @override - _i16.Future> get mnemonic => (super.noSuchMethod( + _i23.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); + returnValue: _i23.Future>.value([]), + ) as _i23.Future>); + @override + _i23.Future get mnemonicString => (super.noSuchMethod( + Invocation.getter(#mnemonicString), + returnValue: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i23.Future.value(), + ) as _i23.Future); @override bool get hasCalledExit => (super.noSuchMethod( Invocation.getter(#hasCalledExit), @@ -2078,9 +2669,14 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { returnValue: false, ) as bool); @override - _i16.Future> prepareSend({ + int get storedChainHeight => (super.noSuchMethod( + Invocation.getter(#storedChainHeight), + returnValue: 0, + ) as int); + @override + _i23.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i14.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -2089,59 +2685,41 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i23.Future>.value({}), + ) as _i23.Future>); @override - _i16.Future confirmSend({required Map? txData}) => + _i23.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future send({ - required String? toAddress, - required int? amount, - Map? args, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); - @override - _i16.Future refresh() => (super.noSuchMethod( + _i23.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i23.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -2151,16 +2729,17 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { returnValue: false, ) as bool); @override - _i16.Future testNetworkConnection() => (super.noSuchMethod( + _i23.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future recoverFromMnemonic({ + _i23.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -2171,43 +2750,44 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future initializeNew() => (super.noSuchMethod( + _i23.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future initializeExisting() => (super.noSuchMethod( + _i23.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future exit() => (super.noSuchMethod( + _i23.Future exit() => (super.noSuchMethod( Invocation.method( #exit, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future fullRescan( + _i23.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -2219,40 +2799,49 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { maxNumberOfIndexesToCheck, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future estimateFeeFor( - int? satoshiAmount, + _i23.Future<_i14.Amount> estimateFeeFor( + _i14.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future<_i14.Amount>.value(_FakeAmount_12( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i23.Future<_i14.Amount>); @override - _i16.Future generateNewAddress() => (super.noSuchMethod( + _i23.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future updateSentCachedTxData(Map? txData) => + _i23.Future updateSentCachedTxData(Map? txData) => (super.noSuchMethod( Invocation.method( #updateSentCachedTxData, [txData], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); } diff --git a/test/widget_tests/wallet_info_row/wallet_info_row_test.dart b/test/widget_tests/wallet_info_row/wallet_info_row_test.dart index d0ca88983..ba669e8c0 100644 --- a/test/widget_tests/wallet_info_row/wallet_info_row_test.dart +++ b/test/widget_tests/wallet_info_row/wallet_info_row_test.dart @@ -3,6 +3,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; +import 'package:stackwallet/models/balance.dart'; +import 'package:stackwallet/models/isar/stack_theme.dart'; import 'package:stackwallet/providers/providers.dart'; import 'package:stackwallet/services/coins/bitcoin/bitcoin_wallet.dart'; import 'package:stackwallet/services/coins/coin_service.dart'; @@ -10,19 +12,20 @@ import 'package:stackwallet/services/coins/manager.dart'; import 'package:stackwallet/services/node_service.dart'; import 'package:stackwallet/services/wallets.dart'; import 'package:stackwallet/services/wallets_service.dart'; -import 'package:stackwallet/utilities/listenable_map.dart'; -import 'package:stackwallet/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance_future.dart'; -import 'package:stackwallet/widgets/wallet_info_row/sub_widgets/wallet_info_row_coin_icon.dart'; -import 'package:stackwallet/widgets/wallet_info_row/wallet_info_row.dart'; +import 'package:stackwallet/themes/stack_colors.dart'; +import 'package:stackwallet/themes/theme_service.dart'; +import 'package:stackwallet/utilities/amount/amount.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; -import 'package:stackwallet/utilities/theme/light_colors.dart'; -import 'package:stackwallet/utilities/theme/stack_colors.dart'; +import 'package:stackwallet/widgets/wallet_info_row/sub_widgets/wallet_info_row_balance.dart'; +import 'package:stackwallet/widgets/wallet_info_row/wallet_info_row.dart'; +import '../../sample_data/theme_json.dart'; import 'wallet_info_row_test.mocks.dart'; @GenerateMocks([ Wallets, WalletsService, + ThemeService, BitcoinWallet ], customMocks: [ MockSpec(returnNullOnMissingStub: true), @@ -33,10 +36,25 @@ import 'wallet_info_row_test.mocks.dart'; void main() { testWidgets("Test wallet info row displays correctly", (widgetTester) async { final wallets = MockWallets(); + final mockThemeService = MockThemeService(); final CoinServiceAPI wallet = MockBitcoinWallet(); + when(mockThemeService.getTheme(themeId: "light")).thenAnswer( + (_) => StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), + ); when(wallet.coin).thenAnswer((_) => Coin.bitcoin); when(wallet.walletName).thenAnswer((_) => "some wallet"); when(wallet.walletId).thenAnswer((_) => "some-wallet-id"); + when(wallet.balance).thenAnswer( + (_) => Balance( + total: Amount.zero, + spendable: Amount.zero, + blockedTotal: Amount.zero, + pendingSpendable: Amount.zero, + ), + ); final manager = Manager(wallet); when(wallets.getManagerProvider("some-wallet-id")).thenAnswer( @@ -47,12 +65,16 @@ void main() { ProviderScope( overrides: [ walletsChangeNotifierProvider.overrideWithValue(wallets), + pThemeService.overrideWithValue(mockThemeService), ], child: MaterialApp( theme: ThemeData( extensions: [ StackColors.fromStackColorTheme( - LightColors(), + StackTheme.fromJson( + json: lightThemeJsonMap, + applicationThemesDirectoryPath: "test", + ), ), ], ), @@ -63,7 +85,9 @@ void main() { ), ); + await widgetTester.pumpAndSettle(); + expect(find.text("some wallet"), findsOneWidget); - expect(find.byType(WalletInfoRowBalanceFuture), findsOneWidget); + expect(find.byType(WalletInfoRowBalance), findsOneWidget); }); } diff --git a/test/widget_tests/wallet_info_row/wallet_info_row_test.mocks.dart b/test/widget_tests/wallet_info_row/wallet_info_row_test.mocks.dart index 7f370eb9a..8c8fdc9df 100644 --- a/test/widget_tests/wallet_info_row/wallet_info_row_test.mocks.dart +++ b/test/widget_tests/wallet_info_row/wallet_info_row_test.mocks.dart @@ -3,29 +3,41 @@ // Do not manually edit this file. // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:async' as _i16; -import 'dart:ui' as _i18; +import 'dart:async' as _i23; +import 'dart:typed_data' as _i28; +import 'dart:ui' as _i25; -import 'package:decimal/decimal.dart' as _i9; +import 'package:bip32/bip32.dart' as _i16; +import 'package:bip47/bip47.dart' as _i18; +import 'package:bitcoindart/bitcoindart.dart' as _i13; import 'package:flutter/foundation.dart' as _i4; import 'package:flutter_riverpod/flutter_riverpod.dart' as _i5; import 'package:mockito/mockito.dart' as _i1; +import 'package:stackwallet/db/isar/main_db.dart' as _i7; import 'package:stackwallet/electrumx_rpc/cached_electrumx.dart' as _i11; import 'package:stackwallet/electrumx_rpc/electrumx.dart' as _i10; -import 'package:stackwallet/models/models.dart' as _i8; -import 'package:stackwallet/models/node_model.dart' as _i20; -import 'package:stackwallet/services/coins/bitcoin/bitcoin_wallet.dart' as _i19; -import 'package:stackwallet/services/coins/coin_service.dart' as _i13; +import 'package:stackwallet/models/balance.dart' as _i12; +import 'package:stackwallet/models/isar/models/isar_models.dart' as _i17; +import 'package:stackwallet/models/isar/stack_theme.dart' as _i27; +import 'package:stackwallet/models/node_model.dart' as _i32; +import 'package:stackwallet/models/paymint/fee_object_model.dart' as _i9; +import 'package:stackwallet/models/signing_data.dart' as _i31; +import 'package:stackwallet/services/coins/bitcoin/bitcoin_wallet.dart' as _i29; +import 'package:stackwallet/services/coins/coin_service.dart' as _i20; import 'package:stackwallet/services/coins/manager.dart' as _i6; import 'package:stackwallet/services/node_service.dart' as _i3; import 'package:stackwallet/services/transaction_notification_tracker.dart' - as _i7; -import 'package:stackwallet/services/wallets.dart' as _i14; + as _i8; +import 'package:stackwallet/services/wallets.dart' as _i21; import 'package:stackwallet/services/wallets_service.dart' as _i2; -import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i15; +import 'package:stackwallet/themes/theme_service.dart' as _i26; +import 'package:stackwallet/utilities/amount/amount.dart' as _i14; +import 'package:stackwallet/utilities/enums/coin_enum.dart' as _i22; +import 'package:stackwallet/utilities/enums/derive_path_type_enum.dart' as _i30; import 'package:stackwallet/utilities/flutter_secure_storage_interface.dart' - as _i12; -import 'package:stackwallet/utilities/prefs.dart' as _i17; + as _i19; +import 'package:stackwallet/utilities/prefs.dart' as _i24; +import 'package:tuple/tuple.dart' as _i15; // ignore_for_file: type=lint // ignore_for_file: avoid_redundant_argument_values @@ -80,9 +92,8 @@ class _FakeManager_3 extends _i1.SmartFake implements _i6.Manager { ); } -class _FakeTransactionNotificationTracker_4 extends _i1.SmartFake - implements _i7.TransactionNotificationTracker { - _FakeTransactionNotificationTracker_4( +class _FakeMainDB_4 extends _i1.SmartFake implements _i7.MainDB { + _FakeMainDB_4( Object parent, Invocation parentInvocation, ) : super( @@ -91,8 +102,9 @@ class _FakeTransactionNotificationTracker_4 extends _i1.SmartFake ); } -class _FakeUtxoData_5 extends _i1.SmartFake implements _i8.UtxoData { - _FakeUtxoData_5( +class _FakeTransactionNotificationTracker_5 extends _i1.SmartFake + implements _i8.TransactionNotificationTracker { + _FakeTransactionNotificationTracker_5( Object parent, Invocation parentInvocation, ) : super( @@ -101,8 +113,8 @@ class _FakeUtxoData_5 extends _i1.SmartFake implements _i8.UtxoData { ); } -class _FakeDecimal_6 extends _i1.SmartFake implements _i9.Decimal { - _FakeDecimal_6( +class _FakeFeeObject_6 extends _i1.SmartFake implements _i9.FeeObject { + _FakeFeeObject_6( Object parent, Invocation parentInvocation, ) : super( @@ -111,8 +123,8 @@ class _FakeDecimal_6 extends _i1.SmartFake implements _i9.Decimal { ); } -class _FakeFeeObject_7 extends _i1.SmartFake implements _i8.FeeObject { - _FakeFeeObject_7( +class _FakeElectrumX_7 extends _i1.SmartFake implements _i10.ElectrumX { + _FakeElectrumX_7( Object parent, Invocation parentInvocation, ) : super( @@ -121,30 +133,29 @@ class _FakeFeeObject_7 extends _i1.SmartFake implements _i8.FeeObject { ); } -class _FakeTransactionData_8 extends _i1.SmartFake - implements _i8.TransactionData { - _FakeTransactionData_8( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakeElectrumX_9 extends _i1.SmartFake implements _i10.ElectrumX { - _FakeElectrumX_9( - Object parent, - Invocation parentInvocation, - ) : super( - parent, - parentInvocation, - ); -} - -class _FakeCachedElectrumX_10 extends _i1.SmartFake +class _FakeCachedElectrumX_8 extends _i1.SmartFake implements _i11.CachedElectrumX { - _FakeCachedElectrumX_10( + _FakeCachedElectrumX_8( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeBalance_9 extends _i1.SmartFake implements _i12.Balance { + _FakeBalance_9( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeNetworkType_10 extends _i1.SmartFake implements _i13.NetworkType { + _FakeNetworkType_10( Object parent, Invocation parentInvocation, ) : super( @@ -164,9 +175,8 @@ class _FakeElectrumXNode_11 extends _i1.SmartFake ); } -class _FakeSecureStorageInterface_12 extends _i1.SmartFake - implements _i12.SecureStorageInterface { - _FakeSecureStorageInterface_12( +class _FakeAmount_12 extends _i1.SmartFake implements _i14.Amount { + _FakeAmount_12( Object parent, Invocation parentInvocation, ) : super( @@ -175,9 +185,61 @@ class _FakeSecureStorageInterface_12 extends _i1.SmartFake ); } -class _FakeCoinServiceAPI_13 extends _i1.SmartFake - implements _i13.CoinServiceAPI { - _FakeCoinServiceAPI_13( +class _FakeTuple2_13 extends _i1.SmartFake + implements _i15.Tuple2 { + _FakeTuple2_13( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeBIP32_14 extends _i1.SmartFake implements _i16.BIP32 { + _FakeBIP32_14( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAddress_15 extends _i1.SmartFake implements _i17.Address { + _FakeAddress_15( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePaymentCode_16 extends _i1.SmartFake implements _i18.PaymentCode { + _FakePaymentCode_16( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeSecureStorageInterface_17 extends _i1.SmartFake + implements _i19.SecureStorageInterface { + _FakeSecureStorageInterface_17( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeCoinServiceAPI_18 extends _i1.SmartFake + implements _i20.CoinServiceAPI { + _FakeCoinServiceAPI_18( Object parent, Invocation parentInvocation, ) : super( @@ -189,7 +251,7 @@ class _FakeCoinServiceAPI_13 extends _i1.SmartFake /// A class which mocks [Wallets]. /// /// See the documentation for Mockito's code generation for more information. -class MockWallets extends _i1.Mock implements _i14.Wallets { +class MockWallets extends _i1.Mock implements _i21.Wallets { MockWallets() { _i1.throwOnMissingStub(this); } @@ -256,7 +318,7 @@ class MockWallets extends _i1.Mock implements _i14.Wallets { returnValueForMissingStub: null, ); @override - List getWalletIdsFor({required _i15.Coin? coin}) => + List getWalletIdsFor({required _i22.Coin? coin}) => (super.noSuchMethod( Invocation.method( #getWalletIdsFor, @@ -266,15 +328,28 @@ class MockWallets extends _i1.Mock implements _i14.Wallets { returnValue: [], ) as List); @override - Map<_i15.Coin, List<_i5.ChangeNotifierProvider<_i6.Manager>>> + List<_i15.Tuple2<_i22.Coin, List<_i5.ChangeNotifierProvider<_i6.Manager>>>> getManagerProvidersByCoin() => (super.noSuchMethod( Invocation.method( #getManagerProvidersByCoin, [], ), - returnValue: <_i15.Coin, - List<_i5.ChangeNotifierProvider<_i6.Manager>>>{}, - ) as Map<_i15.Coin, List<_i5.ChangeNotifierProvider<_i6.Manager>>>); + returnValue: < + _i15.Tuple2<_i22.Coin, + List<_i5.ChangeNotifierProvider<_i6.Manager>>>>[], + ) as List< + _i15.Tuple2<_i22.Coin, + List<_i5.ChangeNotifierProvider<_i6.Manager>>>>); + @override + List<_i5.ChangeNotifierProvider<_i6.Manager>> getManagerProvidersForCoin( + _i22.Coin? coin) => + (super.noSuchMethod( + Invocation.method( + #getManagerProvidersForCoin, + [coin], + ), + returnValue: <_i5.ChangeNotifierProvider<_i6.Manager>>[], + ) as List<_i5.ChangeNotifierProvider<_i6.Manager>>); @override _i5.ChangeNotifierProvider<_i6.Manager> getManagerProvider( String? walletId) => @@ -331,17 +406,17 @@ class MockWallets extends _i1.Mock implements _i14.Wallets { returnValueForMissingStub: null, ); @override - _i16.Future load(_i17.Prefs? prefs) => (super.noSuchMethod( + _i23.Future load(_i24.Prefs? prefs) => (super.noSuchMethod( Invocation.method( #load, [prefs], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future loadAfterStackRestore( - _i17.Prefs? prefs, + _i23.Future loadAfterStackRestore( + _i24.Prefs? prefs, List<_i6.Manager>? managers, ) => (super.noSuchMethod( @@ -352,11 +427,11 @@ class MockWallets extends _i1.Mock implements _i14.Wallets { managers, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -364,7 +439,7 @@ class MockWallets extends _i1.Mock implements _i14.Wallets { returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -390,19 +465,19 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { } @override - _i16.Future> get walletNames => + _i23.Future> get walletNames => (super.noSuchMethod( Invocation.getter(#walletNames), - returnValue: _i16.Future>.value( + returnValue: _i23.Future>.value( {}), - ) as _i16.Future>); + ) as _i23.Future>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i16.Future renameWallet({ + _i23.Future renameWallet({ required String? from, required String? to, required bool? shouldNotifyListeners, @@ -417,13 +492,21 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future addExistingStackWallet({ + Map fetchWalletsData() => (super.noSuchMethod( + Invocation.method( + #fetchWalletsData, + [], + ), + returnValue: {}, + ) as Map); + @override + _i23.Future addExistingStackWallet({ required String? name, required String? walletId, - required _i15.Coin? coin, + required _i22.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -437,13 +520,13 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future addNewWallet({ + _i23.Future addNewWallet({ required String? name, - required _i15.Coin? coin, + required _i22.Coin? coin, required bool? shouldNotifyListeners, }) => (super.noSuchMethod( @@ -456,46 +539,46 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future> getFavoriteWalletIds() => (super.noSuchMethod( + _i23.Future> getFavoriteWalletIds() => (super.noSuchMethod( Invocation.method( #getFavoriteWalletIds, [], ), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); + returnValue: _i23.Future>.value([]), + ) as _i23.Future>); @override - _i16.Future saveFavoriteWalletIds(List? walletIds) => + _i23.Future saveFavoriteWalletIds(List? walletIds) => (super.noSuchMethod( Invocation.method( #saveFavoriteWalletIds, [walletIds], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future addFavorite(String? walletId) => (super.noSuchMethod( + _i23.Future addFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #addFavorite, [walletId], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future removeFavorite(String? walletId) => (super.noSuchMethod( + _i23.Future removeFavorite(String? walletId) => (super.noSuchMethod( Invocation.method( #removeFavorite, [walletId], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future moveFavorite({ + _i23.Future moveFavorite({ required int? fromIndex, required int? toIndex, }) => @@ -508,48 +591,48 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { #toIndex: toIndex, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future checkForDuplicate(String? name) => (super.noSuchMethod( + _i23.Future checkForDuplicate(String? name) => (super.noSuchMethod( Invocation.method( #checkForDuplicate, [name], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future getWalletId(String? walletName) => (super.noSuchMethod( + _i23.Future getWalletId(String? walletName) => (super.noSuchMethod( Invocation.method( #getWalletId, [walletName], ), - returnValue: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future isMnemonicVerified({required String? walletId}) => + _i23.Future isMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #isMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future setMnemonicVerified({required String? walletId}) => + _i23.Future setMnemonicVerified({required String? walletId}) => (super.noSuchMethod( Invocation.method( #setMnemonicVerified, [], {#walletId: walletId}, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future deleteWallet( + _i23.Future deleteWallet( String? name, bool? shouldNotifyListeners, ) => @@ -561,20 +644,20 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { shouldNotifyListeners, ], ), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future.value(0), + ) as _i23.Future); @override - _i16.Future refreshWallets(bool? shouldNotifyListeners) => + _i23.Future refreshWallets(bool? shouldNotifyListeners) => (super.noSuchMethod( Invocation.method( #refreshWallets, [shouldNotifyListeners], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -582,7 +665,7 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -607,16 +690,106 @@ class MockWalletsService extends _i1.Mock implements _i2.WalletsService { ); } +/// A class which mocks [ThemeService]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockThemeService extends _i1.Mock implements _i26.ThemeService { + MockThemeService() { + _i1.throwOnMissingStub(this); + } + + @override + _i7.MainDB get db => (super.noSuchMethod( + Invocation.getter(#db), + returnValue: _FakeMainDB_4( + this, + Invocation.getter(#db), + ), + ) as _i7.MainDB); + @override + List<_i27.StackTheme> get installedThemes => (super.noSuchMethod( + Invocation.getter(#installedThemes), + returnValue: <_i27.StackTheme>[], + ) as List<_i27.StackTheme>); + @override + void init(_i7.MainDB? db) => super.noSuchMethod( + Invocation.method( + #init, + [db], + ), + returnValueForMissingStub: null, + ); + @override + _i23.Future install({required _i28.Uint8List? themeArchiveData}) => + (super.noSuchMethod( + Invocation.method( + #install, + [], + {#themeArchiveData: themeArchiveData}, + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future remove({required String? themeId}) => (super.noSuchMethod( + Invocation.method( + #remove, + [], + {#themeId: themeId}, + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future verifyInstalled({required String? themeId}) => + (super.noSuchMethod( + Invocation.method( + #verifyInstalled, + [], + {#themeId: themeId}, + ), + returnValue: _i23.Future.value(false), + ) as _i23.Future); + @override + _i23.Future> fetchThemes() => + (super.noSuchMethod( + Invocation.method( + #fetchThemes, + [], + ), + returnValue: _i23.Future>.value( + <_i26.StackThemeMetaData>[]), + ) as _i23.Future>); + @override + _i23.Future<_i28.Uint8List> fetchTheme( + {required _i26.StackThemeMetaData? themeMetaData}) => + (super.noSuchMethod( + Invocation.method( + #fetchTheme, + [], + {#themeMetaData: themeMetaData}, + ), + returnValue: _i23.Future<_i28.Uint8List>.value(_i28.Uint8List(0)), + ) as _i23.Future<_i28.Uint8List>); + @override + _i27.StackTheme? getTheme({required String? themeId}) => + (super.noSuchMethod(Invocation.method( + #getTheme, + [], + {#themeId: themeId}, + )) as _i27.StackTheme?); +} + /// A class which mocks [BitcoinWallet]. /// /// See the documentation for Mockito's code generation for more information. -class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { +class MockBitcoinWallet extends _i1.Mock implements _i29.BitcoinWallet { MockBitcoinWallet() { _i1.throwOnMissingStub(this); } @override - set timer(_i16.Timer? _timer) => super.noSuchMethod( + set timer(_i23.Timer? _timer) => super.noSuchMethod( Invocation.setter( #timer, _timer, @@ -624,15 +797,15 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValueForMissingStub: null, ); @override - _i7.TransactionNotificationTracker get txTracker => (super.noSuchMethod( + _i8.TransactionNotificationTracker get txTracker => (super.noSuchMethod( Invocation.getter(#txTracker), - returnValue: _FakeTransactionNotificationTracker_4( + returnValue: _FakeTransactionNotificationTracker_5( this, Invocation.getter(#txTracker), ), - ) as _i7.TransactionNotificationTracker); + ) as _i8.TransactionNotificationTracker); @override - set txTracker(_i7.TransactionNotificationTracker? _txTracker) => + set txTracker(_i8.TransactionNotificationTracker? _txTracker) => super.noSuchMethod( Invocation.setter( #txTracker, @@ -641,19 +814,6 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValueForMissingStub: null, ); @override - List<_i8.UtxoObject> get outputsList => (super.noSuchMethod( - Invocation.getter(#outputsList), - returnValue: <_i8.UtxoObject>[], - ) as List<_i8.UtxoObject>); - @override - set outputsList(List<_i8.UtxoObject>? _outputsList) => super.noSuchMethod( - Invocation.setter( - #outputsList, - _outputsList, - ), - returnValueForMissingStub: null, - ); - @override bool get longMutex => (super.noSuchMethod( Invocation.getter(#longMutex), returnValue: false, @@ -680,14 +840,6 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValueForMissingStub: null, ); @override - set cachedTxData(_i8.TransactionData? _cachedTxData) => super.noSuchMethod( - Invocation.setter( - #cachedTxData, - _cachedTxData, - ), - returnValueForMissingStub: null, - ); - @override bool get isActive => (super.noSuchMethod( Invocation.getter(#isActive), returnValue: false, @@ -714,104 +866,74 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValue: false, ) as bool); @override - _i15.Coin get coin => (super.noSuchMethod( + _i22.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i15.Coin.bitcoin, - ) as _i15.Coin); + returnValue: _i22.Coin.bitcoin, + ) as _i22.Coin); @override - _i16.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); + _i23.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i23.Future>.value(<_i17.UTXO>[]), + ) as _i23.Future>); @override - _i16.Future<_i8.UtxoData> get utxoData => (super.noSuchMethod( - Invocation.getter(#utxoData), - returnValue: _i16.Future<_i8.UtxoData>.value(_FakeUtxoData_5( - this, - Invocation.getter(#utxoData), - )), - ) as _i16.Future<_i8.UtxoData>); - @override - _i16.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), + _i23.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i16.Future>.value(<_i8.UtxoObject>[]), - ) as _i16.Future>); + _i23.Future>.value(<_i17.Transaction>[]), + ) as _i23.Future>); @override - _i16.Future<_i9.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#availableBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i9.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i9.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i9.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#totalBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future get currentReceivingAddress => (super.noSuchMethod( + _i23.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future get currentLegacyReceivingAddress => (super.noSuchMethod( - Invocation.getter(#currentLegacyReceivingAddress), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + _i23.Future get currentChangeAddress => (super.noSuchMethod( + Invocation.getter(#currentChangeAddress), + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future get currentReceivingAddressP2SH => (super.noSuchMethod( - Invocation.getter(#currentReceivingAddressP2SH), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + _i23.Future get currentChangeAddressP2PKH => (super.noSuchMethod( + Invocation.getter(#currentChangeAddressP2PKH), + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override bool get hasCalledExit => (super.noSuchMethod( Invocation.getter(#hasCalledExit), returnValue: false, ) as bool); @override - _i16.Future<_i8.FeeObject> get fees => (super.noSuchMethod( + _i23.Future<_i9.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i16.Future<_i8.FeeObject>.value(_FakeFeeObject_7( + returnValue: _i23.Future<_i9.FeeObject>.value(_FakeFeeObject_6( this, Invocation.getter(#fees), )), - ) as _i16.Future<_i8.FeeObject>); + ) as _i23.Future<_i9.FeeObject>); @override - _i16.Future get maxFee => (super.noSuchMethod( + _i23.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future.value(0), + ) as _i23.Future); @override - _i16.Future> get mnemonic => (super.noSuchMethod( + _i23.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); + returnValue: _i23.Future>.value([]), + ) as _i23.Future>); @override - _i16.Future get chainHeight => (super.noSuchMethod( + _i23.Future get mnemonicString => (super.noSuchMethod( + Invocation.getter(#mnemonicString), + returnValue: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future get chainHeight => (super.noSuchMethod( Invocation.getter(#chainHeight), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future.value(0), + ) as _i23.Future); @override int get storedChainHeight => (super.noSuchMethod( Invocation.getter(#storedChainHeight), @@ -841,15 +963,6 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValue: false, ) as bool); @override - _i16.Future<_i8.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), - returnValue: - _i16.Future<_i8.TransactionData>.value(_FakeTransactionData_8( - this, - Invocation.getter(#transactionData), - )), - ) as _i16.Future<_i8.TransactionData>); - @override String get walletId => (super.noSuchMethod( Invocation.getter(#walletId), returnValue: '', @@ -870,7 +983,7 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { @override _i10.ElectrumX get electrumXClient => (super.noSuchMethod( Invocation.getter(#electrumXClient), - returnValue: _FakeElectrumX_9( + returnValue: _FakeElectrumX_7( this, Invocation.getter(#electrumXClient), ), @@ -878,12 +991,25 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { @override _i11.CachedElectrumX get cachedElectrumXClient => (super.noSuchMethod( Invocation.getter(#cachedElectrumXClient), - returnValue: _FakeCachedElectrumX_10( + returnValue: _FakeCachedElectrumX_8( this, Invocation.getter(#cachedElectrumXClient), ), ) as _i11.CachedElectrumX); @override + _i12.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_9( + this, + Invocation.getter(#balance), + ), + ) as _i12.Balance); + @override + _i23.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i23.Future.value(''), + ) as _i23.Future); + @override set onIsActiveWalletChanged(void Function(bool)? _onIsActiveWalletChanged) => super.noSuchMethod( Invocation.setter( @@ -893,38 +1019,44 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValueForMissingStub: null, ); @override - _i16.Future exit() => (super.noSuchMethod( + _i7.MainDB get db => (super.noSuchMethod( + Invocation.getter(#db), + returnValue: _FakeMainDB_4( + this, + Invocation.getter(#db), + ), + ) as _i7.MainDB); + @override + _i13.NetworkType get networkType => (super.noSuchMethod( + Invocation.getter(#networkType), + returnValue: _FakeNetworkType_10( + this, + Invocation.getter(#networkType), + ), + ) as _i13.NetworkType); + @override + _i23.Future exit() => (super.noSuchMethod( Invocation.method( #exit, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future updateStoredChainHeight({required int? newHeight}) => - (super.noSuchMethod( - Invocation.method( - #updateStoredChainHeight, - [], - {#newHeight: newHeight}, - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i19.DerivePathType addressType({required String? address}) => + _i30.DerivePathType addressType({required String? address}) => (super.noSuchMethod( Invocation.method( #addressType, [], {#address: address}, ), - returnValue: _i19.DerivePathType.bip44, - ) as _i19.DerivePathType); + returnValue: _i30.DerivePathType.bip44, + ) as _i30.DerivePathType); @override - _i16.Future recoverFromMnemonic({ + _i23.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -935,55 +1067,55 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future getTransactionCacheEarly(List? allAddresses) => + _i23.Future getTransactionCacheEarly(List? allAddresses) => (super.noSuchMethod( Invocation.method( #getTransactionCacheEarly, [allAddresses], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future refreshIfThereIsNewData() => (super.noSuchMethod( + _i23.Future refreshIfThereIsNewData() => (super.noSuchMethod( Invocation.method( #refreshIfThereIsNewData, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future getAllTxsToWatch(_i8.TransactionData? txData) => - (super.noSuchMethod( + _i23.Future getAllTxsToWatch() => (super.noSuchMethod( Invocation.method( #getAllTxsToWatch, - [txData], + [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future refresh() => (super.noSuchMethod( + _i23.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future> prepareSend({ + _i23.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i14.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -992,49 +1124,31 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i23.Future>.value({}), + ) as _i23.Future>); @override - _i16.Future confirmSend({required Map? txData}) => + _i23.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); - @override - _i16.Future testNetworkConnection() => (super.noSuchMethod( + _i23.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override void startNetworkAlivePinging() => super.noSuchMethod( Invocation.method( @@ -1052,33 +1166,33 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValueForMissingStub: null, ); @override - _i16.Future initializeNew() => (super.noSuchMethod( + _i23.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future initializeExisting() => (super.noSuchMethod( + _i23.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future updateSentCachedTxData(Map? txData) => + _i23.Future updateSentCachedTxData(Map? txData) => (super.noSuchMethod( Invocation.method( #updateSentCachedTxData, [txData], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -1088,112 +1202,70 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValue: false, ) as bool); @override - _i16.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i23.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future<_i10.ElectrumXNode> getCurrentNode() => (super.noSuchMethod( + _i23.Future<_i10.ElectrumXNode> getCurrentNode() => (super.noSuchMethod( Invocation.method( #getCurrentNode, [], ), returnValue: - _i16.Future<_i10.ElectrumXNode>.value(_FakeElectrumXNode_11( + _i23.Future<_i10.ElectrumXNode>.value(_FakeElectrumXNode_11( this, Invocation.method( #getCurrentNode, [], ), )), - ) as _i16.Future<_i10.ElectrumXNode>); + ) as _i23.Future<_i10.ElectrumXNode>); @override - _i16.Future addDerivation({ - required int? chain, - required String? address, - required String? pubKey, - required String? wif, - required _i19.DerivePathType? derivePathType, - }) => - (super.noSuchMethod( - Invocation.method( - #addDerivation, - [], - { - #chain: chain, - #address: address, - #pubKey: pubKey, - #wif: wif, - #derivePathType: derivePathType, - }, - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i16.Future addDerivations({ - required int? chain, - required _i19.DerivePathType? derivePathType, - required Map? derivationsToAdd, - }) => - (super.noSuchMethod( - Invocation.method( - #addDerivations, - [], - { - #chain: chain, - #derivePathType: derivePathType, - #derivationsToAdd: derivationsToAdd, - }, - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i16.Future getTxCount({required String? address}) => - (super.noSuchMethod( - Invocation.method( - #getTxCount, - [], - {#address: address}, - ), - returnValue: _i16.Future.value(0), - ) as _i16.Future); - @override - _i16.Future checkCurrentReceivingAddressesForTransactions() => - (super.noSuchMethod( - Invocation.method( - #checkCurrentReceivingAddressesForTransactions, - [], - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i16.Future checkCurrentChangeAddressesForTransactions() => - (super.noSuchMethod( - Invocation.method( - #checkCurrentChangeAddressesForTransactions, - [], - ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); - @override - _i16.Future>> fastFetch( + _i23.Future>> fastFetch( List? allTxHashes) => (super.noSuchMethod( Invocation.method( #fastFetch, [allTxHashes], ), - returnValue: _i16.Future>>.value( + returnValue: _i23.Future>>.value( >[]), - ) as _i16.Future>>); + ) as _i23.Future>>); + @override + _i23.Future getTxCount({required String? address}) => + (super.noSuchMethod( + Invocation.method( + #getTxCount, + [], + {#address: address}, + ), + returnValue: _i23.Future.value(0), + ) as _i23.Future); + @override + _i23.Future checkCurrentReceivingAddressesForTransactions() => + (super.noSuchMethod( + Invocation.method( + #checkCurrentReceivingAddressesForTransactions, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future checkCurrentChangeAddressesForTransactions() => + (super.noSuchMethod( + Invocation.method( + #checkCurrentChangeAddressesForTransactions, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override int estimateTxFee({ required int? vSize, @@ -1211,42 +1283,42 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { returnValue: 0, ) as int); @override - dynamic coinSelection( - int? satoshiAmountToSend, - int? selectedTxFeeRate, - String? _recipientAddress, - bool? isSendAll, { + dynamic coinSelection({ + required int? satoshiAmountToSend, + required int? selectedTxFeeRate, + required String? recipientAddress, + required bool? coinControl, + required bool? isSendAll, int? additionalOutputs = 0, - List<_i8.UtxoObject>? utxos, + List<_i17.UTXO>? utxos, }) => super.noSuchMethod(Invocation.method( #coinSelection, - [ - satoshiAmountToSend, - selectedTxFeeRate, - _recipientAddress, - isSendAll, - ], + [], { + #satoshiAmountToSend: satoshiAmountToSend, + #selectedTxFeeRate: selectedTxFeeRate, + #recipientAddress: recipientAddress, + #coinControl: coinControl, + #isSendAll: isSendAll, #additionalOutputs: additionalOutputs, #utxos: utxos, }, )); @override - _i16.Future> fetchBuildTxData( - List<_i8.UtxoObject>? utxosToUse) => + _i23.Future> fetchBuildTxData( + List<_i17.UTXO>? utxosToUse) => (super.noSuchMethod( Invocation.method( #fetchBuildTxData, [utxosToUse], ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i23.Future>.value(<_i31.SigningData>[]), + ) as _i23.Future>); @override - _i16.Future> buildTransaction({ - required List<_i8.UtxoObject>? utxosToUse, - required Map? utxoSigningData, + _i23.Future> buildTransaction({ + required List<_i31.SigningData>? utxoSigningData, required List? recipients, required List? satoshiAmounts, }) => @@ -1255,17 +1327,16 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { #buildTransaction, [], { - #utxosToUse: utxosToUse, #utxoSigningData: utxoSigningData, #recipients: recipients, #satoshiAmounts: satoshiAmounts, }, ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i23.Future>.value({}), + ) as _i23.Future>); @override - _i16.Future fullRescan( + _i23.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -1277,26 +1348,35 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { maxNumberOfIndexesToCheck, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future estimateFeeFor( - int? satoshiAmount, + _i23.Future<_i14.Amount> estimateFeeFor( + _i14.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future<_i14.Amount>.value(_FakeAmount_12( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i23.Future<_i14.Amount>); @override - int roughFeeEstimate( + _i14.Amount roughFeeEstimate( int? inputCount, int? outputCount, int? feeRatePerKB, @@ -1310,24 +1390,662 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { feeRatePerKB, ], ), - returnValue: 0, - ) as int); + returnValue: _FakeAmount_12( + this, + Invocation.method( + #roughFeeEstimate, + [ + inputCount, + outputCount, + feeRatePerKB, + ], + ), + ), + ) as _i14.Amount); @override - int sweepAllEstimate(int? feeRate) => (super.noSuchMethod( + _i23.Future<_i14.Amount> sweepAllEstimate(int? feeRate) => + (super.noSuchMethod( Invocation.method( #sweepAllEstimate, [feeRate], ), - returnValue: 0, - ) as int); + returnValue: _i23.Future<_i14.Amount>.value(_FakeAmount_12( + this, + Invocation.method( + #sweepAllEstimate, + [feeRate], + ), + )), + ) as _i23.Future<_i14.Amount>); @override - _i16.Future generateNewAddress() => (super.noSuchMethod( + _i23.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); + @override + void initCache( + String? walletId, + _i22.Coin? coin, + ) => + super.noSuchMethod( + Invocation.method( + #initCache, + [ + walletId, + coin, + ], + ), + returnValueForMissingStub: null, + ); + @override + _i23.Future updateCachedId(String? id) => (super.noSuchMethod( + Invocation.method( + #updateCachedId, + [id], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + int getCachedChainHeight() => (super.noSuchMethod( + Invocation.method( + #getCachedChainHeight, + [], + ), + returnValue: 0, + ) as int); + @override + _i23.Future updateCachedChainHeight(int? height) => (super.noSuchMethod( + Invocation.method( + #updateCachedChainHeight, + [height], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + bool getCachedIsFavorite() => (super.noSuchMethod( + Invocation.method( + #getCachedIsFavorite, + [], + ), + returnValue: false, + ) as bool); + @override + _i23.Future updateCachedIsFavorite(bool? isFavorite) => + (super.noSuchMethod( + Invocation.method( + #updateCachedIsFavorite, + [isFavorite], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i12.Balance getCachedBalance() => (super.noSuchMethod( + Invocation.method( + #getCachedBalance, + [], + ), + returnValue: _FakeBalance_9( + this, + Invocation.method( + #getCachedBalance, + [], + ), + ), + ) as _i12.Balance); + @override + _i23.Future updateCachedBalance(_i12.Balance? balance) => + (super.noSuchMethod( + Invocation.method( + #updateCachedBalance, + [balance], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i12.Balance getCachedBalanceSecondary() => (super.noSuchMethod( + Invocation.method( + #getCachedBalanceSecondary, + [], + ), + returnValue: _FakeBalance_9( + this, + Invocation.method( + #getCachedBalanceSecondary, + [], + ), + ), + ) as _i12.Balance); + @override + _i23.Future updateCachedBalanceSecondary(_i12.Balance? balance) => + (super.noSuchMethod( + Invocation.method( + #updateCachedBalanceSecondary, + [balance], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + List getWalletTokenContractAddresses() => (super.noSuchMethod( + Invocation.method( + #getWalletTokenContractAddresses, + [], + ), + returnValue: [], + ) as List); + @override + _i23.Future updateWalletTokenContractAddresses( + List? contractAddresses) => + (super.noSuchMethod( + Invocation.method( + #updateWalletTokenContractAddresses, + [contractAddresses], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + void initWalletDB({_i7.MainDB? mockableOverride}) => super.noSuchMethod( + Invocation.method( + #initWalletDB, + [], + {#mockableOverride: mockableOverride}, + ), + returnValueForMissingStub: null, + ); + @override + _i23.Future<_i15.Tuple2<_i17.Transaction, _i17.Address>> parseTransaction( + Map? txData, + dynamic electrumxClient, + List<_i17.Address>? myAddresses, + _i22.Coin? coin, + int? minConfirms, + String? walletId, + ) => + (super.noSuchMethod( + Invocation.method( + #parseTransaction, + [ + txData, + electrumxClient, + myAddresses, + coin, + minConfirms, + walletId, + ], + ), + returnValue: + _i23.Future<_i15.Tuple2<_i17.Transaction, _i17.Address>>.value( + _FakeTuple2_13<_i17.Transaction, _i17.Address>( + this, + Invocation.method( + #parseTransaction, + [ + txData, + electrumxClient, + myAddresses, + coin, + minConfirms, + walletId, + ], + ), + )), + ) as _i23.Future<_i15.Tuple2<_i17.Transaction, _i17.Address>>); + @override + void initPaynymWalletInterface({ + required String? walletId, + required String? walletName, + required _i13.NetworkType? network, + required _i22.Coin? coin, + required _i7.MainDB? db, + required _i10.ElectrumX? electrumXClient, + required _i19.SecureStorageInterface? secureStorage, + required int? dustLimit, + required int? dustLimitP2PKH, + required int? minConfirms, + required _i23.Future Function()? getMnemonicString, + required _i23.Future Function()? getMnemonicPassphrase, + required _i23.Future Function()? getChainHeight, + required _i23.Future Function()? getCurrentChangeAddress, + required int Function({ + required int feeRatePerKB, + required int vSize, + })? + estimateTxFee, + required _i23.Future> Function({ + required String address, + required _i14.Amount amount, + Map? args, + })? + prepareSend, + required _i23.Future Function({required String address})? getTxCount, + required _i23.Future> Function(List<_i17.UTXO>)? + fetchBuildTxData, + required _i23.Future Function()? refresh, + required _i23.Future Function()? checkChangeAddressForTransactions, + }) => + super.noSuchMethod( + Invocation.method( + #initPaynymWalletInterface, + [], + { + #walletId: walletId, + #walletName: walletName, + #network: network, + #coin: coin, + #db: db, + #electrumXClient: electrumXClient, + #secureStorage: secureStorage, + #dustLimit: dustLimit, + #dustLimitP2PKH: dustLimitP2PKH, + #minConfirms: minConfirms, + #getMnemonicString: getMnemonicString, + #getMnemonicPassphrase: getMnemonicPassphrase, + #getChainHeight: getChainHeight, + #getCurrentChangeAddress: getCurrentChangeAddress, + #estimateTxFee: estimateTxFee, + #prepareSend: prepareSend, + #getTxCount: getTxCount, + #fetchBuildTxData: fetchBuildTxData, + #refresh: refresh, + #checkChangeAddressForTransactions: + checkChangeAddressForTransactions, + }, + ), + returnValueForMissingStub: null, + ); + @override + _i23.Future<_i16.BIP32> getBip47BaseNode() => (super.noSuchMethod( + Invocation.method( + #getBip47BaseNode, + [], + ), + returnValue: _i23.Future<_i16.BIP32>.value(_FakeBIP32_14( + this, + Invocation.method( + #getBip47BaseNode, + [], + ), + )), + ) as _i23.Future<_i16.BIP32>); + @override + _i23.Future<_i28.Uint8List> getPrivateKeyForPaynymReceivingAddress({ + required String? paymentCodeString, + required int? index, + }) => + (super.noSuchMethod( + Invocation.method( + #getPrivateKeyForPaynymReceivingAddress, + [], + { + #paymentCodeString: paymentCodeString, + #index: index, + }, + ), + returnValue: _i23.Future<_i28.Uint8List>.value(_i28.Uint8List(0)), + ) as _i23.Future<_i28.Uint8List>); + @override + _i23.Future<_i17.Address> currentReceivingPaynymAddress({ + required _i18.PaymentCode? sender, + required bool? isSegwit, + }) => + (super.noSuchMethod( + Invocation.method( + #currentReceivingPaynymAddress, + [], + { + #sender: sender, + #isSegwit: isSegwit, + }, + ), + returnValue: _i23.Future<_i17.Address>.value(_FakeAddress_15( + this, + Invocation.method( + #currentReceivingPaynymAddress, + [], + { + #sender: sender, + #isSegwit: isSegwit, + }, + ), + )), + ) as _i23.Future<_i17.Address>); + @override + _i23.Future checkCurrentPaynymReceivingAddressForTransactions({ + required _i18.PaymentCode? sender, + required bool? isSegwit, + }) => + (super.noSuchMethod( + Invocation.method( + #checkCurrentPaynymReceivingAddressForTransactions, + [], + { + #sender: sender, + #isSegwit: isSegwit, + }, + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future checkAllCurrentReceivingPaynymAddressesForTransactions() => + (super.noSuchMethod( + Invocation.method( + #checkAllCurrentReceivingPaynymAddressesForTransactions, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future<_i16.BIP32> deriveNotificationBip32Node() => (super.noSuchMethod( + Invocation.method( + #deriveNotificationBip32Node, + [], + ), + returnValue: _i23.Future<_i16.BIP32>.value(_FakeBIP32_14( + this, + Invocation.method( + #deriveNotificationBip32Node, + [], + ), + )), + ) as _i23.Future<_i16.BIP32>); + @override + _i23.Future<_i18.PaymentCode> getPaymentCode({required bool? isSegwit}) => + (super.noSuchMethod( + Invocation.method( + #getPaymentCode, + [], + {#isSegwit: isSegwit}, + ), + returnValue: _i23.Future<_i18.PaymentCode>.value(_FakePaymentCode_16( + this, + Invocation.method( + #getPaymentCode, + [], + {#isSegwit: isSegwit}, + ), + )), + ) as _i23.Future<_i18.PaymentCode>); + @override + _i23.Future<_i28.Uint8List> signWithNotificationKey(_i28.Uint8List? data) => + (super.noSuchMethod( + Invocation.method( + #signWithNotificationKey, + [data], + ), + returnValue: _i23.Future<_i28.Uint8List>.value(_i28.Uint8List(0)), + ) as _i23.Future<_i28.Uint8List>); + @override + _i23.Future signStringWithNotificationKey(String? data) => + (super.noSuchMethod( + Invocation.method( + #signStringWithNotificationKey, + [data], + ), + returnValue: _i23.Future.value(''), + ) as _i23.Future); + @override + _i23.Future> preparePaymentCodeSend({ + required _i18.PaymentCode? paymentCode, + required bool? isSegwit, + required _i14.Amount? amount, + Map? args, + }) => + (super.noSuchMethod( + Invocation.method( + #preparePaymentCodeSend, + [], + { + #paymentCode: paymentCode, + #isSegwit: isSegwit, + #amount: amount, + #args: args, + }, + ), + returnValue: + _i23.Future>.value({}), + ) as _i23.Future>); + @override + _i23.Future<_i17.Address> nextUnusedSendAddressFrom({ + required _i18.PaymentCode? pCode, + required bool? isSegwit, + required _i16.BIP32? privateKeyNode, + int? startIndex = 0, + }) => + (super.noSuchMethod( + Invocation.method( + #nextUnusedSendAddressFrom, + [], + { + #pCode: pCode, + #isSegwit: isSegwit, + #privateKeyNode: privateKeyNode, + #startIndex: startIndex, + }, + ), + returnValue: _i23.Future<_i17.Address>.value(_FakeAddress_15( + this, + Invocation.method( + #nextUnusedSendAddressFrom, + [], + { + #pCode: pCode, + #isSegwit: isSegwit, + #privateKeyNode: privateKeyNode, + #startIndex: startIndex, + }, + ), + )), + ) as _i23.Future<_i17.Address>); + @override + _i23.Future> prepareNotificationTx({ + required int? selectedTxFeeRate, + required String? targetPaymentCodeString, + int? additionalOutputs = 0, + List<_i17.UTXO>? utxos, + }) => + (super.noSuchMethod( + Invocation.method( + #prepareNotificationTx, + [], + { + #selectedTxFeeRate: selectedTxFeeRate, + #targetPaymentCodeString: targetPaymentCodeString, + #additionalOutputs: additionalOutputs, + #utxos: utxos, + }, + ), + returnValue: + _i23.Future>.value({}), + ) as _i23.Future>); + @override + _i23.Future broadcastNotificationTx( + {required Map? preparedTx}) => + (super.noSuchMethod( + Invocation.method( + #broadcastNotificationTx, + [], + {#preparedTx: preparedTx}, + ), + returnValue: _i23.Future.value(''), + ) as _i23.Future); + @override + _i23.Future hasConnected(String? paymentCodeString) => + (super.noSuchMethod( + Invocation.method( + #hasConnected, + [paymentCodeString], + ), + returnValue: _i23.Future.value(false), + ) as _i23.Future); + @override + _i23.Future<_i18.PaymentCode?> unBlindedPaymentCodeFromTransaction( + {required _i17.Transaction? transaction}) => + (super.noSuchMethod( + Invocation.method( + #unBlindedPaymentCodeFromTransaction, + [], + {#transaction: transaction}, + ), + returnValue: _i23.Future<_i18.PaymentCode?>.value(), + ) as _i23.Future<_i18.PaymentCode?>); + @override + _i23.Future<_i18.PaymentCode?> unBlindedPaymentCodeFromTransactionBad( + {required _i17.Transaction? transaction}) => + (super.noSuchMethod( + Invocation.method( + #unBlindedPaymentCodeFromTransactionBad, + [], + {#transaction: transaction}, + ), + returnValue: _i23.Future<_i18.PaymentCode?>.value(), + ) as _i23.Future<_i18.PaymentCode?>); + @override + _i23.Future> + getAllPaymentCodesFromNotificationTransactions() => (super.noSuchMethod( + Invocation.method( + #getAllPaymentCodesFromNotificationTransactions, + [], + ), + returnValue: + _i23.Future>.value(<_i18.PaymentCode>[]), + ) as _i23.Future>); + @override + _i23.Future checkForNotificationTransactionsTo( + Set? otherCodeStrings) => + (super.noSuchMethod( + Invocation.method( + #checkForNotificationTransactionsTo, + [otherCodeStrings], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future restoreAllHistory({ + required int? maxUnusedAddressGap, + required int? maxNumberOfIndexesToCheck, + required Set? paymentCodeStrings, + }) => + (super.noSuchMethod( + Invocation.method( + #restoreAllHistory, + [], + { + #maxUnusedAddressGap: maxUnusedAddressGap, + #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + #paymentCodeStrings: paymentCodeStrings, + }, + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future restoreHistoryWith({ + required _i18.PaymentCode? other, + required bool? checkSegwitAsWell, + required int? maxUnusedAddressGap, + required int? maxNumberOfIndexesToCheck, + }) => + (super.noSuchMethod( + Invocation.method( + #restoreHistoryWith, + [], + { + #other: other, + #checkSegwitAsWell: checkSegwitAsWell, + #maxUnusedAddressGap: maxUnusedAddressGap, + #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, + }, + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future<_i17.Address> getMyNotificationAddress() => (super.noSuchMethod( + Invocation.method( + #getMyNotificationAddress, + [], + ), + returnValue: _i23.Future<_i17.Address>.value(_FakeAddress_15( + this, + Invocation.method( + #getMyNotificationAddress, + [], + ), + )), + ) as _i23.Future<_i17.Address>); + @override + _i23.Future> lookupKey(String? paymentCodeString) => + (super.noSuchMethod( + Invocation.method( + #lookupKey, + [paymentCodeString], + ), + returnValue: _i23.Future>.value([]), + ) as _i23.Future>); + @override + _i23.Future paymentCodeStringByKey(String? key) => + (super.noSuchMethod( + Invocation.method( + #paymentCodeStringByKey, + [key], + ), + returnValue: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future storeCode(String? paymentCodeString) => + (super.noSuchMethod( + Invocation.method( + #storeCode, + [paymentCodeString], + ), + returnValue: _i23.Future.value(''), + ) as _i23.Future); + @override + void initCoinControlInterface({ + required String? walletId, + required String? walletName, + required _i22.Coin? coin, + required _i7.MainDB? db, + required _i23.Future Function()? getChainHeight, + required _i23.Future Function(_i12.Balance)? refreshedBalanceCallback, + }) => + super.noSuchMethod( + Invocation.method( + #initCoinControlInterface, + [], + { + #walletId: walletId, + #walletName: walletName, + #coin: coin, + #db: db, + #getChainHeight: getChainHeight, + #refreshedBalanceCallback: refreshedBalanceCallback, + }, + ), + returnValueForMissingStub: null, + ); + @override + _i23.Future refreshBalance({bool? notify = false}) => + (super.noSuchMethod( + Invocation.method( + #refreshBalance, + [], + {#notify: notify}, + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); } /// A class which mocks [NodeService]. @@ -1335,41 +2053,41 @@ class MockBitcoinWallet extends _i1.Mock implements _i19.BitcoinWallet { /// See the documentation for Mockito's code generation for more information. class MockNodeService extends _i1.Mock implements _i3.NodeService { @override - _i12.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod( + _i19.SecureStorageInterface get secureStorageInterface => (super.noSuchMethod( Invocation.getter(#secureStorageInterface), - returnValue: _FakeSecureStorageInterface_12( + returnValue: _FakeSecureStorageInterface_17( this, Invocation.getter(#secureStorageInterface), ), - ) as _i12.SecureStorageInterface); + ) as _i19.SecureStorageInterface); @override - List<_i20.NodeModel> get primaryNodes => (super.noSuchMethod( + List<_i32.NodeModel> get primaryNodes => (super.noSuchMethod( Invocation.getter(#primaryNodes), - returnValue: <_i20.NodeModel>[], - ) as List<_i20.NodeModel>); + returnValue: <_i32.NodeModel>[], + ) as List<_i32.NodeModel>); @override - List<_i20.NodeModel> get nodes => (super.noSuchMethod( + List<_i32.NodeModel> get nodes => (super.noSuchMethod( Invocation.getter(#nodes), - returnValue: <_i20.NodeModel>[], - ) as List<_i20.NodeModel>); + returnValue: <_i32.NodeModel>[], + ) as List<_i32.NodeModel>); @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i16.Future updateDefaults() => (super.noSuchMethod( + _i23.Future updateDefaults() => (super.noSuchMethod( Invocation.method( #updateDefaults, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future setPrimaryNodeFor({ - required _i15.Coin? coin, - required _i20.NodeModel? node, + _i23.Future setPrimaryNodeFor({ + required _i22.Coin? coin, + required _i32.NodeModel? node, bool? shouldNotifyListeners = false, }) => (super.noSuchMethod( @@ -1382,44 +2100,44 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { #shouldNotifyListeners: shouldNotifyListeners, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i20.NodeModel? getPrimaryNodeFor({required _i15.Coin? coin}) => + _i32.NodeModel? getPrimaryNodeFor({required _i22.Coin? coin}) => (super.noSuchMethod(Invocation.method( #getPrimaryNodeFor, [], {#coin: coin}, - )) as _i20.NodeModel?); + )) as _i32.NodeModel?); @override - List<_i20.NodeModel> getNodesFor(_i15.Coin? coin) => (super.noSuchMethod( + List<_i32.NodeModel> getNodesFor(_i22.Coin? coin) => (super.noSuchMethod( Invocation.method( #getNodesFor, [coin], ), - returnValue: <_i20.NodeModel>[], - ) as List<_i20.NodeModel>); + returnValue: <_i32.NodeModel>[], + ) as List<_i32.NodeModel>); @override - _i20.NodeModel? getNodeById({required String? id}) => + _i32.NodeModel? getNodeById({required String? id}) => (super.noSuchMethod(Invocation.method( #getNodeById, [], {#id: id}, - )) as _i20.NodeModel?); + )) as _i32.NodeModel?); @override - List<_i20.NodeModel> failoverNodesFor({required _i15.Coin? coin}) => + List<_i32.NodeModel> failoverNodesFor({required _i22.Coin? coin}) => (super.noSuchMethod( Invocation.method( #failoverNodesFor, [], {#coin: coin}, ), - returnValue: <_i20.NodeModel>[], - ) as List<_i20.NodeModel>); + returnValue: <_i32.NodeModel>[], + ) as List<_i32.NodeModel>); @override - _i16.Future add( - _i20.NodeModel? node, + _i23.Future add( + _i32.NodeModel? node, String? password, bool? shouldNotifyListeners, ) => @@ -1432,11 +2150,11 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { shouldNotifyListeners, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future delete( + _i23.Future delete( String? id, bool? shouldNotifyListeners, ) => @@ -1448,11 +2166,11 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { shouldNotifyListeners, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future setEnabledState( + _i23.Future setEnabledState( String? id, bool? enabled, bool? shouldNotifyListeners, @@ -1466,12 +2184,12 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { shouldNotifyListeners, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future edit( - _i20.NodeModel? editedNode, + _i23.Future edit( + _i32.NodeModel? editedNode, String? password, bool? shouldNotifyListeners, ) => @@ -1484,20 +2202,20 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { shouldNotifyListeners, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future updateCommunityNodes() => (super.noSuchMethod( + _i23.Future updateCommunityNodes() => (super.noSuchMethod( Invocation.method( #updateCommunityNodes, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void addListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -1505,7 +2223,7 @@ class MockNodeService extends _i1.Mock implements _i3.NodeService { returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -1548,23 +2266,23 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValueForMissingStub: null, ); @override - _i13.CoinServiceAPI get wallet => (super.noSuchMethod( + _i20.CoinServiceAPI get wallet => (super.noSuchMethod( Invocation.getter(#wallet), - returnValue: _FakeCoinServiceAPI_13( + returnValue: _FakeCoinServiceAPI_18( this, Invocation.getter(#wallet), ), - ) as _i13.CoinServiceAPI); + ) as _i20.CoinServiceAPI); @override bool get hasBackgroundRefreshListener => (super.noSuchMethod( Invocation.getter(#hasBackgroundRefreshListener), returnValue: false, ) as bool); @override - _i15.Coin get coin => (super.noSuchMethod( + _i22.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i15.Coin.bitcoin, - ) as _i15.Coin); + returnValue: _i22.Coin.bitcoin, + ) as _i22.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -1597,91 +2315,42 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValueForMissingStub: null, ); @override - _i16.Future<_i8.FeeObject> get fees => (super.noSuchMethod( + _i23.Future<_i9.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i16.Future<_i8.FeeObject>.value(_FakeFeeObject_7( + returnValue: _i23.Future<_i9.FeeObject>.value(_FakeFeeObject_6( this, Invocation.getter(#fees), )), - ) as _i16.Future<_i8.FeeObject>); + ) as _i23.Future<_i9.FeeObject>); @override - _i16.Future get maxFee => (super.noSuchMethod( + _i23.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future.value(0), + ) as _i23.Future); @override - _i16.Future get currentReceivingAddress => (super.noSuchMethod( + _i23.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future<_i9.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( + _i12.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_9( this, - Invocation.getter(#availableBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i9.Decimal get cachedAvailableBalance => (super.noSuchMethod( - Invocation.getter(#cachedAvailableBalance), - returnValue: _FakeDecimal_6( - this, - Invocation.getter(#cachedAvailableBalance), + Invocation.getter(#balance), ), - ) as _i9.Decimal); + ) as _i12.Balance); @override - _i16.Future<_i9.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i9.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i9.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#totalBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i9.Decimal get cachedTotalBalance => (super.noSuchMethod( - Invocation.getter(#cachedTotalBalance), - returnValue: _FakeDecimal_6( - this, - Invocation.getter(#cachedTotalBalance), - ), - ) as _i9.Decimal); - @override - _i16.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); - @override - _i16.Future<_i8.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i23.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i16.Future<_i8.TransactionData>.value(_FakeTransactionData_8( - this, - Invocation.getter(#transactionData), - )), - ) as _i16.Future<_i8.TransactionData>); + _i23.Future>.value(<_i17.Transaction>[]), + ) as _i23.Future>); @override - _i16.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: - _i16.Future>.value(<_i8.UtxoObject>[]), - ) as _i16.Future>); + _i23.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i23.Future>.value(<_i17.UTXO>[]), + ) as _i23.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -1701,29 +2370,74 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValue: '', ) as String); @override - _i16.Future> get mnemonic => (super.noSuchMethod( + _i23.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); + returnValue: _i23.Future>.value([]), + ) as _i23.Future>); + @override + _i23.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i23.Future.value(), + ) as _i23.Future); @override bool get isConnected => (super.noSuchMethod( Invocation.getter(#isConnected), returnValue: false, ) as bool); @override + int get currentHeight => (super.noSuchMethod( + Invocation.getter(#currentHeight), + returnValue: 0, + ) as int); + @override + bool get hasPaynymSupport => (super.noSuchMethod( + Invocation.getter(#hasPaynymSupport), + returnValue: false, + ) as bool); + @override + bool get hasCoinControlSupport => (super.noSuchMethod( + Invocation.getter(#hasCoinControlSupport), + returnValue: false, + ) as bool); + @override + bool get hasTokenSupport => (super.noSuchMethod( + Invocation.getter(#hasTokenSupport), + returnValue: false, + ) as bool); + @override + bool get hasWhirlpoolSupport => (super.noSuchMethod( + Invocation.getter(#hasWhirlpoolSupport), + returnValue: false, + ) as bool); + @override + int get rescanOnOpenVersion => (super.noSuchMethod( + Invocation.getter(#rescanOnOpenVersion), + returnValue: 0, + ) as int); + @override + bool get hasXPub => (super.noSuchMethod( + Invocation.getter(#hasXPub), + returnValue: false, + ) as bool); + @override + _i23.Future get xpub => (super.noSuchMethod( + Invocation.getter(#xpub), + returnValue: _i23.Future.value(''), + ) as _i23.Future); + @override bool get hasListeners => (super.noSuchMethod( Invocation.getter(#hasListeners), returnValue: false, ) as bool); @override - _i16.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i23.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override void dispose() => super.noSuchMethod( Invocation.method( @@ -1733,9 +2447,9 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValueForMissingStub: null, ); @override - _i16.Future> prepareSend({ + _i23.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i14.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -1744,50 +2458,32 @@ class MockManager extends _i1.Mock implements _i6.Manager { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i23.Future>.value({}), + ) as _i23.Future>); @override - _i16.Future confirmSend({required Map? txData}) => + _i23.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future send({ - required String? toAddress, - required int? amount, - Map? args = const {}, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); - @override - _i16.Future refresh() => (super.noSuchMethod( + _i23.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -1797,34 +2493,35 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValue: false, ) as bool); @override - _i16.Future testNetworkConnection() => (super.noSuchMethod( + _i23.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future initializeNew() => (super.noSuchMethod( + _i23.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future initializeExisting() => (super.noSuchMethod( + _i23.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future recoverFromMnemonic({ + _i23.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -1835,25 +2532,26 @@ class MockManager extends _i1.Mock implements _i6.Manager { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future exitCurrentWallet() => (super.noSuchMethod( + _i23.Future exitCurrentWallet() => (super.noSuchMethod( Invocation.method( #exitCurrentWallet, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future fullRescan( + _i23.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -1865,42 +2563,52 @@ class MockManager extends _i1.Mock implements _i6.Manager { maxNumberOfIndexesToCheck, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future isOwnAddress(String? address) => (super.noSuchMethod( - Invocation.method( - #isOwnAddress, - [address], - ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); - @override - _i16.Future estimateFeeFor( - int? satoshiAmount, + _i23.Future<_i14.Amount> estimateFeeFor( + _i14.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future<_i14.Amount>.value(_FakeAmount_12( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i23.Future<_i14.Amount>); @override - _i16.Future generateNewAddress() => (super.noSuchMethod( + _i23.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - void addListener(_i18.VoidCallback? listener) => super.noSuchMethod( + _i23.Future resetRescanOnOpen() => (super.noSuchMethod( + Invocation.method( + #resetRescanOnOpen, + [], + ), + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); + @override + void addListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #addListener, [listener], @@ -1908,7 +2616,7 @@ class MockManager extends _i1.Mock implements _i6.Manager { returnValueForMissingStub: null, ); @override - void removeListener(_i18.VoidCallback? listener) => super.noSuchMethod( + void removeListener(_i25.VoidCallback? listener) => super.noSuchMethod( Invocation.method( #removeListener, [listener], @@ -1928,7 +2636,7 @@ class MockManager extends _i1.Mock implements _i6.Manager { /// A class which mocks [CoinServiceAPI]. /// /// See the documentation for Mockito's code generation for more information. -class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { +class MockCoinServiceAPI extends _i1.Mock implements _i20.CoinServiceAPI { @override set onIsActiveWalletChanged(void Function(bool)? _onIsActiveWalletChanged) => super.noSuchMethod( @@ -1939,10 +2647,10 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { returnValueForMissingStub: null, ); @override - _i15.Coin get coin => (super.noSuchMethod( + _i22.Coin get coin => (super.noSuchMethod( Invocation.getter(#coin), - returnValue: _i15.Coin.bitcoin, - ) as _i15.Coin); + returnValue: _i22.Coin.bitcoin, + ) as _i22.Coin); @override bool get isRefreshing => (super.noSuchMethod( Invocation.getter(#isRefreshing), @@ -1975,75 +2683,42 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { returnValueForMissingStub: null, ); @override - _i16.Future<_i8.FeeObject> get fees => (super.noSuchMethod( + _i23.Future<_i9.FeeObject> get fees => (super.noSuchMethod( Invocation.getter(#fees), - returnValue: _i16.Future<_i8.FeeObject>.value(_FakeFeeObject_7( + returnValue: _i23.Future<_i9.FeeObject>.value(_FakeFeeObject_6( this, Invocation.getter(#fees), )), - ) as _i16.Future<_i8.FeeObject>); + ) as _i23.Future<_i9.FeeObject>); @override - _i16.Future get maxFee => (super.noSuchMethod( + _i23.Future get maxFee => (super.noSuchMethod( Invocation.getter(#maxFee), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future.value(0), + ) as _i23.Future); @override - _i16.Future get currentReceivingAddress => (super.noSuchMethod( + _i23.Future get currentReceivingAddress => (super.noSuchMethod( Invocation.getter(#currentReceivingAddress), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future<_i9.Decimal> get availableBalance => (super.noSuchMethod( - Invocation.getter(#availableBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( + _i12.Balance get balance => (super.noSuchMethod( + Invocation.getter(#balance), + returnValue: _FakeBalance_9( this, - Invocation.getter(#availableBalance), - )), - ) as _i16.Future<_i9.Decimal>); + Invocation.getter(#balance), + ), + ) as _i12.Balance); @override - _i16.Future<_i9.Decimal> get pendingBalance => (super.noSuchMethod( - Invocation.getter(#pendingBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#pendingBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i9.Decimal> get totalBalance => (super.noSuchMethod( - Invocation.getter(#totalBalance), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#totalBalance), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future<_i9.Decimal> get balanceMinusMaxFee => (super.noSuchMethod( - Invocation.getter(#balanceMinusMaxFee), - returnValue: _i16.Future<_i9.Decimal>.value(_FakeDecimal_6( - this, - Invocation.getter(#balanceMinusMaxFee), - )), - ) as _i16.Future<_i9.Decimal>); - @override - _i16.Future> get allOwnAddresses => (super.noSuchMethod( - Invocation.getter(#allOwnAddresses), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); - @override - _i16.Future<_i8.TransactionData> get transactionData => (super.noSuchMethod( - Invocation.getter(#transactionData), + _i23.Future> get transactions => (super.noSuchMethod( + Invocation.getter(#transactions), returnValue: - _i16.Future<_i8.TransactionData>.value(_FakeTransactionData_8( - this, - Invocation.getter(#transactionData), - )), - ) as _i16.Future<_i8.TransactionData>); + _i23.Future>.value(<_i17.Transaction>[]), + ) as _i23.Future>); @override - _i16.Future> get unspentOutputs => (super.noSuchMethod( - Invocation.getter(#unspentOutputs), - returnValue: - _i16.Future>.value(<_i8.UtxoObject>[]), - ) as _i16.Future>); + _i23.Future> get utxos => (super.noSuchMethod( + Invocation.getter(#utxos), + returnValue: _i23.Future>.value(<_i17.UTXO>[]), + ) as _i23.Future>); @override set walletName(String? newName) => super.noSuchMethod( Invocation.setter( @@ -2063,10 +2738,20 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { returnValue: '', ) as String); @override - _i16.Future> get mnemonic => (super.noSuchMethod( + _i23.Future> get mnemonic => (super.noSuchMethod( Invocation.getter(#mnemonic), - returnValue: _i16.Future>.value([]), - ) as _i16.Future>); + returnValue: _i23.Future>.value([]), + ) as _i23.Future>); + @override + _i23.Future get mnemonicString => (super.noSuchMethod( + Invocation.getter(#mnemonicString), + returnValue: _i23.Future.value(), + ) as _i23.Future); + @override + _i23.Future get mnemonicPassphrase => (super.noSuchMethod( + Invocation.getter(#mnemonicPassphrase), + returnValue: _i23.Future.value(), + ) as _i23.Future); @override bool get hasCalledExit => (super.noSuchMethod( Invocation.getter(#hasCalledExit), @@ -2078,9 +2763,14 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { returnValue: false, ) as bool); @override - _i16.Future> prepareSend({ + int get storedChainHeight => (super.noSuchMethod( + Invocation.getter(#storedChainHeight), + returnValue: 0, + ) as int); + @override + _i23.Future> prepareSend({ required String? address, - required int? satoshiAmount, + required _i14.Amount? amount, Map? args, }) => (super.noSuchMethod( @@ -2089,59 +2779,41 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { [], { #address: address, - #satoshiAmount: satoshiAmount, + #amount: amount, #args: args, }, ), returnValue: - _i16.Future>.value({}), - ) as _i16.Future>); + _i23.Future>.value({}), + ) as _i23.Future>); @override - _i16.Future confirmSend({required Map? txData}) => + _i23.Future confirmSend({required Map? txData}) => (super.noSuchMethod( Invocation.method( #confirmSend, [], {#txData: txData}, ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); + returnValue: _i23.Future.value(''), + ) as _i23.Future); @override - _i16.Future send({ - required String? toAddress, - required int? amount, - Map? args, - }) => - (super.noSuchMethod( - Invocation.method( - #send, - [], - { - #toAddress: toAddress, - #amount: amount, - #args: args, - }, - ), - returnValue: _i16.Future.value(''), - ) as _i16.Future); - @override - _i16.Future refresh() => (super.noSuchMethod( + _i23.Future refresh() => (super.noSuchMethod( Invocation.method( #refresh, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( + _i23.Future updateNode(bool? shouldRefresh) => (super.noSuchMethod( Invocation.method( #updateNode, [shouldRefresh], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override bool validateAddress(String? address) => (super.noSuchMethod( Invocation.method( @@ -2151,16 +2823,17 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { returnValue: false, ) as bool); @override - _i16.Future testNetworkConnection() => (super.noSuchMethod( + _i23.Future testNetworkConnection() => (super.noSuchMethod( Invocation.method( #testNetworkConnection, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future recoverFromMnemonic({ + _i23.Future recoverFromMnemonic({ required String? mnemonic, + String? mnemonicPassphrase, required int? maxUnusedAddressGap, required int? maxNumberOfIndexesToCheck, required int? height, @@ -2171,43 +2844,44 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { [], { #mnemonic: mnemonic, + #mnemonicPassphrase: mnemonicPassphrase, #maxUnusedAddressGap: maxUnusedAddressGap, #maxNumberOfIndexesToCheck: maxNumberOfIndexesToCheck, #height: height, }, ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future initializeNew() => (super.noSuchMethod( + _i23.Future initializeNew() => (super.noSuchMethod( Invocation.method( #initializeNew, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future initializeExisting() => (super.noSuchMethod( + _i23.Future initializeExisting() => (super.noSuchMethod( Invocation.method( #initializeExisting, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future exit() => (super.noSuchMethod( + _i23.Future exit() => (super.noSuchMethod( Invocation.method( #exit, [], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future fullRescan( + _i23.Future fullRescan( int? maxUnusedAddressGap, int? maxNumberOfIndexesToCheck, ) => @@ -2219,40 +2893,49 @@ class MockCoinServiceAPI extends _i1.Mock implements _i13.CoinServiceAPI { maxNumberOfIndexesToCheck, ], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); @override - _i16.Future estimateFeeFor( - int? satoshiAmount, + _i23.Future<_i14.Amount> estimateFeeFor( + _i14.Amount? amount, int? feeRate, ) => (super.noSuchMethod( Invocation.method( #estimateFeeFor, [ - satoshiAmount, + amount, feeRate, ], ), - returnValue: _i16.Future.value(0), - ) as _i16.Future); + returnValue: _i23.Future<_i14.Amount>.value(_FakeAmount_12( + this, + Invocation.method( + #estimateFeeFor, + [ + amount, + feeRate, + ], + ), + )), + ) as _i23.Future<_i14.Amount>); @override - _i16.Future generateNewAddress() => (super.noSuchMethod( + _i23.Future generateNewAddress() => (super.noSuchMethod( Invocation.method( #generateNewAddress, [], ), - returnValue: _i16.Future.value(false), - ) as _i16.Future); + returnValue: _i23.Future.value(false), + ) as _i23.Future); @override - _i16.Future updateSentCachedTxData(Map? txData) => + _i23.Future updateSentCachedTxData(Map? txData) => (super.noSuchMethod( Invocation.method( #updateSentCachedTxData, [txData], ), - returnValue: _i16.Future.value(), - returnValueForMissingStub: _i16.Future.value(), - ) as _i16.Future); + returnValue: _i23.Future.value(), + returnValueForMissingStub: _i23.Future.value(), + ) as _i23.Future); } diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 98655fe05..53dc112e6 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,10 +7,12 @@ #include "generated_plugin_registrant.h" #include +#include #include #include #include #include +#include #include #include #include @@ -18,6 +20,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { ConnectivityPlusWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); + DesktopDropPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("DesktopDropPlugin")); FlutterLibepiccashPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterLibepiccashPluginCApi")); FlutterSecureStorageWindowsPluginRegisterWithRegistrar( @@ -26,6 +30,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("IsarFlutterLibsPlugin")); PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + SharePlusWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); StackWalletBackupPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("StackWalletBackupPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 4426d9497..fb11dd376 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,10 +4,12 @@ list(APPEND FLUTTER_PLUGIN_LIST connectivity_plus_windows + desktop_drop flutter_libepiccash flutter_secure_storage_windows isar_flutter_libs permission_handler_windows + share_plus stack_wallet_backup url_launcher_windows window_size