mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-04-16 11:11:58 +00:00
Compare commits
85 commits
Author | SHA1 | Date | |
---|---|---|---|
|
9f0f94b29b | ||
|
3266e4e055 | ||
|
9e988b8ba5 | ||
|
dbf68f2a68 | ||
|
1ce4bee68c | ||
|
d6abd7d658 | ||
|
3fb18bf2db | ||
|
b548386097 | ||
|
800de8873d | ||
|
cd003ba412 | ||
|
7d047e8d47 | ||
|
31fe9a538b | ||
|
d87af969d6 | ||
|
25c6c7590b | ||
|
8a63c42582 | ||
|
7b603cd7f9 | ||
|
4197ff40f4 | ||
|
d6d4df7822 | ||
|
6cfe9e9c0f | ||
|
f38efd35e9 | ||
|
d8ec93fb30 | ||
|
537e44f1f8 | ||
|
2431d5f300 | ||
|
9a2589b4c3 | ||
|
7511ce2ac3 | ||
|
973d8b3eee | ||
|
6dd7ef183a | ||
|
216719ba56 | ||
|
91de7ddbc0 | ||
|
0ce0350039 | ||
|
db7422a100 | ||
|
9c9a9f8d3e | ||
|
40b0f49f20 | ||
|
3566d75d58 | ||
|
31e785c23f | ||
|
6c42da2add | ||
|
f41a85bd6e | ||
|
ae9a844ed8 | ||
|
4594801cf3 | ||
|
f15d051108 | ||
|
5496d2de96 | ||
|
b84c3ab2d3 | ||
|
6494bec72b | ||
|
48d46cd18c | ||
|
f2a654e1e5 | ||
|
1d22eed7c4 | ||
|
44f9886ffd | ||
|
3177fadea6 | ||
|
5452598064 | ||
|
98d77621b2 | ||
|
e95d5a10b6 | ||
|
6ff539e71b | ||
|
3da57bc150 | ||
|
1f0ee995b9 | ||
|
53eb6ac8d1 | ||
|
3d2d0e4e73 | ||
|
0080d25436 | ||
|
9cde0a1f65 | ||
|
a03b0ec2aa | ||
|
016a53f8eb | ||
|
809a5a38e7 | ||
|
d3d6709763 | ||
|
f0070b5aa8 | ||
|
11d1ceb40f | ||
|
699990883c | ||
|
e6b0733044 | ||
|
217bf9e301 | ||
|
07bf622fcd | ||
|
bb2e8580e2 | ||
|
b1eeddced1 | ||
|
c913b05fc9 | ||
|
16b9254761 | ||
|
4109e7ed9f | ||
|
e2f3a1ad79 | ||
|
b4610fb5b0 | ||
|
57a50a96cd | ||
|
f7b367a3aa | ||
|
9279295484 | ||
|
51c45d4332 | ||
|
99ea691bef | ||
|
c47bf0b21c | ||
|
233029ca21 | ||
|
9c37f99e9e | ||
|
cec219f3f9 | ||
|
f5489feae7 |
222 changed files with 9725 additions and 4798 deletions
.gitignore.gitmodules
android/app/src/main/jniLibs
asset_sources/svg
assets/gif
crypto_plugins
docs
ios
lib
db
electrumx_rpc
exceptions/wallet
main.dartmodels
pages
add_wallet_views
add_wallet_view
name_your_wallet_view
new_wallet_options
new_wallet_recovery_phrase_warning_view
restore_wallet_view
verify_recovery_phrase_view
address_book_views/subviews
buy_view
churning
churn_error_dialog.dartchurning_progress_view.dartchurning_rounds_selection_sheet.dartchurning_view.dart
coin_control
exchange_view
paynym
pinpad_views
receive_view
send_view
settings_views
global_settings_view
sub_widgets
wallet_settings_view
special
wallet_view
wallets_view/sub_widgets
pages_desktop_specific
churning
coin_control
my_stack_view
22
.gitignore
vendored
22
.gitignore
vendored
|
@ -101,21 +101,7 @@ pubspec.yaml
|
|||
# FVM Version Cache
|
||||
.fvm/
|
||||
|
||||
android/app/src/main/jniLibs/arm64-v8a/libwownero_wallet2_api_c.so
|
||||
android/app/src/main/jniLibs/arm64-v8a/libmonero_wallet2_api_c.so
|
||||
android/app/src/main/jniLibs/armeabi-v7a/libmonero_wallet2_api_c.so
|
||||
android/app/src/main/jniLibs/armeabi-v7a/libwownero_wallet2_api_c.so
|
||||
android/app/src/main/jniLibs/x86_64/libmonero_wallet2_api_c.so
|
||||
android/app/src/main/jniLibs/x86_64/libwownero_wallet2_api_c.so
|
||||
macos/monero_wallet2_api_c.dylib
|
||||
macos/wownero_wallet2_api_c.dylib
|
||||
/macos/monero_libwallet2_api_c.dylib
|
||||
/macos/wownero_libwallet2_api_c.dylib
|
||||
/ios/monero_libwallet2_api_c.dylib
|
||||
/ios/wownero_libwallet2_api_c.dylib
|
||||
/android/app/src/main/jniLibs/arm64-v8a/libmonero_libwallet2_api_c.so
|
||||
/android/app/src/main/jniLibs/armeabi-v7a/libmonero_libwallet2_api_c.so
|
||||
/android/app/src/main/jniLibs/x86_64/libmonero_libwallet2_api_c.so
|
||||
/android/app/src/main/jniLibs/arm64-v8a/libwownero_libwallet2_api_c.so
|
||||
/android/app/src/main/jniLibs/armeabi-v7a/libwownero_libwallet2_api_c.so
|
||||
/android/app/src/main/jniLibs/x86_64/libwownero_libwallet2_api_c.so
|
||||
scripts/linux/build/libsecret/subprojects/gi-docgen/.meson-subproject-wrap-hash.txt
|
||||
|
||||
crypto_plugins/cs_monero/built_outputs
|
||||
crypto_plugins/cs_monero/build
|
||||
|
|
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -1,9 +1,6 @@
|
|||
[submodule "crypto_plugins/flutter_libepiccash"]
|
||||
path = crypto_plugins/flutter_libepiccash
|
||||
url = https://github.com/cypherstack/flutter_libepiccash.git
|
||||
[submodule "crypto_plugins/flutter_libmonero"]
|
||||
path = crypto_plugins/flutter_libmonero
|
||||
url = https://github.com/cypherstack/flutter_libmonero.git
|
||||
[submodule "crypto_plugins/flutter_liblelantus"]
|
||||
path = crypto_plugins/flutter_liblelantus
|
||||
url = https://github.com/cypherstack/flutter_liblelantus.git
|
||||
|
|
3
asset_sources/svg/campfire/churn.svg
Normal file
3
asset_sources/svg/campfire/churn.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.77444 13.4823C9.60687 13.4092 9.43072 13.3749 9.25027 13.3749H3.75081C2.99076 13.3749 2.37594 13.9897 2.37594 14.7497C2.37594 15.5098 2.99076 16.1246 3.75081 16.1246H5.93126L1.40279 20.6531C0.865736 21.1901 0.865736 22.0602 1.40279 22.5972C1.93985 23.1343 2.80988 23.1343 3.34694 22.5972L7.8754 18.0709V20.2492C7.8754 21.0092 8.49023 21.6241 9.25027 21.6241C10.0103 21.6241 10.6251 21.0092 10.6251 20.2492V14.7497C10.6251 14.5708 10.5887 14.3926 10.5192 14.2247C10.3802 13.8861 10.1139 13.6198 9.77444 13.4823ZM14.2256 10.5177C14.3931 10.5908 14.5693 10.6251 14.7497 10.6251H20.2492C21.0092 10.6251 21.6241 10.0103 21.6241 9.25027C21.6241 8.49023 21.0092 7.8754 20.2492 7.8754H18.0687L22.5972 3.34694C23.1343 2.80988 23.1343 1.93985 22.5972 1.40279C22.0606 0.866166 21.1905 0.865306 20.6531 1.40279L16.1246 5.93341V3.75081C16.1246 2.99076 15.5098 2.37594 14.7497 2.37594C13.9897 2.37594 13.3749 2.99076 13.3749 3.75081V9.25027C13.3749 9.42917 13.4113 9.60739 13.4807 9.7753C13.6198 10.1139 13.8861 10.3802 14.2256 10.5177ZM9.25027 2.37594C8.4898 2.37594 7.8754 2.99076 7.8754 3.75081V5.93126L3.34823 1.40387C2.81117 0.86681 1.94114 0.86681 1.40408 1.40387C0.867025 1.94092 0.867025 2.81096 1.40408 3.34801L5.93341 7.8754H3.75081C2.99076 7.8754 2.37594 8.4898 2.37594 9.25027C2.37594 10.0107 2.99076 10.6251 3.75081 10.6251H9.25027C9.42917 10.6251 9.60739 10.5887 9.7753 10.5192C10.1139 10.3802 10.3802 10.1139 10.5177 9.77444C10.5908 9.60687 10.6251 9.43072 10.6251 9.25027V3.75081C10.6251 2.99076 10.0107 2.37594 9.25027 2.37594ZM18.0709 16.1246H20.2492C21.0092 16.1246 21.6241 15.5098 21.6241 14.7497C21.6241 13.9897 21.0092 13.3749 20.2492 13.3749H14.7497C14.5708 13.3749 14.3926 13.4113 14.2247 13.4806C13.8879 13.6199 13.6198 13.8879 13.4806 14.2247C13.4092 14.3931 13.3749 14.5693 13.3749 14.7497V20.2492C13.3749 21.0092 13.9897 21.6241 14.7497 21.6241C15.5098 21.6241 16.1246 21.0092 16.1246 20.2492V18.0687L20.6531 22.5972C21.1901 23.1343 22.0602 23.1343 22.5972 22.5972C23.1338 22.0606 23.1347 21.1905 22.5972 20.6531L18.0709 16.1246Z" fill="black"/>
|
||||
</svg>
|
After (image error) Size: 2.1 KiB |
3
asset_sources/svg/stack_duo/churn.svg
Normal file
3
asset_sources/svg/stack_duo/churn.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.77444 13.4823C9.60687 13.4092 9.43072 13.3749 9.25027 13.3749H3.75081C2.99076 13.3749 2.37594 13.9897 2.37594 14.7497C2.37594 15.5098 2.99076 16.1246 3.75081 16.1246H5.93126L1.40279 20.6531C0.865736 21.1901 0.865736 22.0602 1.40279 22.5972C1.93985 23.1343 2.80988 23.1343 3.34694 22.5972L7.8754 18.0709V20.2492C7.8754 21.0092 8.49023 21.6241 9.25027 21.6241C10.0103 21.6241 10.6251 21.0092 10.6251 20.2492V14.7497C10.6251 14.5708 10.5887 14.3926 10.5192 14.2247C10.3802 13.8861 10.1139 13.6198 9.77444 13.4823ZM14.2256 10.5177C14.3931 10.5908 14.5693 10.6251 14.7497 10.6251H20.2492C21.0092 10.6251 21.6241 10.0103 21.6241 9.25027C21.6241 8.49023 21.0092 7.8754 20.2492 7.8754H18.0687L22.5972 3.34694C23.1343 2.80988 23.1343 1.93985 22.5972 1.40279C22.0606 0.866166 21.1905 0.865306 20.6531 1.40279L16.1246 5.93341V3.75081C16.1246 2.99076 15.5098 2.37594 14.7497 2.37594C13.9897 2.37594 13.3749 2.99076 13.3749 3.75081V9.25027C13.3749 9.42917 13.4113 9.60739 13.4807 9.7753C13.6198 10.1139 13.8861 10.3802 14.2256 10.5177ZM9.25027 2.37594C8.4898 2.37594 7.8754 2.99076 7.8754 3.75081V5.93126L3.34823 1.40387C2.81117 0.86681 1.94114 0.86681 1.40408 1.40387C0.867025 1.94092 0.867025 2.81096 1.40408 3.34801L5.93341 7.8754H3.75081C2.99076 7.8754 2.37594 8.4898 2.37594 9.25027C2.37594 10.0107 2.99076 10.6251 3.75081 10.6251H9.25027C9.42917 10.6251 9.60739 10.5887 9.7753 10.5192C10.1139 10.3802 10.3802 10.1139 10.5177 9.77444C10.5908 9.60687 10.6251 9.43072 10.6251 9.25027V3.75081C10.6251 2.99076 10.0107 2.37594 9.25027 2.37594ZM18.0709 16.1246H20.2492C21.0092 16.1246 21.6241 15.5098 21.6241 14.7497C21.6241 13.9897 21.0092 13.3749 20.2492 13.3749H14.7497C14.5708 13.3749 14.3926 13.4113 14.2247 13.4806C13.8879 13.6199 13.6198 13.8879 13.4806 14.2247C13.4092 14.3931 13.3749 14.5693 13.3749 14.7497V20.2492C13.3749 21.0092 13.9897 21.6241 14.7497 21.6241C15.5098 21.6241 16.1246 21.0092 16.1246 20.2492V18.0687L20.6531 22.5972C21.1901 23.1343 22.0602 23.1343 22.5972 22.5972C23.1338 22.0606 23.1347 21.1905 22.5972 20.6531L18.0709 16.1246Z" fill="black"/>
|
||||
</svg>
|
After (image error) Size: 2.1 KiB |
3
asset_sources/svg/stack_wallet/churn.svg
Normal file
3
asset_sources/svg/stack_wallet/churn.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9.77444 13.4823C9.60687 13.4092 9.43072 13.3749 9.25027 13.3749H3.75081C2.99076 13.3749 2.37594 13.9897 2.37594 14.7497C2.37594 15.5098 2.99076 16.1246 3.75081 16.1246H5.93126L1.40279 20.6531C0.865736 21.1901 0.865736 22.0602 1.40279 22.5972C1.93985 23.1343 2.80988 23.1343 3.34694 22.5972L7.8754 18.0709V20.2492C7.8754 21.0092 8.49023 21.6241 9.25027 21.6241C10.0103 21.6241 10.6251 21.0092 10.6251 20.2492V14.7497C10.6251 14.5708 10.5887 14.3926 10.5192 14.2247C10.3802 13.8861 10.1139 13.6198 9.77444 13.4823ZM14.2256 10.5177C14.3931 10.5908 14.5693 10.6251 14.7497 10.6251H20.2492C21.0092 10.6251 21.6241 10.0103 21.6241 9.25027C21.6241 8.49023 21.0092 7.8754 20.2492 7.8754H18.0687L22.5972 3.34694C23.1343 2.80988 23.1343 1.93985 22.5972 1.40279C22.0606 0.866166 21.1905 0.865306 20.6531 1.40279L16.1246 5.93341V3.75081C16.1246 2.99076 15.5098 2.37594 14.7497 2.37594C13.9897 2.37594 13.3749 2.99076 13.3749 3.75081V9.25027C13.3749 9.42917 13.4113 9.60739 13.4807 9.7753C13.6198 10.1139 13.8861 10.3802 14.2256 10.5177ZM9.25027 2.37594C8.4898 2.37594 7.8754 2.99076 7.8754 3.75081V5.93126L3.34823 1.40387C2.81117 0.86681 1.94114 0.86681 1.40408 1.40387C0.867025 1.94092 0.867025 2.81096 1.40408 3.34801L5.93341 7.8754H3.75081C2.99076 7.8754 2.37594 8.4898 2.37594 9.25027C2.37594 10.0107 2.99076 10.6251 3.75081 10.6251H9.25027C9.42917 10.6251 9.60739 10.5887 9.7753 10.5192C10.1139 10.3802 10.3802 10.1139 10.5177 9.77444C10.5908 9.60687 10.6251 9.43072 10.6251 9.25027V3.75081C10.6251 2.99076 10.0107 2.37594 9.25027 2.37594ZM18.0709 16.1246H20.2492C21.0092 16.1246 21.6241 15.5098 21.6241 14.7497C21.6241 13.9897 21.0092 13.3749 20.2492 13.3749H14.7497C14.5708 13.3749 14.3926 13.4113 14.2247 13.4806C13.8879 13.6199 13.6198 13.8879 13.4806 14.2247C13.4092 14.3931 13.3749 14.5693 13.3749 14.7497V20.2492C13.3749 21.0092 13.9897 21.6241 14.7497 21.6241C15.5098 21.6241 16.1246 21.0092 16.1246 20.2492V18.0687L20.6531 22.5972C21.1901 23.1343 22.0602 23.1343 22.5972 22.5972C23.1338 22.0606 23.1347 21.1905 22.5972 20.6531L18.0709 16.1246Z" fill="black"/>
|
||||
</svg>
|
After (image error) Size: 2.1 KiB |
BIN
assets/gif/monero-chan-dance.gif
Normal file
BIN
assets/gif/monero-chan-dance.gif
Normal file
Binary file not shown.
After ![]() (image error) Size: 2.2 MiB |
|
@ -1 +1 @@
|
|||
Subproject commit 1d48c0a8aa394324e7c39267e5654038834aff95
|
||||
Subproject commit 0bb1b1ced6e0d3c66e383698f89825754c692986
|
|
@ -1 +1 @@
|
|||
Subproject commit 8e9e20e2f90387dcc6e23833d186a84ad3ac372a
|
||||
Subproject commit 5b08645a5b5d30955f4bde2a624ff89ef516e452
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 66369ef5b432e4d58e76b6cc4f91a5e24eb6b1ea
|
|
@ -53,7 +53,26 @@ sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1 libbz2-
|
|||
### Build dependencies
|
||||
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 g++ gcc gperf libopencv-dev
|
||||
sudo apt-get install libssl-dev curl unzip automake build-essential file pkg-config git python3 libtool libtinfo6 cmake libgit2-dev clang libncurses5-dev libncursesw5-dev zlib1g-dev llvm g++ gcc gperf libopencv-dev python3-typogrify xsltproc valac gobject-introspection meson
|
||||
```
|
||||
|
||||
For Ubuntu 20.04,
|
||||
```
|
||||
sudo apt-get install vapigen
|
||||
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
|
||||
```
|
||||
|
||||
For Ubuntu 24.04,
|
||||
```
|
||||
sudo apt install pipx libgcrypt20-dev libglib2.0-dev libsecret-1-dev
|
||||
pipx install 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
|
||||
```
|
||||
|
||||
Install `libtinfo5` (required by [monero_c](https://github.com/MrCyjaneK/monero_c), should be dropped in the future):
|
||||
```
|
||||
wget http://mirrors.kernel.org/ubuntu/pool/universe/n/ncurses/libtinfo5_6.3-2ubuntu0.1_amd64.deb -O libtinfo5.deb \
|
||||
&& apt install ./libtinfo5.deb \
|
||||
&& rm libtinfo5.deb
|
||||
```
|
||||
|
||||
Install [Rust](https://www.rust-lang.org/tools/install) via [rustup.rs](https://rustup.rs), the required Rust toolchains, and `cargo-ndk 2.12.7` with command:
|
||||
|
|
1
ios/MoneroWallet.framework/.gitignore
vendored
1
ios/MoneroWallet.framework/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
MoneroWallet
|
|
@ -1,57 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildMachineOSBuild</key>
|
||||
<string>23E224</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>MoneroWallet</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.cypherstack.MoneroWallet</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>MoneroWallet</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>???</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>iPhoneOS</string>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>DTCompiler</key>
|
||||
<string>com.apple.compilers.llvm.clang.1_0</string>
|
||||
<key>DTPlatformBuild</key>
|
||||
<string>21E210</string>
|
||||
<key>DTPlatformName</key>
|
||||
<string>iphoneos</string>
|
||||
<key>DTPlatformVersion</key>
|
||||
<string>17.4</string>
|
||||
<key>DTSDKBuild</key>
|
||||
<string>21E210</string>
|
||||
<key>DTSDKName</key>
|
||||
<string>iphoneos17.4</string>
|
||||
<key>DTXcode</key>
|
||||
<string>1530</string>
|
||||
<key>DTXcodeBuild</key>
|
||||
<string>15E204a</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>16.0</string>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
|
@ -9,6 +9,8 @@ PODS:
|
|||
- connectivity_plus (0.0.1):
|
||||
- Flutter
|
||||
- ReachabilitySwift
|
||||
- cs_monero_flutter_libs (0.0.1):
|
||||
- Flutter
|
||||
- device_info_plus (0.0.1):
|
||||
- Flutter
|
||||
- devicelocale (0.0.1):
|
||||
|
@ -50,8 +52,6 @@ PODS:
|
|||
- Flutter (1.0.0)
|
||||
- flutter_libepiccash (0.0.1):
|
||||
- Flutter
|
||||
- flutter_libmonero (0.0.1):
|
||||
- Flutter
|
||||
- flutter_libsparkmobile (0.0.1):
|
||||
- Flutter
|
||||
- flutter_local_notifications (0.0.1):
|
||||
|
@ -67,15 +67,16 @@ PODS:
|
|||
- Flutter
|
||||
- lelantus (0.0.1):
|
||||
- Flutter
|
||||
- local_auth (0.0.1):
|
||||
- local_auth_darwin (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- MTBBarcodeScanner (5.0.11)
|
||||
- package_info_plus (0.4.5):
|
||||
- Flutter
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- permission_handler_apple (9.1.1):
|
||||
- permission_handler_apple (9.3.0):
|
||||
- Flutter
|
||||
- ReachabilitySwift (5.0.0)
|
||||
- SDWebImage (5.13.2):
|
||||
|
@ -86,6 +87,8 @@ PODS:
|
|||
- "sqlite3 (3.46.0+1)":
|
||||
- "sqlite3/common (= 3.46.0+1)"
|
||||
- "sqlite3/common (3.46.0+1)"
|
||||
- "sqlite3/dbstatvtab (3.46.0+1)":
|
||||
- sqlite3/common
|
||||
- "sqlite3/fts5 (3.46.0+1)":
|
||||
- sqlite3/common
|
||||
- "sqlite3/perf-threadsafe (3.46.0+1)":
|
||||
|
@ -94,7 +97,8 @@ PODS:
|
|||
- sqlite3/common
|
||||
- sqlite3_flutter_libs (0.0.1):
|
||||
- Flutter
|
||||
- sqlite3 (~> 3.46.0)
|
||||
- "sqlite3 (~> 3.46.0+1)"
|
||||
- sqlite3/dbstatvtab
|
||||
- sqlite3/fts5
|
||||
- sqlite3/perf-threadsafe
|
||||
- sqlite3/rtree
|
||||
|
@ -106,19 +110,19 @@ PODS:
|
|||
- Flutter
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
- wakelock (0.0.1):
|
||||
- wakelock_plus (0.0.1):
|
||||
- Flutter
|
||||
|
||||
DEPENDENCIES:
|
||||
- barcode_scan2 (from `.symlinks/plugins/barcode_scan2/ios`)
|
||||
- coinlib_flutter (from `.symlinks/plugins/coinlib_flutter/darwin`)
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- cs_monero_flutter_libs (from `.symlinks/plugins/cs_monero_flutter_libs/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`)
|
||||
- flutter_libepiccash (from `.symlinks/plugins/flutter_libepiccash/ios`)
|
||||
- flutter_libmonero (from `.symlinks/plugins/flutter_libmonero/ios`)
|
||||
- flutter_libsparkmobile (from `.symlinks/plugins/flutter_libsparkmobile/ios`)
|
||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||
|
@ -127,7 +131,7 @@ DEPENDENCIES:
|
|||
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
||||
- isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`)
|
||||
- lelantus (from `.symlinks/plugins/lelantus/ios`)
|
||||
- local_auth (from `.symlinks/plugins/local_auth/ios`)
|
||||
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||
|
@ -136,7 +140,7 @@ DEPENDENCIES:
|
|||
- stack_wallet_backup (from `.symlinks/plugins/stack_wallet_backup/ios`)
|
||||
- tor_ffi_plugin (from `.symlinks/plugins/tor_ffi_plugin/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
- wakelock (from `.symlinks/plugins/wakelock/ios`)
|
||||
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
||||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
|
@ -156,6 +160,8 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/coinlib_flutter/darwin"
|
||||
connectivity_plus:
|
||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||
cs_monero_flutter_libs:
|
||||
:path: ".symlinks/plugins/cs_monero_flutter_libs/ios"
|
||||
device_info_plus:
|
||||
:path: ".symlinks/plugins/device_info_plus/ios"
|
||||
devicelocale:
|
||||
|
@ -166,8 +172,6 @@ EXTERNAL SOURCES:
|
|||
:path: Flutter
|
||||
flutter_libepiccash:
|
||||
:path: ".symlinks/plugins/flutter_libepiccash/ios"
|
||||
flutter_libmonero:
|
||||
:path: ".symlinks/plugins/flutter_libmonero/ios"
|
||||
flutter_libsparkmobile:
|
||||
:path: ".symlinks/plugins/flutter_libsparkmobile/ios"
|
||||
flutter_local_notifications:
|
||||
|
@ -184,8 +188,8 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/isar_flutter_libs/ios"
|
||||
lelantus:
|
||||
:path: ".symlinks/plugins/lelantus/ios"
|
||||
local_auth:
|
||||
:path: ".symlinks/plugins/local_auth/ios"
|
||||
local_auth_darwin:
|
||||
:path: ".symlinks/plugins/local_auth_darwin/darwin"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider_foundation:
|
||||
|
@ -202,45 +206,45 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/tor_ffi_plugin/ios"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
wakelock:
|
||||
:path: ".symlinks/plugins/wakelock/ios"
|
||||
wakelock_plus:
|
||||
:path: ".symlinks/plugins/wakelock_plus/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
barcode_scan2: 0af2bb63c81b4565aab6cd78278e4c0fa136dbb0
|
||||
coinlib_flutter: 9275e8255ef67d3da33beb6e117d09ced4f46eb5
|
||||
connectivity_plus: 07c49e96d7fc92bc9920617b83238c4d178b446a
|
||||
cs_monero_flutter_libs: 43cda3474c2bc907f2b2b5bb26fd89cb864fcfc6
|
||||
device_info_plus: 97af1d7e84681a90d0693e63169a5d50e0839a0d
|
||||
devicelocale: b22617f40038496deffba44747101255cee005b0
|
||||
devicelocale: 35ba84dc7f45f527c3001535d8c8d104edd5d926
|
||||
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
|
||||
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
|
||||
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
flutter_libepiccash: 36241aa7d3126f6521529985ccb3dc5eaf7bb317
|
||||
flutter_libmonero: da68a616b73dd0374a8419c684fa6b6df2c44ffe
|
||||
flutter_libsparkmobile: 6373955cc3327a926d17059e7405dde2fb12f99f
|
||||
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
|
||||
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
|
||||
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
|
||||
frostdart: 4c72b69ccac2f13ede744107db046a125acce597
|
||||
integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
|
||||
isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073
|
||||
isar_flutter_libs: fdf730ca925d05687f36d7f1d355e482529ed097
|
||||
lelantus: 417f0221260013dfc052cae9cf4b741b6479edba
|
||||
local_auth: 1740f55d7af0a2e2a8684ce225fe79d8931e808c
|
||||
local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3
|
||||
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
|
||||
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
|
||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||
permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6
|
||||
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866
|
||||
share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028
|
||||
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
|
||||
sqlite3: 292c3e1bfe89f64e51ea7fc7dab9182a017c8630
|
||||
sqlite3_flutter_libs: 0d611efdf6d1c9297d5ab03dab21b75aeebdae31
|
||||
sqlite3_flutter_libs: c00457ebd31e59fa6bb830380ddba24d44fbcd3b
|
||||
stack_wallet_backup: 5b8563aba5d8ffbf2ce1944331ff7294a0ec7c03
|
||||
SwiftProtobuf: 6ef3f0e422ef90d6605ca20b21a94f6c1324d6b3
|
||||
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
|
||||
tor_ffi_plugin: d80e291b649379c8176e1be739e49be007d4ef93
|
||||
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
||||
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
|
||||
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
||||
wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1
|
||||
|
||||
PODFILE CHECKSUM: 57c8aed26fba39d3ec9424816221f294a07c58eb
|
||||
|
||||
|
|
1
ios/WowneroWallet.framework/.gitignore
vendored
1
ios/WowneroWallet.framework/.gitignore
vendored
|
@ -1 +0,0 @@
|
|||
WowneroWallet
|
|
@ -1,57 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>BuildMachineOSBuild</key>
|
||||
<string>23E224</string>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>WowneroWallet</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.cypherstack.WowneroWallet</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>WowneroWallet</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>???</string>
|
||||
<key>CFBundleSupportedPlatforms</key>
|
||||
<array>
|
||||
<string>iPhoneOS</string>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>DTCompiler</key>
|
||||
<string>com.apple.compilers.llvm.clang.1_0</string>
|
||||
<key>DTPlatformBuild</key>
|
||||
<string>21E210</string>
|
||||
<key>DTPlatformName</key>
|
||||
<string>iphoneos</string>
|
||||
<key>DTPlatformVersion</key>
|
||||
<string>17.4</string>
|
||||
<key>DTSDKBuild</key>
|
||||
<string>21E210</string>
|
||||
<key>DTSDKName</key>
|
||||
<string>iphoneos17.4</string>
|
||||
<key>DTXcode</key>
|
||||
<string>1530</string>
|
||||
<key>DTXcodeBuild</key>
|
||||
<string>15E204a</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>16.0</string>
|
||||
<key>UIDeviceFamily</key>
|
||||
<array>
|
||||
<integer>1</integer>
|
||||
<integer>2</integer>
|
||||
</array>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
|
@ -77,6 +77,8 @@ class DbVersionMigrator with WalletDB {
|
|||
name: e.name,
|
||||
id: e.id,
|
||||
useSSL: e.useSSL,
|
||||
torEnabled: e.torEnabled,
|
||||
clearnetEnabled: e.clearnetEnabled,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
@ -88,6 +90,8 @@ class DbVersionMigrator with WalletDB {
|
|||
name: node.name,
|
||||
id: node.id,
|
||||
useSSL: node.useSSL,
|
||||
torEnabled: node.torEnabled,
|
||||
clearnetEnabled: node.clearnetEnabled,
|
||||
),
|
||||
prefs: prefs,
|
||||
failovers: failovers,
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
import 'dart:isolate';
|
||||
|
||||
import 'package:cw_core/wallet_info.dart' as xmr;
|
||||
import 'package:compat/compat.dart' as lib_monero_compat;
|
||||
import 'package:hive/hive.dart' show Box;
|
||||
import 'package:hive/src/hive_impl.dart';
|
||||
import 'package:mutex/mutex.dart';
|
||||
|
@ -71,7 +71,7 @@ class DB {
|
|||
Box<Trade>? _boxTradesV2;
|
||||
Box<String>? _boxTradeNotes;
|
||||
Box<String>? _boxFavoriteWallets;
|
||||
Box<xmr.WalletInfo>? _walletInfoSource;
|
||||
Box<lib_monero_compat.WalletInfo>? _walletInfoSource;
|
||||
Box<dynamic>? _boxPrefs;
|
||||
Box<TradeWalletLookup>? _boxTradeLookup;
|
||||
Box<dynamic>? _boxDBInfo;
|
||||
|
@ -85,7 +85,8 @@ class DB {
|
|||
final Map<String, Box<dynamic>> _getSparkUsedCoinsTagsCacheBoxes = {};
|
||||
|
||||
// exposed for monero
|
||||
Box<xmr.WalletInfo> get moneroWalletInfoBox => _walletInfoSource!;
|
||||
Box<lib_monero_compat.WalletInfo> get moneroWalletInfoBox =>
|
||||
_walletInfoSource!;
|
||||
|
||||
// mutex for stack backup
|
||||
final mutex = Mutex();
|
||||
|
@ -147,8 +148,8 @@ class DB {
|
|||
_boxTradesV2 = await hive.openBox<Trade>(boxNameTradesV2);
|
||||
_boxTradeNotes = await hive.openBox<String>(boxNameTradeNotes);
|
||||
_boxTradeLookup = await hive.openBox<TradeWalletLookup>(boxNameTradeLookup);
|
||||
_walletInfoSource =
|
||||
await hive.openBox<xmr.WalletInfo>(xmr.WalletInfo.boxName);
|
||||
_walletInfoSource = await hive.openBox<lib_monero_compat.WalletInfo>(
|
||||
lib_monero_compat.WalletInfo.boxName);
|
||||
_boxFavoriteWallets = await hive.openBox<String>(boxNameFavoriteWallets);
|
||||
|
||||
await Future.wait([
|
||||
|
|
|
@ -3,6 +3,8 @@ import 'dart:async';
|
|||
import 'package:electrum_adapter/electrum_adapter.dart';
|
||||
|
||||
import '../utilities/logger.dart';
|
||||
import '../utilities/prefs.dart';
|
||||
import '../utilities/tor_plain_net_option_enum.dart';
|
||||
import '../wallets/crypto_currency/crypto_currency.dart';
|
||||
|
||||
class ClientManager {
|
||||
|
@ -10,6 +12,7 @@ class ClientManager {
|
|||
static final ClientManager sharedInstance = ClientManager._();
|
||||
|
||||
final Map<String, ElectrumClient> _map = {};
|
||||
final Map<String, TorPlainNetworkOption> _mapNet = {};
|
||||
final Map<String, int> _heights = {};
|
||||
final Map<String, StreamSubscription<BlockHeader>> _subscriptions = {};
|
||||
final Map<String, Completer<int>> _heightCompleters = {};
|
||||
|
@ -24,18 +27,37 @@ class ClientManager {
|
|||
|
||||
ElectrumClient? getClient({
|
||||
required CryptoCurrency cryptoCurrency,
|
||||
}) =>
|
||||
_map[_keyHelper(cryptoCurrency)];
|
||||
required TorPlainNetworkOption netType,
|
||||
}) {
|
||||
final _key = _keyHelper(cryptoCurrency);
|
||||
|
||||
void addClient(
|
||||
if (netType == _mapNet[_key]) {
|
||||
return _map[_key];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> addClient(
|
||||
ElectrumClient client, {
|
||||
required CryptoCurrency cryptoCurrency,
|
||||
}) {
|
||||
required TorPlainNetworkOption netType,
|
||||
}) async {
|
||||
final key = _keyHelper(cryptoCurrency);
|
||||
if (_map[key] != null) {
|
||||
throw Exception("ElectrumX Client for $key already exists.");
|
||||
if (_mapNet[key] == netType) {
|
||||
throw Exception(
|
||||
"ElectrumX Client for $key and $netType already exists.",
|
||||
);
|
||||
}
|
||||
|
||||
await remove(cryptoCurrency: cryptoCurrency);
|
||||
|
||||
_map[key] = client;
|
||||
_mapNet[key] = netType;
|
||||
} else {
|
||||
_map[key] = client;
|
||||
_mapNet[key] = netType;
|
||||
}
|
||||
|
||||
_heightCompleters[key] = Completer<int>();
|
||||
|
@ -68,10 +90,24 @@ class ClientManager {
|
|||
);
|
||||
}
|
||||
|
||||
if (Prefs.instance.useTor) {
|
||||
if (_mapNet[key]! == TorPlainNetworkOption.clear) {
|
||||
throw Exception(
|
||||
"Non-TOR only client for $key found.",
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (_mapNet[key]! == TorPlainNetworkOption.tor) {
|
||||
throw Exception(
|
||||
"TOR only client for $key found.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return _heights[key] ?? await _heightCompleters[key]!.future;
|
||||
}
|
||||
|
||||
Future<ElectrumClient?> remove({
|
||||
Future<(ElectrumClient?, TorPlainNetworkOption?)> remove({
|
||||
required CryptoCurrency cryptoCurrency,
|
||||
}) async {
|
||||
final key = _keyHelper(cryptoCurrency);
|
||||
|
@ -80,7 +116,7 @@ class ClientManager {
|
|||
_heights.remove(key);
|
||||
_heightCompleters.remove(key);
|
||||
|
||||
return _map.remove(key);
|
||||
return (_map.remove(key), _mapNet.remove(key));
|
||||
}
|
||||
|
||||
Future<void> closeAll() async {
|
||||
|
@ -99,6 +135,7 @@ class ClientManager {
|
|||
_heightCompleters.clear();
|
||||
_heights.clear();
|
||||
_subscriptions.clear();
|
||||
_mapNet.clear();
|
||||
_map.clear();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import '../utilities/amount/amount.dart';
|
|||
import '../utilities/extensions/impl/string.dart';
|
||||
import '../utilities/logger.dart';
|
||||
import '../utilities/prefs.dart';
|
||||
import '../utilities/tor_plain_net_option_enum.dart';
|
||||
import '../wallets/crypto_currency/crypto_currency.dart';
|
||||
import '../wallets/crypto_currency/interfaces/electrumx_currency_interface.dart';
|
||||
import 'client_manager.dart';
|
||||
|
@ -42,6 +43,10 @@ typedef SparkMempoolData = ({
|
|||
|
||||
class WifiOnlyException implements Exception {}
|
||||
|
||||
class TorOnlyException implements Exception {}
|
||||
|
||||
class ClearnetOnlyException implements Exception {}
|
||||
|
||||
class ElectrumXNode {
|
||||
ElectrumXNode({
|
||||
required this.address,
|
||||
|
@ -49,12 +54,16 @@ class ElectrumXNode {
|
|||
required this.name,
|
||||
required this.id,
|
||||
required this.useSSL,
|
||||
required this.torEnabled,
|
||||
required this.clearnetEnabled,
|
||||
});
|
||||
final String address;
|
||||
final int port;
|
||||
final String name;
|
||||
final String id;
|
||||
final bool useSSL;
|
||||
final bool torEnabled;
|
||||
final bool clearnetEnabled;
|
||||
|
||||
factory ElectrumXNode.from(ElectrumXNode node) {
|
||||
return ElectrumXNode(
|
||||
|
@ -63,6 +72,8 @@ class ElectrumXNode {
|
|||
name: node.name,
|
||||
id: node.id,
|
||||
useSSL: node.useSSL,
|
||||
torEnabled: node.torEnabled,
|
||||
clearnetEnabled: node.clearnetEnabled,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -74,6 +85,7 @@ class ElectrumXNode {
|
|||
|
||||
class ElectrumXClient {
|
||||
final CryptoCurrency cryptoCurrency;
|
||||
final TorPlainNetworkOption netType;
|
||||
|
||||
String get host => _host;
|
||||
late String _host;
|
||||
|
@ -90,6 +102,7 @@ class ElectrumXClient {
|
|||
ElectrumClient? getElectrumAdapter() =>
|
||||
ClientManager.sharedInstance.getClient(
|
||||
cryptoCurrency: cryptoCurrency,
|
||||
netType: netType,
|
||||
);
|
||||
|
||||
late Prefs _prefs;
|
||||
|
@ -119,6 +132,7 @@ class ElectrumXClient {
|
|||
required int port,
|
||||
required bool useSSL,
|
||||
required Prefs prefs,
|
||||
required this.netType,
|
||||
required List<ElectrumXNode> failovers,
|
||||
required this.cryptoCurrency,
|
||||
this.connectionTimeoutForSpecialCaseJsonRPCClients =
|
||||
|
@ -168,6 +182,7 @@ class ElectrumXClient {
|
|||
_electrumAdapterChannel = null;
|
||||
await (await ClientManager.sharedInstance
|
||||
.remove(cryptoCurrency: cryptoCurrency))
|
||||
.$1
|
||||
?.close();
|
||||
|
||||
// Also close any chain height services that are currently open.
|
||||
|
@ -193,6 +208,10 @@ class ElectrumXClient {
|
|||
failovers: failovers,
|
||||
globalEventBusForTesting: globalEventBusForTesting,
|
||||
cryptoCurrency: cryptoCurrency,
|
||||
netType: TorPlainNetworkOption.fromNodeData(
|
||||
node.torEnabled,
|
||||
node.clearnetEnabled,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -236,6 +255,18 @@ class ElectrumXClient {
|
|||
// Get the proxy info from the TorService.
|
||||
proxyInfo = _torService.getProxyInfo();
|
||||
}
|
||||
|
||||
if (netType == TorPlainNetworkOption.clear) {
|
||||
_electrumAdapterChannel = null;
|
||||
await ClientManager.sharedInstance
|
||||
.remove(cryptoCurrency: cryptoCurrency);
|
||||
}
|
||||
} else {
|
||||
if (netType == TorPlainNetworkOption.tor) {
|
||||
_electrumAdapterChannel = null;
|
||||
await ClientManager.sharedInstance
|
||||
.remove(cryptoCurrency: cryptoCurrency);
|
||||
}
|
||||
}
|
||||
|
||||
// If the current ElectrumAdapterClient is closed, create a new one.
|
||||
|
@ -288,9 +319,10 @@ class ElectrumXClient {
|
|||
);
|
||||
}
|
||||
|
||||
ClientManager.sharedInstance.addClient(
|
||||
await ClientManager.sharedInstance.addClient(
|
||||
newClient,
|
||||
cryptoCurrency: cryptoCurrency,
|
||||
netType: netType,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -352,6 +384,10 @@ class ElectrumXClient {
|
|||
return response;
|
||||
} on WifiOnlyException {
|
||||
rethrow;
|
||||
} on ClearnetOnlyException {
|
||||
rethrow;
|
||||
} on TorOnlyException {
|
||||
rethrow;
|
||||
} on SocketException {
|
||||
// likely timed out so then retry
|
||||
if (retries > 0) {
|
||||
|
@ -442,6 +478,10 @@ class ElectrumXClient {
|
|||
return response;
|
||||
} on WifiOnlyException {
|
||||
rethrow;
|
||||
} on ClearnetOnlyException {
|
||||
rethrow;
|
||||
} on TorOnlyException {
|
||||
rethrow;
|
||||
} on SocketException {
|
||||
// likely timed out so then retry
|
||||
if (retries > 0) {
|
||||
|
@ -488,10 +528,10 @@ class ElectrumXClient {
|
|||
return await request(
|
||||
requestID: requestID,
|
||||
command: 'server.ping',
|
||||
requestTimeout: const Duration(seconds: 2),
|
||||
requestTimeout: const Duration(seconds: 3),
|
||||
retries: retryCount,
|
||||
).timeout(
|
||||
const Duration(seconds: 2),
|
||||
const Duration(seconds: 3),
|
||||
onTimeout: () {
|
||||
Logging.instance.log(
|
||||
"ElectrumxClient.ping timed out with retryCount=$retryCount, host=$_host",
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
class NodeTorMismatchConfigException implements Exception {
|
||||
final String message;
|
||||
|
||||
NodeTorMismatchConfigException({required this.message});
|
||||
|
||||
@override
|
||||
String toString() => message;
|
||||
}
|
|
@ -13,11 +13,9 @@ import 'dart:io';
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:coinlib_flutter/coinlib_flutter.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/pathForWallet.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:compat/compat.dart' as lib_monero_compat;
|
||||
import 'package:cs_monero/cs_monero.dart' as lib_monero;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
@ -92,12 +90,6 @@ void main(List<String> args) async {
|
|||
StackFileSystem.setDesktopOverrideDir(args.last);
|
||||
}
|
||||
|
||||
// Tell flutter_libmonero how to get access to the application dir
|
||||
FS.setApplicationRootDirectoryFunction(
|
||||
StackFileSystem.applicationRootDirectory,
|
||||
);
|
||||
// TODO set any other external libs file paths (bad external lib design workaround)
|
||||
|
||||
final loadCoinlibFuture = loadCoinlib();
|
||||
|
||||
GoogleFonts.config.allowRuntimeFetching = false;
|
||||
|
@ -170,15 +162,14 @@ void main(List<String> args) async {
|
|||
// node model adapter
|
||||
DB.instance.hive.registerAdapter(NodeModelAdapter());
|
||||
|
||||
DB.instance.hive.registerAdapter(NodeAdapter());
|
||||
|
||||
if (!DB.instance.hive.isAdapterRegistered(WalletInfoAdapter().typeId)) {
|
||||
DB.instance.hive.registerAdapter(WalletInfoAdapter());
|
||||
if (!DB.instance.hive
|
||||
.isAdapterRegistered(lib_monero_compat.WalletInfoAdapter().typeId)) {
|
||||
DB.instance.hive.registerAdapter(lib_monero_compat.WalletInfoAdapter());
|
||||
}
|
||||
|
||||
DB.instance.hive.registerAdapter(WalletTypeAdapter());
|
||||
DB.instance.hive.registerAdapter(lib_monero_compat.WalletTypeAdapter());
|
||||
|
||||
DB.instance.hive.registerAdapter(UnspentCoinsInfoAdapter());
|
||||
lib_monero.Logging.useLogger = kDebugMode;
|
||||
|
||||
DB.instance.hive.init(
|
||||
(await StackFileSystem.applicationHiveDirectory()).path,
|
||||
|
|
|
@ -80,9 +80,14 @@ class UTXO {
|
|||
return max(0, currentChainHeight - (blockHeight! - 1));
|
||||
}
|
||||
|
||||
bool isConfirmed(int currentChainHeight, int minimumConfirms) {
|
||||
bool isConfirmed(
|
||||
int currentChainHeight,
|
||||
int minimumConfirms,
|
||||
int minimumCoinbaseConfirms,
|
||||
) {
|
||||
final confirmations = getConfirmations(currentChainHeight);
|
||||
return confirmations >= minimumConfirms;
|
||||
return confirmations >=
|
||||
(isCoinbase ? minimumCoinbaseConfirms : minimumConfirms);
|
||||
}
|
||||
|
||||
@ignore
|
||||
|
|
|
@ -112,9 +112,14 @@ class TransactionV2 {
|
|||
return max(0, currentChainHeight - (height! - 1));
|
||||
}
|
||||
|
||||
bool isConfirmed(int currentChainHeight, int minimumConfirms) {
|
||||
bool isConfirmed(
|
||||
int currentChainHeight,
|
||||
int minimumConfirms,
|
||||
int minimumCoinbaseConfirms,
|
||||
) {
|
||||
final confirmations = getConfirmations(currentChainHeight);
|
||||
return confirmations >= minimumConfirms;
|
||||
return confirmations >=
|
||||
(isCoinbase() ? minimumCoinbaseConfirms : minimumConfirms);
|
||||
}
|
||||
|
||||
Amount getFee({required int fractionDigits}) {
|
||||
|
@ -124,6 +129,10 @@ class TransactionV2 {
|
|||
return fee;
|
||||
}
|
||||
|
||||
if (isCoinbase()) {
|
||||
return Amount.zeroWith(fractionDigits: fractionDigits);
|
||||
}
|
||||
|
||||
final inSum =
|
||||
inputs.map((e) => e.value).reduce((value, element) => value += element);
|
||||
final outSum = outputs
|
||||
|
@ -134,6 +143,14 @@ class TransactionV2 {
|
|||
}
|
||||
|
||||
Amount getAmountReceivedInThisWallet({required int fractionDigits}) {
|
||||
if (_isMonero()) {
|
||||
if (type == TransactionType.incoming) {
|
||||
return _getMoneroAmount()!;
|
||||
} else {
|
||||
return Amount.zeroWith(fractionDigits: fractionDigits);
|
||||
}
|
||||
}
|
||||
|
||||
final outSum = outputs
|
||||
.where((e) => e.walletOwns)
|
||||
.fold(BigInt.zero, (p, e) => p + e.value);
|
||||
|
@ -151,6 +168,14 @@ class TransactionV2 {
|
|||
}
|
||||
|
||||
Amount getAmountSentFromThisWallet({required int fractionDigits}) {
|
||||
if (_isMonero()) {
|
||||
if (type == TransactionType.outgoing) {
|
||||
return _getMoneroAmount()!;
|
||||
} else {
|
||||
return Amount.zeroWith(fractionDigits: fractionDigits);
|
||||
}
|
||||
}
|
||||
|
||||
final inSum = inputs
|
||||
.where((e) => e.walletOwns)
|
||||
.fold(BigInt.zero, (p, e) => p + e.value);
|
||||
|
@ -191,18 +216,37 @@ class TransactionV2 {
|
|||
}
|
||||
}
|
||||
|
||||
Amount? _getMoneroAmount() {
|
||||
try {
|
||||
return Amount.fromSerializedJsonString(
|
||||
_getFromOtherData(key: TxV2OdKeys.moneroAmount) as String,
|
||||
);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
bool _isMonero() {
|
||||
final value = _getFromOtherData(key: TxV2OdKeys.isMoneroTransaction);
|
||||
return value is bool ? value : false;
|
||||
}
|
||||
|
||||
String statusLabel({
|
||||
required int currentChainHeight,
|
||||
required int minConfirms,
|
||||
required int minCoinbaseConfirms,
|
||||
}) {
|
||||
String prettyConfirms() =>
|
||||
"(${getConfirmations(currentChainHeight)}/$minConfirms)";
|
||||
String prettyConfirms() => "("
|
||||
"${getConfirmations(currentChainHeight)}"
|
||||
"/"
|
||||
"${(isCoinbase() ? minCoinbaseConfirms : minConfirms)}"
|
||||
")";
|
||||
|
||||
if (subType == TransactionSubType.cashFusion ||
|
||||
subType == TransactionSubType.mint ||
|
||||
(subType == TransactionSubType.sparkMint &&
|
||||
type == TransactionType.sentToSelf)) {
|
||||
if (isConfirmed(currentChainHeight, minConfirms)) {
|
||||
if (isConfirmed(currentChainHeight, minConfirms, minCoinbaseConfirms)) {
|
||||
return "Anonymized";
|
||||
} else {
|
||||
return "Anonymizing ${prettyConfirms()}";
|
||||
|
@ -217,7 +261,7 @@ class TransactionV2 {
|
|||
if (isCancelled) {
|
||||
return "Cancelled";
|
||||
} else if (type == TransactionType.incoming) {
|
||||
if (isConfirmed(currentChainHeight, minConfirms)) {
|
||||
if (isConfirmed(currentChainHeight, minConfirms, minCoinbaseConfirms)) {
|
||||
return "Received";
|
||||
} else {
|
||||
if (numberOfMessages == 1) {
|
||||
|
@ -229,7 +273,7 @@ class TransactionV2 {
|
|||
}
|
||||
}
|
||||
} else if (type == TransactionType.outgoing) {
|
||||
if (isConfirmed(currentChainHeight, minConfirms)) {
|
||||
if (isConfirmed(currentChainHeight, minConfirms, minCoinbaseConfirms)) {
|
||||
return "Sent (confirmed)";
|
||||
} else {
|
||||
if (numberOfMessages == 1) {
|
||||
|
@ -247,19 +291,19 @@ class TransactionV2 {
|
|||
// if (_transaction.isMinting) {
|
||||
// return "Minting";
|
||||
// } else
|
||||
if (isConfirmed(currentChainHeight, minConfirms)) {
|
||||
if (isConfirmed(currentChainHeight, minConfirms, minCoinbaseConfirms)) {
|
||||
return "Received";
|
||||
} else {
|
||||
return "Receiving ${prettyConfirms()}";
|
||||
}
|
||||
} else if (type == TransactionType.outgoing) {
|
||||
if (isConfirmed(currentChainHeight, minConfirms)) {
|
||||
if (isConfirmed(currentChainHeight, minConfirms, minCoinbaseConfirms)) {
|
||||
return "Sent";
|
||||
} else {
|
||||
return "Sending ${prettyConfirms()}";
|
||||
}
|
||||
} else if (type == TransactionType.sentToSelf) {
|
||||
if (isConfirmed(currentChainHeight, minConfirms)) {
|
||||
if (isConfirmed(currentChainHeight, minConfirms, minCoinbaseConfirms)) {
|
||||
return "Sent to self";
|
||||
} else {
|
||||
return "Sent to self ${prettyConfirms()}";
|
||||
|
@ -277,6 +321,9 @@ class TransactionV2 {
|
|||
return map[key];
|
||||
}
|
||||
|
||||
bool isCoinbase() =>
|
||||
type == TransactionType.incoming && inputs.any((e) => e.coinbase != null);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'TransactionV2(\n'
|
||||
|
@ -307,4 +354,7 @@ abstract final class TxV2OdKeys {
|
|||
static const contractAddress = "contractAddress";
|
||||
static const nonce = "nonce";
|
||||
static const overrideFee = "overrideFee";
|
||||
static const moneroAmount = "moneroAmount";
|
||||
static const moneroAccountIndex = "moneroAccountIndex";
|
||||
static const isMoneroTransaction = "isMoneroTransaction";
|
||||
}
|
||||
|
|
164
lib/models/keys/view_only_wallet_data.dart
Normal file
164
lib/models/keys/view_only_wallet_data.dart
Normal file
|
@ -0,0 +1,164 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import '../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
|
||||
import 'key_data_interface.dart';
|
||||
|
||||
// do not remove or change the order of these enum values
|
||||
enum ViewOnlyWalletType {
|
||||
cryptonote,
|
||||
addressOnly,
|
||||
xPub;
|
||||
}
|
||||
|
||||
sealed class ViewOnlyWalletData with KeyDataInterface {
|
||||
@override
|
||||
final String walletId;
|
||||
|
||||
ViewOnlyWalletType get type;
|
||||
|
||||
ViewOnlyWalletData({
|
||||
required this.walletId,
|
||||
});
|
||||
|
||||
static ViewOnlyWalletData fromJsonEncodedString(
|
||||
String jsonEncodedString, {
|
||||
required String walletId,
|
||||
}) {
|
||||
final map = jsonDecode(jsonEncodedString) as Map;
|
||||
final json = Map<String, dynamic>.from(map);
|
||||
final type = ViewOnlyWalletType.values[json["type"] as int];
|
||||
|
||||
switch (type) {
|
||||
case ViewOnlyWalletType.cryptonote:
|
||||
return CryptonoteViewOnlyWalletData.fromJsonEncodedString(
|
||||
jsonEncodedString,
|
||||
walletId: walletId,
|
||||
);
|
||||
|
||||
case ViewOnlyWalletType.addressOnly:
|
||||
return AddressViewOnlyWalletData.fromJsonEncodedString(
|
||||
jsonEncodedString,
|
||||
walletId: walletId,
|
||||
);
|
||||
|
||||
case ViewOnlyWalletType.xPub:
|
||||
return ExtendedKeysViewOnlyWalletData.fromJsonEncodedString(
|
||||
jsonEncodedString,
|
||||
walletId: walletId,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String toJsonEncodedString();
|
||||
}
|
||||
|
||||
class CryptonoteViewOnlyWalletData extends ViewOnlyWalletData {
|
||||
@override
|
||||
final type = ViewOnlyWalletType.cryptonote;
|
||||
|
||||
final String address;
|
||||
final String privateViewKey;
|
||||
|
||||
CryptonoteViewOnlyWalletData({
|
||||
required super.walletId,
|
||||
required this.address,
|
||||
required this.privateViewKey,
|
||||
});
|
||||
|
||||
static CryptonoteViewOnlyWalletData fromJsonEncodedString(
|
||||
String jsonEncodedString, {
|
||||
required String walletId,
|
||||
}) {
|
||||
final map = jsonDecode(jsonEncodedString) as Map;
|
||||
final json = Map<String, dynamic>.from(map);
|
||||
|
||||
return CryptonoteViewOnlyWalletData(
|
||||
walletId: walletId,
|
||||
address: json["address"] as String,
|
||||
privateViewKey: json["privateViewKey"] as String,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toJsonEncodedString() => jsonEncode({
|
||||
"type": type.index,
|
||||
"address": address,
|
||||
"privateViewKey": privateViewKey,
|
||||
});
|
||||
}
|
||||
|
||||
class AddressViewOnlyWalletData extends ViewOnlyWalletData {
|
||||
@override
|
||||
final type = ViewOnlyWalletType.addressOnly;
|
||||
|
||||
final String address;
|
||||
|
||||
AddressViewOnlyWalletData({
|
||||
required super.walletId,
|
||||
required this.address,
|
||||
});
|
||||
|
||||
static AddressViewOnlyWalletData fromJsonEncodedString(
|
||||
String jsonEncodedString, {
|
||||
required String walletId,
|
||||
}) {
|
||||
final map = jsonDecode(jsonEncodedString) as Map;
|
||||
final json = Map<String, dynamic>.from(map);
|
||||
|
||||
return AddressViewOnlyWalletData(
|
||||
walletId: walletId,
|
||||
address: json["address"] as String,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toJsonEncodedString() => jsonEncode({
|
||||
"type": type.index,
|
||||
"address": address,
|
||||
});
|
||||
}
|
||||
|
||||
class ExtendedKeysViewOnlyWalletData extends ViewOnlyWalletData {
|
||||
@override
|
||||
final type = ViewOnlyWalletType.xPub;
|
||||
|
||||
final List<XPub> xPubs;
|
||||
|
||||
ExtendedKeysViewOnlyWalletData({
|
||||
required super.walletId,
|
||||
required List<XPub> xPubs,
|
||||
}) : xPubs = List.unmodifiable(xPubs);
|
||||
|
||||
static ExtendedKeysViewOnlyWalletData fromJsonEncodedString(
|
||||
String jsonEncodedString, {
|
||||
required String walletId,
|
||||
}) {
|
||||
final map = jsonDecode(jsonEncodedString) as Map;
|
||||
final json = Map<String, dynamic>.from(map);
|
||||
|
||||
return ExtendedKeysViewOnlyWalletData(
|
||||
walletId: walletId,
|
||||
xPubs: List<Map<String, dynamic>>.from((json["xPubs"] as List))
|
||||
.map(
|
||||
(e) => XPub(
|
||||
path: e["path"] as String,
|
||||
encoded: e["encoded"] as String,
|
||||
),
|
||||
)
|
||||
.toList(growable: false),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toJsonEncodedString() => jsonEncode({
|
||||
"type": type.index,
|
||||
"xPubs": [
|
||||
...xPubs.map(
|
||||
(e) => {
|
||||
"path": e.path,
|
||||
"encoded": e.encoded,
|
||||
},
|
||||
),
|
||||
],
|
||||
});
|
||||
}
|
|
@ -9,6 +9,7 @@
|
|||
*/
|
||||
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
import '../utilities/default_nodes.dart';
|
||||
import '../utilities/flutter_secure_storage_interface.dart';
|
||||
|
||||
|
@ -38,6 +39,10 @@ class NodeModel {
|
|||
final bool isDown;
|
||||
// @HiveField(10)
|
||||
final bool? trusted;
|
||||
// @HiveField(11)
|
||||
final bool torEnabled;
|
||||
// @HiveField(12)
|
||||
final bool clearnetEnabled;
|
||||
|
||||
NodeModel({
|
||||
required this.host,
|
||||
|
@ -49,6 +54,8 @@ class NodeModel {
|
|||
required this.coinName,
|
||||
required this.isFailover,
|
||||
required this.isDown,
|
||||
required this.torEnabled,
|
||||
required this.clearnetEnabled,
|
||||
this.loginName,
|
||||
this.trusted,
|
||||
});
|
||||
|
@ -64,6 +71,8 @@ class NodeModel {
|
|||
bool? isFailover,
|
||||
bool? isDown,
|
||||
bool? trusted,
|
||||
bool? torEnabled,
|
||||
bool? clearnetEnabled,
|
||||
}) {
|
||||
return NodeModel(
|
||||
host: host ?? this.host,
|
||||
|
@ -77,6 +86,8 @@ class NodeModel {
|
|||
isFailover: isFailover ?? this.isFailover,
|
||||
isDown: isDown ?? this.isDown,
|
||||
trusted: trusted ?? this.trusted,
|
||||
torEnabled: torEnabled ?? this.torEnabled,
|
||||
clearnetEnabled: clearnetEnabled ?? this.clearnetEnabled,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -98,6 +109,8 @@ class NodeModel {
|
|||
map['isFailover'] = isFailover;
|
||||
map['isDown'] = isDown;
|
||||
map['trusted'] = trusted;
|
||||
map['torEnabled'] = torEnabled;
|
||||
map['clearEnabled'] = clearnetEnabled;
|
||||
return map;
|
||||
}
|
||||
|
||||
|
|
|
@ -28,13 +28,15 @@ class NodeModelAdapter extends TypeAdapter<NodeModel> {
|
|||
isFailover: fields[8] as bool,
|
||||
isDown: fields[9] as bool,
|
||||
trusted: fields[10] as bool?,
|
||||
torEnabled: fields[11] as bool? ?? true,
|
||||
clearnetEnabled: fields[12] as bool? ?? true,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, NodeModel obj) {
|
||||
writer
|
||||
..writeByte(11)
|
||||
..writeByte(13)
|
||||
..writeByte(0)
|
||||
..write(obj.id)
|
||||
..writeByte(1)
|
||||
|
@ -56,7 +58,11 @@ class NodeModelAdapter extends TypeAdapter<NodeModel> {
|
|||
..writeByte(9)
|
||||
..write(obj.isDown)
|
||||
..writeByte(10)
|
||||
..write(obj.trusted);
|
||||
..write(obj.trusted)
|
||||
..writeByte(11)
|
||||
..write(obj.torEnabled)
|
||||
..writeByte(12)
|
||||
..write(obj.clearnetEnabled);
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
@ -131,11 +130,6 @@ class _AddWalletViewState extends ConsumerState<AddWalletView> {
|
|||
void initState() {
|
||||
_searchFieldController = TextEditingController();
|
||||
_searchFocusNode = FocusNode();
|
||||
// _coinsTestnet.remove(Coin.firoTestNet);
|
||||
|
||||
if (Util.isDesktop && !kDebugMode) {
|
||||
_coins.removeWhere((e) => e is BitcoinFrost);
|
||||
}
|
||||
|
||||
coinEntities.addAll(_coins.map((e) => CoinEntity(e)));
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import '../../../utilities/name_generator.dart';
|
|||
import '../../../utilities/text_styles.dart';
|
||||
import '../../../utilities/util.dart';
|
||||
import '../../../wallets/crypto_currency/crypto_currency.dart';
|
||||
import '../../../wallets/crypto_currency/interfaces/view_only_option_currency_interface.dart';
|
||||
import '../../../wallets/crypto_currency/intermediate/frost_currency.dart';
|
||||
import '../../../wallets/isar/models/wallet_info.dart';
|
||||
import '../../../widgets/background.dart';
|
||||
|
@ -104,7 +105,9 @@ class _NameYourWalletViewState extends ConsumerState<NameYourWalletView> {
|
|||
case AddWalletType.New:
|
||||
unawaited(
|
||||
Navigator.of(context).pushNamed(
|
||||
coin.hasMnemonicPassphraseSupport
|
||||
coin.possibleMnemonicLengths.length > 1 ||
|
||||
coin.hasMnemonicPassphraseSupport ||
|
||||
coin is ViewOnlyOptionCurrencyInterface
|
||||
? NewWalletOptionsView.routeName
|
||||
: NewWalletRecoveryPhraseWarningView.routeName,
|
||||
arguments: Tuple2(
|
||||
|
|
|
@ -12,9 +12,11 @@ import '../../../utilities/constants.dart';
|
|||
import '../../../utilities/text_styles.dart';
|
||||
import '../../../utilities/util.dart';
|
||||
import '../../../wallets/crypto_currency/crypto_currency.dart';
|
||||
import '../../../wallets/crypto_currency/interfaces/view_only_option_currency_interface.dart';
|
||||
import '../../../widgets/background.dart';
|
||||
import '../../../widgets/conditional_parent.dart';
|
||||
import '../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../../widgets/custom_buttons/checkbox_text_button.dart';
|
||||
import '../../../widgets/desktop/desktop_app_bar.dart';
|
||||
import '../../../widgets/desktop/desktop_scaffold.dart';
|
||||
import '../../../widgets/desktop/primary_button.dart';
|
||||
|
@ -25,8 +27,12 @@ import '../new_wallet_recovery_phrase_warning_view/new_wallet_recovery_phrase_wa
|
|||
import '../restore_wallet_view/restore_options_view/sub_widgets/mobile_mnemonic_length_selector.dart';
|
||||
import '../restore_wallet_view/sub_widgets/mnemonic_word_count_select_sheet.dart';
|
||||
|
||||
final pNewWalletOptions =
|
||||
StateProvider<({String mnemonicPassphrase, int mnemonicWordsCount})?>(
|
||||
final pNewWalletOptions = StateProvider<
|
||||
({
|
||||
String mnemonicPassphrase,
|
||||
int mnemonicWordsCount,
|
||||
bool convertToViewOnly,
|
||||
})?>(
|
||||
(ref) => null,
|
||||
);
|
||||
|
||||
|
@ -59,6 +65,8 @@ class _NewWalletOptionsViewState extends ConsumerState<NewWalletOptionsView> {
|
|||
bool hidePassword = true;
|
||||
NewWalletOptions _selectedOptions = NewWalletOptions.Default;
|
||||
|
||||
bool _convertToViewOnly = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
passwordController = TextEditingController();
|
||||
|
@ -210,7 +218,7 @@ class _NewWalletOptionsViewState extends ConsumerState<NewWalletOptionsView> {
|
|||
if (_selectedOptions == NewWalletOptions.Advanced)
|
||||
Column(
|
||||
children: [
|
||||
if (Util.isDesktop)
|
||||
if (Util.isDesktop && lengths.length > 1)
|
||||
DropdownButtonHideUnderline(
|
||||
child: DropdownButton2<int>(
|
||||
value: ref
|
||||
|
@ -265,7 +273,7 @@ class _NewWalletOptionsViewState extends ConsumerState<NewWalletOptionsView> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (!Util.isDesktop)
|
||||
if (!Util.isDesktop && lengths.length > 1)
|
||||
MobileMnemonicLengthSelector(
|
||||
chooseMnemonicLength: () {
|
||||
showModalBottomSheet<dynamic>(
|
||||
|
@ -284,91 +292,109 @@ class _NewWalletOptionsViewState extends ConsumerState<NewWalletOptionsView> {
|
|||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
RoundedWhiteContainer(
|
||||
child: Center(
|
||||
child: Text(
|
||||
"You may add a BIP39 passphrase. This is optional. "
|
||||
"You will need BOTH your seed and your passphrase to recover the wallet.",
|
||||
style: Util.isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
)
|
||||
: STextStyles.itemSubtitle(context),
|
||||
if (widget.coin.hasMnemonicPassphraseSupport)
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
if (widget.coin.hasMnemonicPassphraseSupport)
|
||||
RoundedWhiteContainer(
|
||||
child: Center(
|
||||
child: Text(
|
||||
"You may add a BIP39 passphrase. This is optional. "
|
||||
"You will need BOTH your seed and your passphrase to recover the wallet.",
|
||||
style: Util.isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
)
|
||||
: STextStyles.itemSubtitle(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
if (widget.coin.hasMnemonicPassphraseSupport)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
child: TextField(
|
||||
key: const Key("mnemonicPassphraseFieldKey1"),
|
||||
focusNode: passwordFocusNode,
|
||||
controller: passwordController,
|
||||
style: Util.isDesktop
|
||||
? STextStyles.desktopTextMedium(context).copyWith(
|
||||
height: 2,
|
||||
)
|
||||
: STextStyles.field(context),
|
||||
obscureText: hidePassword,
|
||||
enableSuggestions: false,
|
||||
autocorrect: false,
|
||||
decoration: standardInputDecoration(
|
||||
"BIP39 passphrase",
|
||||
passwordFocusNode,
|
||||
context,
|
||||
).copyWith(
|
||||
suffixIcon: UnconstrainedBox(
|
||||
child: ConditionalParent(
|
||||
condition: Util.isDesktop,
|
||||
builder: (child) => SizedBox(
|
||||
height: 70,
|
||||
child: child,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: Util.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<StackColors>()!
|
||||
.textDark3,
|
||||
if (widget.coin.hasMnemonicPassphraseSupport)
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
key: const Key("mnemonicPassphraseFieldKey1"),
|
||||
focusNode: passwordFocusNode,
|
||||
controller: passwordController,
|
||||
style: Util.isDesktop
|
||||
? STextStyles.desktopTextMedium(context).copyWith(
|
||||
height: 2,
|
||||
)
|
||||
: STextStyles.field(context),
|
||||
obscureText: hidePassword,
|
||||
enableSuggestions: false,
|
||||
autocorrect: false,
|
||||
decoration: standardInputDecoration(
|
||||
"BIP39 passphrase",
|
||||
passwordFocusNode,
|
||||
context,
|
||||
).copyWith(
|
||||
suffixIcon: UnconstrainedBox(
|
||||
child: ConditionalParent(
|
||||
condition: Util.isDesktop,
|
||||
builder: (child) => SizedBox(
|
||||
height: 70,
|
||||
child: child,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: Util.isDesktop ? 24 : 16,
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
],
|
||||
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<StackColors>()!
|
||||
.textDark3,
|
||||
width: Util.isDesktop ? 24 : 16,
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.coin is ViewOnlyOptionCurrencyInterface)
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
if (widget.coin is ViewOnlyOptionCurrencyInterface)
|
||||
CheckboxTextButton(
|
||||
label: "Convert to view only wallet. "
|
||||
"You will only be shown the seed phrase once. "
|
||||
"Save it somewhere. "
|
||||
"If you lose it you will lose access to any funds in this wallet.",
|
||||
onChanged: (value) {
|
||||
_convertToViewOnly = value;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
if (!Util.isDesktop) const Spacer(),
|
||||
|
@ -383,6 +409,7 @@ class _NewWalletOptionsViewState extends ConsumerState<NewWalletOptionsView> {
|
|||
mnemonicWordsCount:
|
||||
ref.read(mnemonicWordCountStateProvider.state).state,
|
||||
mnemonicPassphrase: passwordController.text,
|
||||
convertToViewOnly: _convertToViewOnly,
|
||||
);
|
||||
} else {
|
||||
ref.read(pNewWalletOptions.notifier).state = null;
|
||||
|
|
|
@ -12,8 +12,6 @@ import 'dart:async';
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:blockchain_utils/bip/bip/bip39/bip39_mnemonic.dart';
|
||||
import 'package:blockchain_utils/bip/bip/bip39/bip39_mnemonic_generator.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
@ -584,7 +582,12 @@ class _NewWalletRecoveryPhraseWarningViewState
|
|||
)
|
||||
.state!
|
||||
.mnemonicPassphrase;
|
||||
} else {}
|
||||
} else {
|
||||
// this may not be epiccash specific?
|
||||
if (coin is Epiccash) {
|
||||
mnemonicPassphrase = "";
|
||||
}
|
||||
}
|
||||
|
||||
wordCount = ref
|
||||
.read(
|
||||
|
|
|
@ -23,6 +23,8 @@ import '../../../../utilities/format.dart';
|
|||
import '../../../../utilities/text_styles.dart';
|
||||
import '../../../../utilities/util.dart';
|
||||
import '../../../../wallets/crypto_currency/crypto_currency.dart';
|
||||
import '../../../../wallets/crypto_currency/interfaces/view_only_option_currency_interface.dart';
|
||||
import '../../../../wallets/crypto_currency/intermediate/cryptonote_currency.dart';
|
||||
import '../../../../widgets/conditional_parent.dart';
|
||||
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../../../widgets/custom_buttons/checkbox_text_button.dart';
|
||||
|
@ -32,7 +34,9 @@ import '../../../../widgets/desktop/desktop_scaffold.dart';
|
|||
import '../../../../widgets/expandable.dart';
|
||||
import '../../../../widgets/rounded_white_container.dart';
|
||||
import '../../../../widgets/stack_text_field.dart';
|
||||
import '../../../../widgets/toggle.dart';
|
||||
import '../../create_or_restore_wallet_view/sub_widgets/coin_image.dart';
|
||||
import '../restore_view_only_wallet_view.dart';
|
||||
import '../restore_wallet_view.dart';
|
||||
import '../sub_widgets/mnemonic_word_count_select_sheet.dart';
|
||||
import 'sub_widgets/mobile_mnemonic_length_selector.dart';
|
||||
|
@ -67,9 +71,8 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
|
|||
late final TextEditingController passwordController;
|
||||
|
||||
final bool _nextEnabled = true;
|
||||
DateTime _restoreFromDate = DateTime.fromMillisecondsSinceEpoch(0);
|
||||
DateTime? _restoreFromDate;
|
||||
bool hidePassword = true;
|
||||
bool _expandedAdavnced = false;
|
||||
|
||||
bool get supportsMnemonicPassphrase => coin.hasMnemonicPassphraseSupport;
|
||||
|
||||
|
@ -99,27 +102,46 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
bool _nextLock = false;
|
||||
Future<void> nextPressed() async {
|
||||
if (!isDesktop) {
|
||||
// hide keyboard if has focus
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 75));
|
||||
if (_nextLock) return;
|
||||
_nextLock = true;
|
||||
try {
|
||||
if (!isDesktop) {
|
||||
// hide keyboard if has focus
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 75));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
await Navigator.of(context).pushNamed(
|
||||
RestoreWalletView.routeName,
|
||||
arguments: Tuple6(
|
||||
walletName,
|
||||
coin,
|
||||
ref.read(mnemonicWordCountStateProvider.state).state,
|
||||
_restoreFromDate,
|
||||
passwordController.text,
|
||||
enableLelantusScanning,
|
||||
),
|
||||
);
|
||||
if (mounted) {
|
||||
if (!_showViewOnlyOption) {
|
||||
await Navigator.of(context).pushNamed(
|
||||
RestoreWalletView.routeName,
|
||||
arguments: Tuple6(
|
||||
walletName,
|
||||
coin,
|
||||
ref.read(mnemonicWordCountStateProvider.state).state,
|
||||
_restoreFromDate,
|
||||
passwordController.text,
|
||||
enableLelantusScanning,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await Navigator.of(context).pushNamed(
|
||||
RestoreViewOnlyWalletView.routeName,
|
||||
arguments: (
|
||||
walletName: walletName,
|
||||
coin: coin,
|
||||
restoreFromDate: _restoreFromDate,
|
||||
enableLelantusScanning: enableLelantusScanning,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
_nextLock = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,17 +186,12 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
|
|||
);
|
||||
}
|
||||
|
||||
bool _showViewOnlyOption = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint("BUILD: $runtimeType with ${coin.identifier} $walletName");
|
||||
|
||||
final lengths = coin.possibleMnemonicLengths;
|
||||
|
||||
final isMoneroAnd25 = coin is Monero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state == 25;
|
||||
final isWowneroAnd25 = coin is Wownero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state == 25;
|
||||
|
||||
return MasterScaffold(
|
||||
isDesktop: isDesktop,
|
||||
appBar: isDesktop
|
||||
|
@ -227,288 +244,57 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
|
|||
SizedBox(
|
||||
height: isDesktop ? 40 : 24,
|
||||
),
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
Text(
|
||||
"Choose start date",
|
||||
style: isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
)
|
||||
: STextStyles.smallMed12(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
if (coin is ViewOnlyOptionCurrencyInterface)
|
||||
SizedBox(
|
||||
height: isDesktop ? 16 : 8,
|
||||
),
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
if (!isDesktop)
|
||||
RestoreFromDatePicker(
|
||||
onTap: chooseDate,
|
||||
controller: _dateController,
|
||||
),
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
if (isDesktop)
|
||||
// TODO desktop date picker
|
||||
RestoreFromDatePicker(
|
||||
onTap: chooseDesktopDate,
|
||||
controller: _dateController,
|
||||
),
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
RoundedWhiteContainer(
|
||||
child: Center(
|
||||
child: Text(
|
||||
"Choose the date you made the wallet (approximate is fine)",
|
||||
style: isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
)
|
||||
: STextStyles.smallMed12(context).copyWith(
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
SizedBox(
|
||||
height: isDesktop ? 24 : 16,
|
||||
),
|
||||
Text(
|
||||
"Choose recovery phrase length",
|
||||
style: isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
)
|
||||
: STextStyles.smallMed12(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
SizedBox(
|
||||
height: isDesktop ? 16 : 8,
|
||||
),
|
||||
if (isDesktop)
|
||||
DropdownButtonHideUnderline(
|
||||
child: DropdownButton2<int>(
|
||||
value:
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state,
|
||||
items: [
|
||||
...lengths.map(
|
||||
(e) => DropdownMenuItem(
|
||||
value: e,
|
||||
child: Text(
|
||||
"$e words",
|
||||
style: STextStyles.desktopTextMedium(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value is int) {
|
||||
ref.read(mnemonicWordCountStateProvider.state).state =
|
||||
value;
|
||||
}
|
||||
height: isDesktop ? 56 : 48,
|
||||
width: isDesktop ? 490 : null,
|
||||
child: Toggle(
|
||||
key: UniqueKey(),
|
||||
onText: "Seed",
|
||||
offText: "View Only",
|
||||
onColor:
|
||||
Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
offColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
isOn: _showViewOnlyOption,
|
||||
onValueChanged: (value) {
|
||||
setState(() {
|
||||
_showViewOnlyOption = value;
|
||||
});
|
||||
},
|
||||
isExpanded: true,
|
||||
iconStyleData: IconStyleData(
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.chevronDown,
|
||||
width: 12,
|
||||
height: 6,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldActiveSearchIconRight,
|
||||
),
|
||||
),
|
||||
dropdownStyleData: DropdownStyleData(
|
||||
offset: const Offset(0, -10),
|
||||
elevation: 0,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!isDesktop)
|
||||
MobileMnemonicLengthSelector(
|
||||
chooseMnemonicLength: chooseMnemonicLength,
|
||||
),
|
||||
if (supportsMnemonicPassphrase)
|
||||
if (coin is ViewOnlyOptionCurrencyInterface)
|
||||
SizedBox(
|
||||
height: isDesktop ? 24 : 16,
|
||||
height: isDesktop ? 40 : 24,
|
||||
),
|
||||
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<StackColors>()!
|
||||
.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<StackColors>()!
|
||||
.textFieldActiveSearchIconRight,
|
||||
),
|
||||
],
|
||||
),
|
||||
_showViewOnlyOption
|
||||
? ViewOnlyRestoreOption(
|
||||
coin: coin,
|
||||
dateController: _dateController,
|
||||
dateChooserFunction:
|
||||
isDesktop ? chooseDesktopDate : chooseDate,
|
||||
)
|
||||
: SeedRestoreOption(
|
||||
coin: coin,
|
||||
dateController: _dateController,
|
||||
pwController: passwordController,
|
||||
pwFocusNode: passwordFocusNode,
|
||||
supportsMnemonicPassphrase: supportsMnemonicPassphrase,
|
||||
dateChooserFunction:
|
||||
isDesktop ? chooseDesktopDate : chooseDate,
|
||||
chooseMnemonicLength: chooseMnemonicLength,
|
||||
lelScanChanged: (value) {
|
||||
enableLelantusScanning = value;
|
||||
},
|
||||
),
|
||||
),
|
||||
body: Container(
|
||||
color: Colors.transparent,
|
||||
child: Column(
|
||||
children: [
|
||||
if (coin is Firo)
|
||||
CheckboxTextButton(
|
||||
label: "Scan for Lelantus transactions",
|
||||
onChanged: (newValue) {
|
||||
setState(() {
|
||||
enableLelantusScanning = newValue ?? true;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (coin is Firo)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
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(
|
||||
"BIP39 passphrase",
|
||||
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<StackColors>()!
|
||||
.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 BIP39 passphrase "
|
||||
"you can enter it here.",
|
||||
style: isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
)
|
||||
: STextStyles.itemSubtitle(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!isDesktop)
|
||||
const Spacer(
|
||||
flex: 3,
|
||||
|
@ -532,3 +318,394 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SeedRestoreOption extends ConsumerStatefulWidget {
|
||||
const SeedRestoreOption({
|
||||
super.key,
|
||||
required this.coin,
|
||||
required this.dateController,
|
||||
required this.pwController,
|
||||
required this.pwFocusNode,
|
||||
required this.supportsMnemonicPassphrase,
|
||||
required this.dateChooserFunction,
|
||||
required this.chooseMnemonicLength,
|
||||
required this.lelScanChanged,
|
||||
});
|
||||
|
||||
final CryptoCurrency coin;
|
||||
final TextEditingController dateController;
|
||||
final TextEditingController pwController;
|
||||
final FocusNode pwFocusNode;
|
||||
final bool supportsMnemonicPassphrase;
|
||||
|
||||
final Future<void> Function() dateChooserFunction;
|
||||
final Future<void> Function() chooseMnemonicLength;
|
||||
final void Function(bool) lelScanChanged;
|
||||
|
||||
@override
|
||||
ConsumerState<SeedRestoreOption> createState() => _SeedRestoreOptionState();
|
||||
}
|
||||
|
||||
class _SeedRestoreOptionState extends ConsumerState<SeedRestoreOption> {
|
||||
bool _hidePassword = true;
|
||||
bool _expandedAdvanced = false;
|
||||
bool _enableLelantusScanning = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final lengths = widget.coin.possibleMnemonicLengths;
|
||||
|
||||
final isMoneroAnd25 = widget.coin is Monero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state == 25;
|
||||
final isWowneroAnd25 = widget.coin is Wownero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state == 25;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25)
|
||||
Text(
|
||||
"Choose start date",
|
||||
style: Util.isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.textDark3,
|
||||
)
|
||||
: STextStyles.smallMed12(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25)
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 16 : 8,
|
||||
),
|
||||
if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25)
|
||||
RestoreFromDatePicker(
|
||||
onTap: widget.dateChooserFunction,
|
||||
controller: widget.dateController,
|
||||
),
|
||||
if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25)
|
||||
RoundedWhiteContainer(
|
||||
child: Center(
|
||||
child: Text(
|
||||
"Choose the date you made the wallet (approximate is fine)",
|
||||
style: Util.isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
)
|
||||
: STextStyles.smallMed12(context).copyWith(
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isMoneroAnd25 || widget.coin is Epiccash || isWowneroAnd25)
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
Text(
|
||||
"Choose recovery phrase length",
|
||||
style: Util.isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color: Theme.of(context).extension<StackColors>()!.textDark3,
|
||||
)
|
||||
: STextStyles.smallMed12(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 16 : 8,
|
||||
),
|
||||
if (Util.isDesktop)
|
||||
DropdownButtonHideUnderline(
|
||||
child: DropdownButton2<int>(
|
||||
value: ref.watch(mnemonicWordCountStateProvider.state).state,
|
||||
items: [
|
||||
...lengths.map(
|
||||
(e) => DropdownMenuItem(
|
||||
value: e,
|
||||
child: Text(
|
||||
"$e words",
|
||||
style: STextStyles.desktopTextMedium(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value is int) {
|
||||
ref.read(mnemonicWordCountStateProvider.state).state = value;
|
||||
}
|
||||
},
|
||||
isExpanded: true,
|
||||
iconStyleData: IconStyleData(
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.chevronDown,
|
||||
width: 12,
|
||||
height: 6,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldActiveSearchIconRight,
|
||||
),
|
||||
),
|
||||
dropdownStyleData: DropdownStyleData(
|
||||
offset: const Offset(0, -10),
|
||||
elevation: 0,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (!Util.isDesktop)
|
||||
MobileMnemonicLengthSelector(
|
||||
chooseMnemonicLength: widget.chooseMnemonicLength,
|
||||
),
|
||||
if (widget.supportsMnemonicPassphrase)
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
if (widget.supportsMnemonicPassphrase)
|
||||
Expandable(
|
||||
onExpandChanged: (state) {
|
||||
setState(() {
|
||||
_expandedAdvanced = 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: Util.isDesktop
|
||||
? STextStyles.desktopTextExtraExtraSmall(
|
||||
context,
|
||||
).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
)
|
||||
: STextStyles.smallMed12(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
SvgPicture.asset(
|
||||
_expandedAdvanced
|
||||
? Assets.svg.chevronUp
|
||||
: Assets.svg.chevronDown,
|
||||
width: 12,
|
||||
height: 6,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldActiveSearchIconRight,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
body: Container(
|
||||
color: Colors.transparent,
|
||||
child: Column(
|
||||
children: [
|
||||
if (widget.coin is Firo)
|
||||
CheckboxTextButton(
|
||||
label: "Scan for Lelantus transactions",
|
||||
onChanged: (newValue) {
|
||||
setState(() {
|
||||
_enableLelantusScanning = newValue ?? true;
|
||||
});
|
||||
|
||||
widget.lelScanChanged(_enableLelantusScanning);
|
||||
},
|
||||
),
|
||||
if (widget.coin is Firo)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
key: const Key("mnemonicPassphraseFieldKey1"),
|
||||
focusNode: widget.pwFocusNode,
|
||||
controller: widget.pwController,
|
||||
style: Util.isDesktop
|
||||
? STextStyles.desktopTextMedium(context).copyWith(
|
||||
height: 2,
|
||||
)
|
||||
: STextStyles.field(context),
|
||||
obscureText: _hidePassword,
|
||||
enableSuggestions: false,
|
||||
autocorrect: false,
|
||||
decoration: standardInputDecoration(
|
||||
"BIP39 passphrase",
|
||||
widget.pwFocusNode,
|
||||
context,
|
||||
).copyWith(
|
||||
suffixIcon: UnconstrainedBox(
|
||||
child: ConditionalParent(
|
||||
condition: Util.isDesktop,
|
||||
builder: (child) => SizedBox(
|
||||
height: 70,
|
||||
child: child,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: Util.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<StackColors>()!
|
||||
.textDark3,
|
||||
width: Util.isDesktop ? 24 : 16,
|
||||
height: Util.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 BIP39 passphrase "
|
||||
"you can enter it here.",
|
||||
style: Util.isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
)
|
||||
: STextStyles.itemSubtitle(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ViewOnlyRestoreOption extends StatefulWidget {
|
||||
const ViewOnlyRestoreOption({
|
||||
super.key,
|
||||
required this.coin,
|
||||
required this.dateController,
|
||||
required this.dateChooserFunction,
|
||||
});
|
||||
|
||||
final CryptoCurrency coin;
|
||||
final TextEditingController dateController;
|
||||
|
||||
final Future<void> Function() dateChooserFunction;
|
||||
|
||||
@override
|
||||
State<ViewOnlyRestoreOption> createState() => _ViewOnlyRestoreOptionState();
|
||||
}
|
||||
|
||||
class _ViewOnlyRestoreOptionState extends State<ViewOnlyRestoreOption> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final showDateOption = widget.coin is CryptonoteCurrency;
|
||||
return Column(
|
||||
children: [
|
||||
if (showDateOption)
|
||||
Text(
|
||||
"Choose start date",
|
||||
style: Util.isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color:
|
||||
Theme.of(context).extension<StackColors>()!.textDark3,
|
||||
)
|
||||
: STextStyles.smallMed12(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
if (showDateOption)
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 16 : 8,
|
||||
),
|
||||
if (showDateOption)
|
||||
RestoreFromDatePicker(
|
||||
onTap: widget.dateChooserFunction,
|
||||
controller: widget.dateController,
|
||||
),
|
||||
if (showDateOption)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (showDateOption)
|
||||
RoundedWhiteContainer(
|
||||
child: Center(
|
||||
child: Text(
|
||||
"Choose the date you made the wallet (approximate is fine)",
|
||||
style: Util.isDesktop
|
||||
? STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
)
|
||||
: STextStyles.smallMed12(context).copyWith(
|
||||
fontSize: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (showDateOption)
|
||||
SizedBox(
|
||||
height: Util.isDesktop ? 24 : 16,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,592 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cs_monero/src/deprecated/get_height_by_date.dart'
|
||||
as cs_monero_deprecated;
|
||||
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:wakelock_plus/wakelock_plus.dart';
|
||||
|
||||
import '../../../models/keys/view_only_wallet_data.dart';
|
||||
import '../../../pages_desktop_specific/desktop_home_view.dart';
|
||||
import '../../../pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart';
|
||||
import '../../../providers/db/main_db_provider.dart';
|
||||
import '../../../providers/global/secure_store_provider.dart';
|
||||
import '../../../providers/providers.dart';
|
||||
import '../../../themes/stack_colors.dart';
|
||||
import '../../../utilities/assets.dart';
|
||||
import '../../../utilities/barcode_scanner_interface.dart';
|
||||
import '../../../utilities/clipboard_interface.dart';
|
||||
import '../../../utilities/constants.dart';
|
||||
import '../../../utilities/text_styles.dart';
|
||||
import '../../../utilities/util.dart';
|
||||
import '../../../wallets/crypto_currency/crypto_currency.dart';
|
||||
import '../../../wallets/crypto_currency/interfaces/electrumx_currency_interface.dart';
|
||||
import '../../../wallets/crypto_currency/intermediate/bip39_hd_currency.dart';
|
||||
import '../../../wallets/crypto_currency/intermediate/cryptonote_currency.dart';
|
||||
import '../../../wallets/isar/models/wallet_info.dart';
|
||||
import '../../../wallets/wallet/impl/epiccash_wallet.dart';
|
||||
import '../../../wallets/wallet/impl/monero_wallet.dart';
|
||||
import '../../../wallets/wallet/impl/wownero_wallet.dart';
|
||||
import '../../../wallets/wallet/wallet.dart';
|
||||
import '../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
|
||||
import '../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../../widgets/desktop/desktop_app_bar.dart';
|
||||
import '../../../widgets/desktop/desktop_scaffold.dart';
|
||||
import '../../../widgets/desktop/primary_button.dart';
|
||||
import '../../../widgets/stack_text_field.dart';
|
||||
import '../../../widgets/toggle.dart';
|
||||
import '../../home_view/home_view.dart';
|
||||
import 'confirm_recovery_dialog.dart';
|
||||
import 'sub_widgets/restore_failed_dialog.dart';
|
||||
import 'sub_widgets/restore_succeeded_dialog.dart';
|
||||
import 'sub_widgets/restoring_dialog.dart';
|
||||
|
||||
class RestoreViewOnlyWalletView extends ConsumerStatefulWidget {
|
||||
const RestoreViewOnlyWalletView({
|
||||
super.key,
|
||||
required this.walletName,
|
||||
required this.coin,
|
||||
required this.restoreFromDate,
|
||||
this.enableLelantusScanning = false,
|
||||
this.barcodeScanner = const BarcodeScannerWrapper(),
|
||||
this.clipboard = const ClipboardWrapper(),
|
||||
});
|
||||
|
||||
static const routeName = "/restoreViewOnlyWallet";
|
||||
|
||||
final String walletName;
|
||||
final CryptoCurrency coin;
|
||||
final DateTime? restoreFromDate;
|
||||
final bool enableLelantusScanning;
|
||||
final BarcodeScannerInterface barcodeScanner;
|
||||
final ClipboardInterface clipboard;
|
||||
|
||||
@override
|
||||
ConsumerState<RestoreViewOnlyWalletView> createState() =>
|
||||
_RestoreViewOnlyWalletViewState();
|
||||
}
|
||||
|
||||
class _RestoreViewOnlyWalletViewState
|
||||
extends ConsumerState<RestoreViewOnlyWalletView> {
|
||||
late final TextEditingController addressController;
|
||||
late final TextEditingController viewKeyController;
|
||||
|
||||
late String _currentDropDownValue;
|
||||
|
||||
bool _enableRestoreButton = false;
|
||||
bool _addressOnly = false;
|
||||
|
||||
bool _buttonLock = false;
|
||||
|
||||
Future<void> _requestRestore() async {
|
||||
if (_buttonLock) return;
|
||||
_buttonLock = true;
|
||||
|
||||
try {
|
||||
if (!Util.isDesktop) {
|
||||
// wait for keyboard to disappear
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(
|
||||
const Duration(milliseconds: 100),
|
||||
);
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
await showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return ConfirmRecoveryDialog(
|
||||
onConfirm: _attemptRestore,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
_buttonLock = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _attemptRestore() async {
|
||||
int height = 0;
|
||||
final Map<String, dynamic> otherDataJson = {
|
||||
WalletInfoKeys.isViewOnlyKey: true,
|
||||
};
|
||||
|
||||
final ViewOnlyWalletType viewOnlyWalletType;
|
||||
if (widget.coin is Bip39HDCurrency) {
|
||||
if (widget.coin is Firo) {
|
||||
otherDataJson.addAll(
|
||||
{
|
||||
WalletInfoKeys.lelantusCoinIsarRescanRequired: false,
|
||||
WalletInfoKeys.enableLelantusScanning:
|
||||
widget.enableLelantusScanning,
|
||||
},
|
||||
);
|
||||
}
|
||||
viewOnlyWalletType = _addressOnly
|
||||
? ViewOnlyWalletType.addressOnly
|
||||
: ViewOnlyWalletType.xPub;
|
||||
} else if (widget.coin is CryptonoteCurrency) {
|
||||
if (widget.restoreFromDate != null) {
|
||||
if (widget.coin is Monero) {
|
||||
height = cs_monero_deprecated.getMoneroHeightByDate(
|
||||
date: widget.restoreFromDate!,
|
||||
);
|
||||
}
|
||||
if (widget.coin is Wownero) {
|
||||
height = cs_monero_deprecated.getWowneroHeightByDate(
|
||||
date: widget.restoreFromDate!,
|
||||
);
|
||||
}
|
||||
if (height < 0) height = 0;
|
||||
}
|
||||
|
||||
viewOnlyWalletType = ViewOnlyWalletType.cryptonote;
|
||||
} else {
|
||||
throw Exception(
|
||||
"Unsupported view only wallet currency type found: ${widget.coin.runtimeType}",
|
||||
);
|
||||
}
|
||||
otherDataJson[WalletInfoKeys.viewOnlyTypeIndexKey] =
|
||||
viewOnlyWalletType.index;
|
||||
|
||||
if (!Platform.isLinux && !Util.isDesktop) await WakelockPlus.enable();
|
||||
|
||||
try {
|
||||
final info = WalletInfo.createNew(
|
||||
coin: widget.coin,
|
||||
name: widget.walletName,
|
||||
restoreHeight: height,
|
||||
otherDataJsonString: jsonEncode(otherDataJson),
|
||||
);
|
||||
|
||||
bool isRestoring = true;
|
||||
// show restoring in progress
|
||||
|
||||
if (mounted) {
|
||||
unawaited(
|
||||
showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: false,
|
||||
builder: (context) {
|
||||
return RestoringDialog(
|
||||
onCancel: () async {
|
||||
isRestoring = false;
|
||||
|
||||
await ref.read(pWallets).deleteWallet(
|
||||
info,
|
||||
ref.read(secureStoreProvider),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final ViewOnlyWalletData viewOnlyData;
|
||||
switch (viewOnlyWalletType) {
|
||||
case ViewOnlyWalletType.cryptonote:
|
||||
if (addressController.text.isEmpty ||
|
||||
viewKeyController.text.isEmpty) {
|
||||
throw Exception("Missing address and/or private view key fields");
|
||||
}
|
||||
viewOnlyData = CryptonoteViewOnlyWalletData(
|
||||
walletId: info.walletId,
|
||||
address: addressController.text,
|
||||
privateViewKey: viewKeyController.text,
|
||||
);
|
||||
break;
|
||||
|
||||
case ViewOnlyWalletType.addressOnly:
|
||||
if (addressController.text.isEmpty) {
|
||||
throw Exception("Address is empty");
|
||||
}
|
||||
viewOnlyData = AddressViewOnlyWalletData(
|
||||
walletId: info.walletId,
|
||||
address: addressController.text,
|
||||
);
|
||||
break;
|
||||
|
||||
case ViewOnlyWalletType.xPub:
|
||||
viewOnlyData = ExtendedKeysViewOnlyWalletData(
|
||||
walletId: info.walletId,
|
||||
xPubs: [
|
||||
XPub(
|
||||
path: _currentDropDownValue,
|
||||
encoded: viewKeyController.text,
|
||||
),
|
||||
],
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
var node = ref
|
||||
.read(nodeServiceChangeNotifierProvider)
|
||||
.getPrimaryNodeFor(currency: widget.coin);
|
||||
|
||||
if (node == null) {
|
||||
node = widget.coin.defaultNode;
|
||||
await ref.read(nodeServiceChangeNotifierProvider).setPrimaryNodeFor(
|
||||
coin: widget.coin,
|
||||
node: node,
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
final wallet = await Wallet.create(
|
||||
walletInfo: info,
|
||||
mainDB: ref.read(mainDBProvider),
|
||||
secureStorageInterface: ref.read(secureStoreProvider),
|
||||
nodeService: ref.read(nodeServiceChangeNotifierProvider),
|
||||
prefs: ref.read(prefsChangeNotifierProvider),
|
||||
viewOnlyData: viewOnlyData,
|
||||
);
|
||||
|
||||
// TODO: extract interface with isRestore param
|
||||
switch (wallet.runtimeType) {
|
||||
case const (EpiccashWallet):
|
||||
await (wallet as EpiccashWallet).init(isRestore: true);
|
||||
break;
|
||||
|
||||
case const (MoneroWallet):
|
||||
await (wallet as MoneroWallet).init(isRestore: true);
|
||||
break;
|
||||
|
||||
case const (WowneroWallet):
|
||||
await (wallet as WowneroWallet).init(isRestore: true);
|
||||
break;
|
||||
|
||||
default:
|
||||
await wallet.init();
|
||||
}
|
||||
|
||||
await wallet.recover(isRescan: false);
|
||||
|
||||
// check if state is still active before continuing
|
||||
if (mounted) {
|
||||
// don't remove this setMnemonicVerified thing
|
||||
await wallet.info.setMnemonicVerified(
|
||||
isar: ref.read(mainDBProvider).isar,
|
||||
);
|
||||
|
||||
ref.read(pWallets).addWallet(wallet);
|
||||
|
||||
if (mounted) {
|
||||
if (Util.isDesktop) {
|
||||
Navigator.of(context).popUntil(
|
||||
ModalRoute.withName(
|
||||
DesktopHomeView.routeName,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
unawaited(
|
||||
Navigator.of(context).pushNamedAndRemoveUntil(
|
||||
HomeView.routeName,
|
||||
(route) => false,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return const RestoreSucceededDialog();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// check if state is still active and restore wasn't cancelled
|
||||
// before continuing
|
||||
if (mounted && isRestoring) {
|
||||
// pop waiting dialog
|
||||
Navigator.pop(context);
|
||||
|
||||
// show restoring wallet failed dialog
|
||||
await showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return RestoreFailedDialog(
|
||||
errorMessage: e.toString(),
|
||||
walletId: info.walletId,
|
||||
walletName: info.name,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (!Platform.isLinux && !Util.isDesktop) await WakelockPlus.disable();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
addressController = TextEditingController();
|
||||
viewKeyController = TextEditingController();
|
||||
|
||||
if (widget.coin is Bip39HDCurrency) {
|
||||
_currentDropDownValue = (widget.coin as Bip39HDCurrency)
|
||||
.supportedHardenedDerivationPaths
|
||||
.last;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
addressController.dispose();
|
||||
viewKeyController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isDesktop = Util.isDesktop;
|
||||
|
||||
final isElectrumX = widget.coin is ElectrumXCurrencyInterface;
|
||||
|
||||
return MasterScaffold(
|
||||
isDesktop: isDesktop,
|
||||
appBar: isDesktop
|
||||
? const DesktopAppBar(
|
||||
isCompactHeight: false,
|
||||
leading: AppBarBackButton(),
|
||||
trailing: ExitToMyStackButton(),
|
||||
)
|
||||
: AppBar(
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () async {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(
|
||||
const Duration(milliseconds: 50),
|
||||
);
|
||||
}
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
body: Container(
|
||||
color: Theme.of(context).extension<StackColors>()!.background,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
return SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: constraints.maxHeight,
|
||||
maxWidth: isDesktop ? 480 : double.infinity,
|
||||
),
|
||||
child: IntrinsicHeight(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
children: [
|
||||
if (isDesktop)
|
||||
const Spacer(
|
||||
flex: 10,
|
||||
),
|
||||
if (!isDesktop)
|
||||
Text(
|
||||
widget.walletName,
|
||||
style: STextStyles.itemSubtitle(context),
|
||||
),
|
||||
SizedBox(
|
||||
height: isDesktop ? 0 : 4,
|
||||
),
|
||||
Text(
|
||||
"Enter view only details",
|
||||
style: isDesktop
|
||||
? STextStyles.desktopH2(context)
|
||||
: STextStyles.pageTitleH1(context),
|
||||
),
|
||||
if (isElectrumX)
|
||||
SizedBox(
|
||||
height: isDesktop ? 24 : 16,
|
||||
),
|
||||
if (isElectrumX)
|
||||
SizedBox(
|
||||
height: isDesktop ? 56 : 48,
|
||||
width: isDesktop ? 490 : null,
|
||||
child: Toggle(
|
||||
key: UniqueKey(),
|
||||
onText: "Extended pub key",
|
||||
offText: "Single address",
|
||||
onColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.popupBG,
|
||||
offColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
isOn: _addressOnly,
|
||||
onValueChanged: (value) {
|
||||
setState(() {
|
||||
_addressOnly = value;
|
||||
});
|
||||
},
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height: isDesktop ? 24 : 16,
|
||||
),
|
||||
if (!isElectrumX || _addressOnly)
|
||||
FullTextField(
|
||||
key: const Key("viewOnlyAddressRestoreFieldKey"),
|
||||
label: "Address",
|
||||
controller: addressController,
|
||||
onChanged: (newValue) {
|
||||
if (isElectrumX) {
|
||||
viewKeyController.text = "";
|
||||
setState(() {
|
||||
_enableRestoreButton = newValue.isNotEmpty;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_enableRestoreButton = newValue.isNotEmpty &&
|
||||
viewKeyController.text.isNotEmpty;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
if (!isElectrumX)
|
||||
SizedBox(
|
||||
height: isDesktop ? 16 : 12,
|
||||
),
|
||||
if (isElectrumX && !_addressOnly)
|
||||
DropdownButtonHideUnderline(
|
||||
child: DropdownButton2<String>(
|
||||
value: _currentDropDownValue,
|
||||
items: [
|
||||
...(widget.coin as Bip39HDCurrency)
|
||||
.supportedHardenedDerivationPaths
|
||||
.map(
|
||||
(e) => DropdownMenuItem(
|
||||
value: e,
|
||||
child: Text(
|
||||
e,
|
||||
style: STextStyles.w500_14(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value is String) {
|
||||
setState(() {
|
||||
_currentDropDownValue = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
isExpanded: true,
|
||||
buttonStyleData: ButtonStyleData(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
iconStyleData: IconStyleData(
|
||||
icon: Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: SvgPicture.asset(
|
||||
Assets.svg.chevronDown,
|
||||
width: 12,
|
||||
height: 6,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldActiveSearchIconRight,
|
||||
),
|
||||
),
|
||||
),
|
||||
dropdownStyleData: DropdownStyleData(
|
||||
offset: const Offset(0, -10),
|
||||
elevation: 0,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isElectrumX && !_addressOnly)
|
||||
SizedBox(
|
||||
height: isDesktop ? 16 : 12,
|
||||
),
|
||||
if (!isElectrumX || !_addressOnly)
|
||||
FullTextField(
|
||||
key: const Key("viewOnlyKeyRestoreFieldKey"),
|
||||
label:
|
||||
"${isElectrumX ? "Extended" : "Private View"} Key",
|
||||
controller: viewKeyController,
|
||||
onChanged: (value) {
|
||||
if (isElectrumX) {
|
||||
addressController.text = "";
|
||||
setState(() {
|
||||
_enableRestoreButton = value.isNotEmpty;
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_enableRestoreButton = value.isNotEmpty &&
|
||||
addressController.text.isNotEmpty;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
if (!isDesktop) const Spacer(),
|
||||
SizedBox(
|
||||
height: isDesktop ? 24 : 16,
|
||||
),
|
||||
PrimaryButton(
|
||||
enabled: _enableRestoreButton,
|
||||
onPressed: _requestRestore,
|
||||
width: isDesktop ? 480 : null,
|
||||
label: "Restore",
|
||||
),
|
||||
if (isDesktop)
|
||||
const Spacer(
|
||||
flex: 15,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -16,10 +16,11 @@ import 'dart:math';
|
|||
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:bip39/src/wordlists/english.dart' as bip39wordlist;
|
||||
import 'package:cs_monero/cs_monero.dart' as lib_monero;
|
||||
import 'package:cs_monero/src/deprecated/get_height_by_date.dart'
|
||||
as cs_monero_deprecated;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_libmonero/monero/monero.dart' as libxmr;
|
||||
import 'package:flutter_libmonero/wownero/wownero.dart' as libwow;
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
|
@ -86,7 +87,7 @@ class RestoreWalletView extends ConsumerStatefulWidget {
|
|||
final CryptoCurrency coin;
|
||||
final String mnemonicPassphrase;
|
||||
final int seedWordsLength;
|
||||
final DateTime restoreFromDate;
|
||||
final DateTime? restoreFromDate;
|
||||
final bool enableLelantusScanning;
|
||||
|
||||
final BarcodeScannerInterface barcodeScanner;
|
||||
|
@ -181,7 +182,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
if (widget.coin is Monero) {
|
||||
switch (widget.seedWordsLength) {
|
||||
case 25:
|
||||
return libxmr.monero.getMoneroWordList("English").contains(word);
|
||||
return lib_monero.getMoneroWordList("English").contains(word);
|
||||
case 16:
|
||||
return Monero.sixteenWordsWordList.contains(word);
|
||||
default:
|
||||
|
@ -189,7 +190,7 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
}
|
||||
}
|
||||
if (widget.coin is Wownero) {
|
||||
final wowneroWordList = libwow.wownero.getWowneroWordList(
|
||||
final wowneroWordList = lib_monero.getWowneroWordList(
|
||||
"English",
|
||||
seedWordsLength: widget.seedWordsLength,
|
||||
);
|
||||
|
@ -219,29 +220,35 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
int height = 0;
|
||||
String? otherDataJsonString;
|
||||
|
||||
if (widget.coin is Monero) {
|
||||
height = libxmr.monero.getHeigthByDate(date: widget.restoreFromDate);
|
||||
} else if (widget.coin is Wownero) {
|
||||
height = libwow.wownero.getHeightByDate(date: widget.restoreFromDate);
|
||||
if (widget.restoreFromDate != null) {
|
||||
if (widget.coin is Monero) {
|
||||
height = cs_monero_deprecated.getMoneroHeightByDate(
|
||||
date: widget.restoreFromDate!,
|
||||
);
|
||||
}
|
||||
if (widget.coin is Wownero) {
|
||||
height = cs_monero_deprecated.getWowneroHeightByDate(
|
||||
date: widget.restoreFromDate!,
|
||||
);
|
||||
}
|
||||
if (height < 0) {
|
||||
height = 0;
|
||||
}
|
||||
}
|
||||
// todo: wait until this implemented
|
||||
// else if (widget.coin is Wownero) {
|
||||
// height = wownero.getHeightByDate(date: widget.restoreFromDate);
|
||||
// }
|
||||
|
||||
// TODO: make more robust estimate of date maybe using https://explorer.epic.tech/api-index
|
||||
if (widget.coin is Epiccash) {
|
||||
final int secondsSinceEpoch =
|
||||
widget.restoreFromDate.millisecondsSinceEpoch ~/ 1000;
|
||||
const int epicCashFirstBlock = 1565370278;
|
||||
const double overestimateSecondsPerBlock = 61;
|
||||
final int chosenSeconds = secondsSinceEpoch - epicCashFirstBlock;
|
||||
final int approximateHeight =
|
||||
chosenSeconds ~/ overestimateSecondsPerBlock;
|
||||
//todo: check if print needed
|
||||
// debugPrint(
|
||||
// "approximate height: $approximateHeight chosen_seconds: $chosenSeconds");
|
||||
height = approximateHeight;
|
||||
if (widget.restoreFromDate != null) {
|
||||
final int secondsSinceEpoch =
|
||||
widget.restoreFromDate!.millisecondsSinceEpoch ~/ 1000;
|
||||
const int epicCashFirstBlock = 1565370278;
|
||||
const double overestimateSecondsPerBlock = 61;
|
||||
final int chosenSeconds = secondsSinceEpoch - epicCashFirstBlock;
|
||||
final int approximateHeight =
|
||||
chosenSeconds ~/ overestimateSecondsPerBlock;
|
||||
|
||||
height = approximateHeight;
|
||||
}
|
||||
if (height < 0) {
|
||||
height = 0;
|
||||
}
|
||||
|
@ -648,16 +655,18 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
const Duration(milliseconds: 100),
|
||||
);
|
||||
|
||||
await showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return ConfirmRecoveryDialog(
|
||||
onConfirm: attemptRestore,
|
||||
);
|
||||
},
|
||||
);
|
||||
if (mounted) {
|
||||
await showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return ConfirmRecoveryDialog(
|
||||
onConfirm: attemptRestore,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -9,13 +9,17 @@
|
|||
*/
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cs_monero/src/deprecated/get_height_by_date.dart'
|
||||
as cs_monero_deprecated;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import '../../../models/keys/view_only_wallet_data.dart';
|
||||
import '../../../notifications/show_flush_bar.dart';
|
||||
import '../../../pages_desktop_specific/desktop_home_view.dart';
|
||||
import '../../../pages_desktop_specific/my_stack_view/exit_to_my_stack_button.dart';
|
||||
|
@ -25,14 +29,25 @@ import '../../../providers/providers.dart';
|
|||
import '../../../themes/stack_colors.dart';
|
||||
import '../../../utilities/assets.dart';
|
||||
import '../../../utilities/constants.dart';
|
||||
import '../../../utilities/logger.dart';
|
||||
import '../../../utilities/show_loading.dart';
|
||||
import '../../../utilities/text_styles.dart';
|
||||
import '../../../utilities/util.dart';
|
||||
import '../../../wallets/crypto_currency/coins/ethereum.dart';
|
||||
import '../../../wallets/crypto_currency/crypto_currency.dart';
|
||||
import '../../../wallets/crypto_currency/intermediate/bip39_hd_currency.dart';
|
||||
import '../../../wallets/isar/models/wallet_info.dart';
|
||||
import '../../../wallets/isar/providers/wallet_info_provider.dart';
|
||||
import '../../../wallets/wallet/impl/epiccash_wallet.dart';
|
||||
import '../../../wallets/wallet/impl/monero_wallet.dart';
|
||||
import '../../../wallets/wallet/impl/wownero_wallet.dart';
|
||||
import '../../../wallets/wallet/intermediate/lib_monero_wallet.dart';
|
||||
import '../../../wallets/wallet/wallet.dart';
|
||||
import '../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
|
||||
import '../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
|
||||
import '../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../../widgets/desktop/desktop_app_bar.dart';
|
||||
import '../../../widgets/desktop/desktop_scaffold.dart';
|
||||
import '../../../widgets/stack_dialog.dart';
|
||||
import '../../home_view/home_view.dart';
|
||||
import '../add_token_view/edit_wallet_tokens_view.dart';
|
||||
import '../new_wallet_options/new_wallet_options_view.dart';
|
||||
|
@ -64,46 +79,25 @@ class _VerifyRecoveryPhraseViewState
|
|||
extends ConsumerState<VerifyRecoveryPhraseView>
|
||||
// with WidgetsBindingObserver
|
||||
{
|
||||
late Wallet _wallet;
|
||||
late String _walletId;
|
||||
late CryptoCurrency _coin;
|
||||
late List<String> _mnemonic;
|
||||
late final bool isDesktop;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_wallet = widget.wallet;
|
||||
_walletId = widget.wallet.walletId;
|
||||
_coin = widget.wallet.cryptoCurrency;
|
||||
_mnemonic = widget.mnemonic;
|
||||
isDesktop = Util.isDesktop;
|
||||
// WidgetsBinding.instance?.addObserver(this);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
dispose() {
|
||||
// WidgetsBinding.instance?.removeObserver(this);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// @override
|
||||
// void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
// switch (state) {
|
||||
// case AppLifecycleState.inactive:
|
||||
// debugPrint(
|
||||
// "VerifyRecoveryPhraseView ========================= Inactive");
|
||||
// break;
|
||||
// case AppLifecycleState.paused:
|
||||
// debugPrint("VerifyRecoveryPhraseView ========================= Paused");
|
||||
// break;
|
||||
// case AppLifecycleState.resumed:
|
||||
// debugPrint(
|
||||
// "VerifyRecoveryPhraseView ========================= Resumed");
|
||||
// break;
|
||||
// case AppLifecycleState.detached:
|
||||
// debugPrint(
|
||||
// "VerifyRecoveryPhraseView ========================= Detached");
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
Future<bool> _verifyMnemonicPassphrase() async {
|
||||
final result = await showDialog<String?>(
|
||||
context: context,
|
||||
|
@ -113,6 +107,157 @@ class _VerifyRecoveryPhraseViewState
|
|||
return result == "verified";
|
||||
}
|
||||
|
||||
Future<void> _convertToViewOnly() async {
|
||||
int height = 0;
|
||||
final Map<String, dynamic> otherDataJson = {
|
||||
WalletInfoKeys.isViewOnlyKey: true,
|
||||
};
|
||||
|
||||
final ViewOnlyWalletType viewOnlyWalletType;
|
||||
if (widget.wallet is ExtendedKeysInterface) {
|
||||
if (widget.wallet.cryptoCurrency is Firo) {
|
||||
otherDataJson.addAll(
|
||||
{
|
||||
WalletInfoKeys.lelantusCoinIsarRescanRequired: false,
|
||||
WalletInfoKeys.enableLelantusScanning: false,
|
||||
},
|
||||
);
|
||||
}
|
||||
viewOnlyWalletType = ViewOnlyWalletType.xPub;
|
||||
} else if (widget.wallet is LibMoneroWallet) {
|
||||
if (widget.wallet.cryptoCurrency is Monero) {
|
||||
height = cs_monero_deprecated.getMoneroHeightByDate(
|
||||
date: DateTime.now().subtract(const Duration(days: 7)),
|
||||
);
|
||||
}
|
||||
if (widget.wallet.cryptoCurrency is Wownero) {
|
||||
height = cs_monero_deprecated.getWowneroHeightByDate(
|
||||
date: DateTime.now().subtract(const Duration(days: 7)),
|
||||
);
|
||||
}
|
||||
if (height < 0) height = 0;
|
||||
|
||||
viewOnlyWalletType = ViewOnlyWalletType.cryptonote;
|
||||
} else {
|
||||
throw Exception(
|
||||
"Unsupported view only wallet type found: ${widget.wallet.runtimeType}",
|
||||
);
|
||||
}
|
||||
|
||||
otherDataJson[WalletInfoKeys.viewOnlyTypeIndexKey] =
|
||||
viewOnlyWalletType.index;
|
||||
|
||||
final voInfo = WalletInfo.createNew(
|
||||
coin: _coin,
|
||||
name: widget.wallet.info.name,
|
||||
restoreHeight: height,
|
||||
otherDataJsonString: jsonEncode(otherDataJson),
|
||||
);
|
||||
|
||||
final ViewOnlyWalletData viewOnlyData;
|
||||
if (widget.wallet is ExtendedKeysInterface) {
|
||||
final extendedKeyInfo =
|
||||
await (widget.wallet as ExtendedKeysInterface).getXPubs();
|
||||
final testPath = (_coin as Bip39HDCurrency).constructDerivePath(
|
||||
derivePathType: (_coin as Bip39HDCurrency).defaultDerivePathType,
|
||||
chain: 0,
|
||||
index: 0,
|
||||
);
|
||||
|
||||
XPub? xPub;
|
||||
for (final pub in extendedKeyInfo.xpubs) {
|
||||
if (testPath.startsWith(pub.path)) {
|
||||
xPub = pub;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (xPub == null) {
|
||||
throw Exception("Default derivation path not matched in xPubs");
|
||||
}
|
||||
|
||||
viewOnlyData = ExtendedKeysViewOnlyWalletData(
|
||||
walletId: voInfo.walletId,
|
||||
xPubs: [xPub],
|
||||
);
|
||||
} else if (widget.wallet is LibMoneroWallet) {
|
||||
final w = widget.wallet as LibMoneroWallet;
|
||||
|
||||
final info = await w
|
||||
.hackToCreateNewViewOnlyWalletDataFromNewlyCreatedWalletThisFunctionShouldNotBeCalledUnlessYouKnowWhatYouAreDoing();
|
||||
final address = info.$1;
|
||||
final privateViewKey = info.$2;
|
||||
|
||||
await w.exit();
|
||||
|
||||
viewOnlyData = CryptonoteViewOnlyWalletData(
|
||||
walletId: voInfo.walletId,
|
||||
address: address,
|
||||
privateViewKey: privateViewKey,
|
||||
);
|
||||
} else {
|
||||
throw Exception(
|
||||
"Unsupported view only wallet type found: ${widget.wallet.runtimeType}",
|
||||
);
|
||||
}
|
||||
|
||||
final voWallet = await Wallet.create(
|
||||
walletInfo: voInfo,
|
||||
mainDB: ref.read(mainDBProvider),
|
||||
secureStorageInterface: ref.read(secureStoreProvider),
|
||||
nodeService: ref.read(nodeServiceChangeNotifierProvider),
|
||||
prefs: ref.read(prefsChangeNotifierProvider),
|
||||
viewOnlyData: viewOnlyData,
|
||||
);
|
||||
|
||||
try {
|
||||
// TODO: extract interface with isRestore param
|
||||
switch (voWallet.runtimeType) {
|
||||
case const (EpiccashWallet):
|
||||
await (voWallet as EpiccashWallet).init(isRestore: true);
|
||||
break;
|
||||
|
||||
case const (MoneroWallet):
|
||||
await (voWallet as MoneroWallet).init(isRestore: true);
|
||||
break;
|
||||
|
||||
case const (WowneroWallet):
|
||||
await (voWallet as WowneroWallet).init(isRestore: true);
|
||||
break;
|
||||
|
||||
default:
|
||||
await voWallet.init();
|
||||
}
|
||||
|
||||
await voWallet.recover(isRescan: false);
|
||||
|
||||
// don't remove this setMnemonicVerified thing
|
||||
await voWallet.info.setMnemonicVerified(
|
||||
isar: ref.read(mainDBProvider).isar,
|
||||
);
|
||||
|
||||
ref.read(pWallets).addWallet(voWallet);
|
||||
|
||||
await voWallet.exit();
|
||||
|
||||
await ref.read(pWallets).deleteWallet(
|
||||
widget.wallet.info,
|
||||
ref.read(secureStoreProvider),
|
||||
);
|
||||
} catch (e) {
|
||||
await ref.read(pWallets).deleteWallet(
|
||||
widget.wallet.info,
|
||||
ref.read(secureStoreProvider),
|
||||
);
|
||||
await ref.read(pWallets).deleteWallet(
|
||||
voWallet.info,
|
||||
ref.read(secureStoreProvider),
|
||||
);
|
||||
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _continue(bool isMatch) async {
|
||||
if (isMatch) {
|
||||
if (ref.read(pNewWalletOptions) != null &&
|
||||
|
@ -124,11 +269,11 @@ class _VerifyRecoveryPhraseViewState
|
|||
}
|
||||
}
|
||||
|
||||
await ref.read(pWalletInfo(_wallet.walletId)).setMnemonicVerified(
|
||||
await ref.read(pWalletInfo(widget.wallet.walletId)).setMnemonicVerified(
|
||||
isar: ref.read(mainDBProvider).isar,
|
||||
);
|
||||
|
||||
ref.read(pWallets).addWallet(_wallet);
|
||||
ref.read(pWallets).addWallet(widget.wallet);
|
||||
|
||||
final isCreateSpecialEthWallet =
|
||||
ref.read(createSpecialEthWalletRoutingFlag);
|
||||
|
@ -142,6 +287,51 @@ class _VerifyRecoveryPhraseViewState
|
|||
.state;
|
||||
}
|
||||
|
||||
if (mounted &&
|
||||
ref.read(pNewWalletOptions)?.convertToViewOnly == true &&
|
||||
widget.wallet is ViewOnlyOptionInterface) {
|
||||
try {
|
||||
Exception? ex;
|
||||
await showLoading(
|
||||
whileFuture: _convertToViewOnly(),
|
||||
context: context,
|
||||
message: "Converting to view only wallet",
|
||||
rootNavigator: Util.isDesktop,
|
||||
onException: (e) {
|
||||
ex = e;
|
||||
},
|
||||
);
|
||||
|
||||
if (ex != null) {
|
||||
throw ex!;
|
||||
}
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"$e\n$s",
|
||||
level: LogLevel.Fatal,
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => StackOkDialog(
|
||||
title: e.toString(),
|
||||
desktopPopRootNavigator: Util.isDesktop,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
Navigator.of(context).popUntil(
|
||||
ModalRoute.withName(
|
||||
NewWalletRecoveryPhraseView.routeName,
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
if (isDesktop) {
|
||||
if (isCreateSpecialEthWallet) {
|
||||
|
@ -156,7 +346,7 @@ class _VerifyRecoveryPhraseViewState
|
|||
DesktopHomeView.routeName,
|
||||
),
|
||||
);
|
||||
if (widget.wallet.info.coin is Ethereum) {
|
||||
if (_coin is Ethereum) {
|
||||
unawaited(
|
||||
Navigator.of(context).pushNamed(
|
||||
EditWalletTokensView.routeName,
|
||||
|
@ -179,7 +369,7 @@ class _VerifyRecoveryPhraseViewState
|
|||
(route) => false,
|
||||
),
|
||||
);
|
||||
if (widget.wallet.info.coin is Ethereum) {
|
||||
if (_coin is Ethereum) {
|
||||
unawaited(
|
||||
Navigator.of(context).pushNamed(
|
||||
EditWalletTokensView.routeName,
|
||||
|
@ -269,7 +459,7 @@ class _VerifyRecoveryPhraseViewState
|
|||
|
||||
Future<void> delete() async {
|
||||
await ref.read(pWallets).deleteWallet(
|
||||
_wallet.info,
|
||||
widget.wallet.info,
|
||||
ref.read(secureStoreProvider),
|
||||
);
|
||||
}
|
||||
|
@ -299,7 +489,7 @@ class _VerifyRecoveryPhraseViewState
|
|||
trailing: ExitToMyStackButton(
|
||||
onPressed: () async {
|
||||
await delete();
|
||||
if (mounted) {
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).popUntil(
|
||||
ModalRoute.withName(DesktopHomeView.routeName),
|
||||
);
|
||||
|
|
|
@ -66,6 +66,62 @@ class _NewContactAddressEntryFormState
|
|||
|
||||
List<CryptoCurrency> coins = [];
|
||||
|
||||
void _onQrTapped() async {
|
||||
try {
|
||||
// ref
|
||||
// .read(shouldShowLockscreenOnResumeStateProvider
|
||||
// .state)
|
||||
// .state = false;
|
||||
final qrResult = await widget.barcodeScanner.scan();
|
||||
|
||||
// Future<void>.delayed(
|
||||
// const Duration(seconds: 2),
|
||||
// () => ref
|
||||
// .read(
|
||||
// shouldShowLockscreenOnResumeStateProvider
|
||||
// .state)
|
||||
// .state = true,
|
||||
// );
|
||||
|
||||
final paymentData = AddressUtils.parsePaymentUri(
|
||||
qrResult.rawContent,
|
||||
logging: Logging.instance,
|
||||
);
|
||||
|
||||
if (paymentData != null) {
|
||||
addressController.text = paymentData.address;
|
||||
ref.read(addressEntryDataProvider(widget.id)).address =
|
||||
addressController.text.isEmpty ? null : addressController.text;
|
||||
|
||||
addressLabelController.text =
|
||||
paymentData.label ?? addressLabelController.text;
|
||||
ref.read(addressEntryDataProvider(widget.id)).addressLabel =
|
||||
addressLabelController.text.isEmpty
|
||||
? null
|
||||
: addressLabelController.text;
|
||||
|
||||
// now check for non standard encoded basic address
|
||||
} else if (ref.read(addressEntryDataProvider(widget.id)).coin != null) {
|
||||
if (ref.read(addressEntryDataProvider(widget.id)).coin!.validateAddress(
|
||||
qrResult.rawContent,
|
||||
)) {
|
||||
addressController.text = qrResult.rawContent;
|
||||
ref.read(addressEntryDataProvider(widget.id)).address =
|
||||
qrResult.rawContent;
|
||||
}
|
||||
}
|
||||
} on PlatformException catch (e, s) {
|
||||
// ref
|
||||
// .read(shouldShowLockscreenOnResumeStateProvider
|
||||
// .state)
|
||||
// .state = true;
|
||||
Logging.instance.log(
|
||||
"Failed to get camera permissions to scan address qr code: $e\n$s",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
addressLabelController = TextEditingController()
|
||||
|
@ -404,71 +460,7 @@ class _NewContactAddressEntryFormState
|
|||
null)
|
||||
TextFieldIconButton(
|
||||
key: const Key("addAddressBookEntryScanQrButtonKey"),
|
||||
onTap: () async {
|
||||
try {
|
||||
// ref
|
||||
// .read(shouldShowLockscreenOnResumeStateProvider
|
||||
// .state)
|
||||
// .state = false;
|
||||
final qrResult = await widget.barcodeScanner.scan();
|
||||
|
||||
// Future<void>.delayed(
|
||||
// const Duration(seconds: 2),
|
||||
// () => ref
|
||||
// .read(
|
||||
// shouldShowLockscreenOnResumeStateProvider
|
||||
// .state)
|
||||
// .state = true,
|
||||
// );
|
||||
|
||||
final results =
|
||||
AddressUtils.parseUri(qrResult.rawContent);
|
||||
if (results.isNotEmpty) {
|
||||
addressController.text = results["address"] ?? "";
|
||||
ref
|
||||
.read(addressEntryDataProvider(widget.id))
|
||||
.address =
|
||||
addressController.text.isEmpty
|
||||
? null
|
||||
: addressController.text;
|
||||
|
||||
addressLabelController.text = results["label"] ??
|
||||
addressLabelController.text;
|
||||
ref
|
||||
.read(addressEntryDataProvider(widget.id))
|
||||
.addressLabel =
|
||||
addressLabelController.text.isEmpty
|
||||
? null
|
||||
: addressLabelController.text;
|
||||
|
||||
// now check for non standard encoded basic address
|
||||
} else if (ref
|
||||
.read(addressEntryDataProvider(widget.id))
|
||||
.coin !=
|
||||
null) {
|
||||
if (ref
|
||||
.read(addressEntryDataProvider(widget.id))
|
||||
.coin!
|
||||
.validateAddress(
|
||||
qrResult.rawContent,
|
||||
)) {
|
||||
addressController.text = qrResult.rawContent;
|
||||
ref
|
||||
.read(addressEntryDataProvider(widget.id))
|
||||
.address = qrResult.rawContent;
|
||||
}
|
||||
}
|
||||
} on PlatformException catch (e, s) {
|
||||
// ref
|
||||
// .read(shouldShowLockscreenOnResumeStateProvider
|
||||
// .state)
|
||||
// .state = true;
|
||||
Logging.instance.log(
|
||||
"Failed to get camera permissions to scan address qr code: $e\n$s",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
}
|
||||
},
|
||||
onTap: _onQrTapped,
|
||||
child: const QrCodeIcon(),
|
||||
),
|
||||
const SizedBox(
|
||||
|
|
|
@ -713,6 +713,60 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
}
|
||||
}
|
||||
|
||||
void _onQrTapped() async {
|
||||
try {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(
|
||||
const Duration(milliseconds: 75),
|
||||
);
|
||||
}
|
||||
|
||||
final qrResult = await scanner.scan();
|
||||
|
||||
Logging.instance.log(
|
||||
"qrResult content: ${qrResult.rawContent}",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
final paymentData = AddressUtils.parsePaymentUri(
|
||||
qrResult.rawContent,
|
||||
logging: Logging.instance,
|
||||
);
|
||||
|
||||
Logging.instance.log(
|
||||
"qrResult parsed: $paymentData",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
if (paymentData != null) {
|
||||
// auto fill address
|
||||
_address = paymentData.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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_receiveAddressController = TextEditingController();
|
||||
|
@ -1375,63 +1429,7 @@ class _BuyFormState extends ConsumerState<BuyForm> {
|
|||
!isDesktop)
|
||||
TextFieldIconButton(
|
||||
key: const Key("buyViewScanQrButtonKey"),
|
||||
onTap: () async {
|
||||
try {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.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,
|
||||
);
|
||||
}
|
||||
},
|
||||
onTap: _onQrTapped,
|
||||
child: const QrCodeIcon(),
|
||||
),
|
||||
],
|
||||
|
|
127
lib/pages/churning/churn_error_dialog.dart
Normal file
127
lib/pages/churning/churn_error_dialog.dart
Normal file
|
@ -0,0 +1,127 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../providers/churning/churning_service_provider.dart';
|
||||
import '../../utilities/text_styles.dart';
|
||||
import '../../utilities/util.dart';
|
||||
import '../../widgets/conditional_parent.dart';
|
||||
import '../../widgets/desktop/desktop_dialog.dart';
|
||||
import '../../widgets/desktop/primary_button.dart';
|
||||
import '../../widgets/desktop/secondary_button.dart';
|
||||
import '../../widgets/stack_dialog.dart';
|
||||
|
||||
class ChurnErrorDialog extends ConsumerWidget {
|
||||
const ChurnErrorDialog({
|
||||
super.key,
|
||||
required this.error,
|
||||
required this.walletId,
|
||||
});
|
||||
|
||||
final String error;
|
||||
final String walletId;
|
||||
|
||||
static const errorTitle = "An error occurred";
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return ConditionalParent(
|
||||
condition: Util.isDesktop,
|
||||
builder: (child) => DesktopDialog(
|
||||
maxHeight: double.infinity,
|
||||
child: child,
|
||||
),
|
||||
child: ConditionalParent(
|
||||
condition: !Util.isDesktop,
|
||||
builder: (child) => StackDialogBase(
|
||||
child: child,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Util.isDesktop
|
||||
? Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 32, top: 32),
|
||||
child: Text(
|
||||
errorTitle,
|
||||
style: STextStyles.desktopH2(context),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
: Text(
|
||||
errorTitle,
|
||||
style: STextStyles.pageTitleH2(context),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Padding(
|
||||
padding: Util.isDesktop
|
||||
? const EdgeInsets.all(32)
|
||||
: const EdgeInsets.all(20),
|
||||
child: Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: SelectableText(
|
||||
error.startsWith("Exception:")
|
||||
? error.substring(10).trim()
|
||||
: error,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Padding(
|
||||
padding: Util.isDesktop
|
||||
? const EdgeInsets.all(32)
|
||||
: const EdgeInsets.all(20),
|
||||
child: Text(
|
||||
"Stop churning or try and continue?",
|
||||
style: Util.isDesktop
|
||||
? STextStyles.w600_14(context)
|
||||
: STextStyles.w600_14(context),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
left: Util.isDesktop ? 32 : 20,
|
||||
bottom: Util.isDesktop ? 32 : 20,
|
||||
right: Util.isDesktop ? 32 : 20,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SecondaryButton(
|
||||
label: "Stop",
|
||||
onPressed: () {
|
||||
ref.read(pChurningService(walletId)).stopChurning();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
width: Util.isDesktop ? 20 : 16,
|
||||
),
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
label: "Continue",
|
||||
onPressed: () {
|
||||
ref.read(pChurningService(walletId)).unpause();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
264
lib/pages/churning/churning_progress_view.dart
Normal file
264
lib/pages/churning/churning_progress_view.dart
Normal file
|
@ -0,0 +1,264 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
|
||||
import '../../providers/churning/churning_service_provider.dart';
|
||||
import '../../themes/stack_colors.dart';
|
||||
import '../../utilities/assets.dart';
|
||||
import '../../utilities/text_styles.dart';
|
||||
import '../../widgets/background.dart';
|
||||
import '../../widgets/churning/churn_progress_item.dart';
|
||||
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../widgets/desktop/primary_button.dart';
|
||||
import '../../widgets/desktop/secondary_button.dart';
|
||||
import '../../widgets/monero_chan_dance.dart';
|
||||
import '../../widgets/rounded_container.dart';
|
||||
import '../../widgets/stack_dialog.dart';
|
||||
import 'churn_error_dialog.dart';
|
||||
|
||||
class ChurningProgressView extends ConsumerStatefulWidget {
|
||||
const ChurningProgressView({
|
||||
super.key,
|
||||
required this.walletId,
|
||||
});
|
||||
|
||||
static const routeName = "/churningProgressView";
|
||||
|
||||
final String walletId;
|
||||
@override
|
||||
ConsumerState<ChurningProgressView> createState() =>
|
||||
_ChurningProgressViewState();
|
||||
}
|
||||
|
||||
class _ChurningProgressViewState extends ConsumerState<ChurningProgressView> {
|
||||
Future<bool> _requestAndProcessCancel() async {
|
||||
final shouldCancel = await showDialog<bool?>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (_) => StackDialog(
|
||||
title: "Cancel churning?",
|
||||
leftButton: SecondaryButton(
|
||||
label: "No",
|
||||
buttonHeight: null,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
),
|
||||
rightButton: PrimaryButton(
|
||||
label: "Yes",
|
||||
buttonHeight: null,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (shouldCancel == true && mounted) {
|
||||
ref.read(pChurningService(widget.walletId)).stopChurning();
|
||||
|
||||
await WakelockPlus.disable();
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) ref.read(pChurningService(widget.walletId)).churn();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WakelockPlus.disable();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bool _succeeded = ref.watch(
|
||||
pChurningService(widget.walletId).select((s) => s.done),
|
||||
);
|
||||
|
||||
final int _roundsCompleted = ref.watch(
|
||||
pChurningService(widget.walletId).select((s) => s.roundsCompleted),
|
||||
);
|
||||
|
||||
WakelockPlus.enable();
|
||||
|
||||
ref.listen(
|
||||
pChurningService(widget.walletId).select((s) => s.lastSeenError),
|
||||
(p, n) {
|
||||
if (!ref.read(pChurningService(widget.walletId)).ignoreErrors &&
|
||||
n != null) {
|
||||
if (context.mounted) {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => ChurnErrorDialog(
|
||||
error: n.toString(),
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
return await _requestAndProcessCancel();
|
||||
},
|
||||
child: Background(
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () async {
|
||||
if (await _requestAndProcessCancel()) {
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
title: Text(
|
||||
"Churning progress",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
titleSpacing: 0,
|
||||
),
|
||||
body: LayoutBuilder(
|
||||
builder: (builderContext, 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: [
|
||||
if (_roundsCompleted == 0)
|
||||
RoundedContainer(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.snackBarBackError,
|
||||
child: Text(
|
||||
"Do not close this window. If you exit, "
|
||||
"the process will be canceled.",
|
||||
style:
|
||||
STextStyles.smallMed14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.snackBarTextError,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
if (_roundsCompleted > 0)
|
||||
RoundedContainer(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.snackBarBackInfo,
|
||||
child: Text(
|
||||
"Churning rounds completed: $_roundsCompleted",
|
||||
style: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.snackBarTextInfo,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
const MoneroChanDance(),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
ProgressItem(
|
||||
iconAsset: Assets.svg.alertCircle,
|
||||
label: "Waiting for balance to unlock ${ref.watch(
|
||||
pChurningService(widget.walletId)
|
||||
.select((s) => s.confirmsInfo),
|
||||
) ?? ""}",
|
||||
status: ref.watch(
|
||||
pChurningService(widget.walletId)
|
||||
.select((s) => s.waitingForUnlockedBalance),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
ProgressItem(
|
||||
iconAsset: Assets.svg.churn,
|
||||
label: "Creating churn transaction",
|
||||
status: ref.watch(
|
||||
pChurningService(widget.walletId)
|
||||
.select((s) => s.makingChurnTransaction),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
ProgressItem(
|
||||
iconAsset: Assets.svg.checkCircle,
|
||||
label: "Complete",
|
||||
status: ref.watch(
|
||||
pChurningService(widget.walletId)
|
||||
.select((s) => s.completedStatus),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
if (_succeeded)
|
||||
PrimaryButton(
|
||||
label: "Churn again",
|
||||
onPressed: ref
|
||||
.read(pChurningService(widget.walletId))
|
||||
.churn,
|
||||
),
|
||||
if (_succeeded)
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
SecondaryButton(
|
||||
label: "Cancel",
|
||||
onPressed: () async {
|
||||
if (await _requestAndProcessCancel()) {
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
159
lib/pages/churning/churning_rounds_selection_sheet.dart
Normal file
159
lib/pages/churning/churning_rounds_selection_sheet.dart
Normal file
|
@ -0,0 +1,159 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
|
||||
import '../../themes/stack_colors.dart';
|
||||
import '../../utilities/constants.dart';
|
||||
import '../../utilities/extensions/extensions.dart';
|
||||
import '../../utilities/text_styles.dart';
|
||||
|
||||
enum ChurnOption {
|
||||
continuous,
|
||||
custom;
|
||||
}
|
||||
|
||||
class ChurnRoundCountSelectSheet extends HookWidget {
|
||||
const ChurnRoundCountSelectSheet({
|
||||
super.key,
|
||||
required this.currentOption,
|
||||
});
|
||||
|
||||
final ChurnOption currentOption;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final option = useState(currentOption);
|
||||
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
Navigator.of(context).pop(option.value);
|
||||
return false;
|
||||
},
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).extension<StackColors>()!.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<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
width: 60,
|
||||
height: 4,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 36,
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Rounds of churn",
|
||||
style: STextStyles.pageTitleH2(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
for (int i = 0; i < ChurnOption.values.length; i++)
|
||||
Column(
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
option.value = ChurnOption.values[i];
|
||||
Navigator.of(context).pop(option.value);
|
||||
},
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// Column(
|
||||
// mainAxisAlignment: MainAxisAlignment.start,
|
||||
// children: [
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: Radio(
|
||||
activeColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.radioButtonIconEnabled,
|
||||
value: ChurnOption.values[i],
|
||||
groupValue: option.value,
|
||||
onChanged: (_) {
|
||||
option.value = ChurnOption.values[i];
|
||||
Navigator.of(context).pop(option.value);
|
||||
},
|
||||
),
|
||||
),
|
||||
// ],
|
||||
// ),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
ChurnOption.values[i].name.capitalize(),
|
||||
style: STextStyles.titleBold12(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
Text(
|
||||
ChurnOption.values[i] ==
|
||||
ChurnOption.continuous
|
||||
? "Keep churning until manually stopped"
|
||||
: "Stop after a set number of churns",
|
||||
style: STextStyles.itemSubtitle12(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
292
lib/pages/churning/churning_view.dart
Normal file
292
lib/pages/churning/churning_view.dart
Normal file
|
@ -0,0 +1,292 @@
|
|||
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 '../../providers/churning/churning_service_provider.dart';
|
||||
import '../../themes/stack_colors.dart';
|
||||
import '../../utilities/assets.dart';
|
||||
import '../../utilities/constants.dart';
|
||||
import '../../utilities/extensions/extensions.dart';
|
||||
import '../../utilities/text_styles.dart';
|
||||
import '../../widgets/background.dart';
|
||||
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../widgets/custom_buttons/checkbox_text_button.dart';
|
||||
import '../../widgets/desktop/primary_button.dart';
|
||||
import '../../widgets/rounded_container.dart';
|
||||
import '../../widgets/rounded_white_container.dart';
|
||||
import '../../widgets/stack_dialog.dart';
|
||||
import '../../widgets/stack_text_field.dart';
|
||||
import 'churning_progress_view.dart';
|
||||
import 'churning_rounds_selection_sheet.dart';
|
||||
|
||||
class ChurningView extends ConsumerStatefulWidget {
|
||||
const ChurningView({
|
||||
super.key,
|
||||
required this.walletId,
|
||||
});
|
||||
|
||||
static const routeName = "/churnView";
|
||||
|
||||
final String walletId;
|
||||
|
||||
@override
|
||||
ConsumerState<ChurningView> createState() => _ChurnViewState();
|
||||
}
|
||||
|
||||
class _ChurnViewState extends ConsumerState<ChurningView> {
|
||||
late final TextEditingController churningRoundController;
|
||||
late final FocusNode churningRoundFocusNode;
|
||||
|
||||
bool _enableStartButton = false;
|
||||
|
||||
ChurnOption _option = ChurnOption.continuous;
|
||||
|
||||
Future<void> _startChurn() async {
|
||||
final churningService = ref.read(pChurningService(widget.walletId));
|
||||
|
||||
final int rounds = _option == ChurnOption.continuous
|
||||
? 0
|
||||
: int.parse(churningRoundController.text);
|
||||
|
||||
churningService.rounds = rounds;
|
||||
|
||||
await Navigator.of(context).pushNamed(
|
||||
ChurningProgressView.routeName,
|
||||
arguments: widget.walletId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
churningRoundController = TextEditingController();
|
||||
|
||||
churningRoundFocusNode = FocusNode();
|
||||
|
||||
final rounds = ref.read(pChurningService(widget.walletId)).rounds;
|
||||
|
||||
_option = rounds == 0 ? ChurnOption.continuous : ChurnOption.custom;
|
||||
churningRoundController.text = rounds.toString();
|
||||
|
||||
_enableStartButton = churningRoundController.text.isNotEmpty;
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
churningRoundController.dispose();
|
||||
churningRoundFocusNode.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Background(
|
||||
child: SafeArea(
|
||||
child: Scaffold(
|
||||
backgroundColor:
|
||||
Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
leading: const AppBarBackButton(),
|
||||
title: Text(
|
||||
"Churn",
|
||||
style: STextStyles.navBarTitle(context),
|
||||
),
|
||||
titleSpacing: 0,
|
||||
actions: [
|
||||
AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: AppBarIconButton(
|
||||
size: 36,
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.circleQuestion,
|
||||
width: 20,
|
||||
height: 20,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.topNavIconPrimary,
|
||||
),
|
||||
onPressed: () async {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => const StackOkDialog(
|
||||
title: "What is churning?",
|
||||
message: "Churning in a Monero wallet involves"
|
||||
" sending Monero to oneself in multiple"
|
||||
" transactions, which can enhance privacy"
|
||||
" by making it harder for observers to "
|
||||
"link your transactions. This process"
|
||||
" re-mixes the funds within the network,"
|
||||
" helping obscure transaction history. "
|
||||
"Churning is optional and mainly beneficial"
|
||||
" in scenarios where maximum privacy is"
|
||||
" desired or if you received the Monero from"
|
||||
" a source from which you'd like to disassociate.",
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: LayoutBuilder(
|
||||
builder: (builderContext, constraints) {
|
||||
return SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: constraints.maxHeight,
|
||||
),
|
||||
child: IntrinsicHeight(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
RoundedWhiteContainer(
|
||||
child: Text(
|
||||
"Churning helps anonymize your coins by mixing them.",
|
||||
style: STextStyles.w500_12(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Text(
|
||||
"Configuration",
|
||||
style: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
RoundedContainer(
|
||||
onPressed: () async {
|
||||
final option =
|
||||
await showModalBottomSheet<ChurnOption?>(
|
||||
backgroundColor: Colors.transparent,
|
||||
context: context,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: Radius.circular(20),
|
||||
),
|
||||
),
|
||||
builder: (_) {
|
||||
return ChurnRoundCountSelectSheet(
|
||||
currentOption: _option,
|
||||
);
|
||||
},
|
||||
);
|
||||
if (option != null) {
|
||||
setState(() {
|
||||
_option = option;
|
||||
});
|
||||
}
|
||||
},
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldActiveBG,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
_option.name.capitalize(),
|
||||
style: STextStyles.w500_12(context),
|
||||
),
|
||||
SvgPicture.asset(
|
||||
Assets.svg.chevronDown,
|
||||
width: 12,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_option == ChurnOption.custom)
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
if (_option == ChurnOption.custom)
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
controller: churningRoundController,
|
||||
focusNode: churningRoundFocusNode,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
],
|
||||
keyboardType: TextInputType.number,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_enableStartButton = value.isNotEmpty;
|
||||
});
|
||||
},
|
||||
style: STextStyles.field(context),
|
||||
decoration: standardInputDecoration(
|
||||
"Number of churns",
|
||||
churningRoundFocusNode,
|
||||
context,
|
||||
).copyWith(
|
||||
labelText: "Enter number of churns..",
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
CheckboxTextButton(
|
||||
label: "Pause on errors",
|
||||
initialValue: !ref
|
||||
.read(pChurningService(widget.walletId))
|
||||
.ignoreErrors,
|
||||
onChanged: (value) {
|
||||
ref
|
||||
.read(pChurningService(widget.walletId))
|
||||
.ignoreErrors = !value;
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
const Spacer(),
|
||||
PrimaryButton(
|
||||
label: "Start",
|
||||
enabled: _enableStartButton,
|
||||
onPressed: _startChurn,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -350,6 +350,7 @@ class _CoinControlViewState extends ConsumerState<CoinControlView> {
|
|||
utxo.isConfirmed(
|
||||
currentHeight,
|
||||
minConfirms,
|
||||
coin.minCoinbaseConfirms,
|
||||
)),
|
||||
initialSelectedState: isSelected,
|
||||
onSelectedChanged: (value) {
|
||||
|
@ -414,6 +415,7 @@ class _CoinControlViewState extends ConsumerState<CoinControlView> {
|
|||
utxo.isConfirmed(
|
||||
currentHeight,
|
||||
minConfirms,
|
||||
coin.minCoinbaseConfirms,
|
||||
)),
|
||||
initialSelectedState: isSelected,
|
||||
onSelectedChanged: (value) {
|
||||
|
@ -558,6 +560,7 @@ class _CoinControlViewState extends ConsumerState<CoinControlView> {
|
|||
utxo.isConfirmed(
|
||||
currentHeight,
|
||||
minConfirms,
|
||||
coin.minCoinbaseConfirms,
|
||||
)),
|
||||
initialSelectedState: isSelected,
|
||||
onSelectedChanged: (value) {
|
||||
|
|
|
@ -117,6 +117,11 @@ class _UtxoCardState extends ConsumerState<UtxoCard> {
|
|||
.getWallet(widget.walletId)
|
||||
.cryptoCurrency
|
||||
.minConfirms,
|
||||
ref
|
||||
.watch(pWallets)
|
||||
.getWallet(widget.walletId)
|
||||
.cryptoCurrency
|
||||
.minCoinbaseConfirms,
|
||||
)
|
||||
? UTXOStatusIconStatus.confirmed
|
||||
: UTXOStatusIconStatus.unconfirmed,
|
||||
|
|
|
@ -98,6 +98,11 @@ class _UtxoDetailsViewState extends ConsumerState<UtxoDetailsView> {
|
|||
final confirmed = utxo!.isConfirmed(
|
||||
currentHeight,
|
||||
ref.watch(pWallets).getWallet(widget.walletId).cryptoCurrency.minConfirms,
|
||||
ref
|
||||
.watch(pWallets)
|
||||
.getWallet(widget.walletId)
|
||||
.cryptoCurrency
|
||||
.minCoinbaseConfirms,
|
||||
);
|
||||
|
||||
return ConditionalParent(
|
||||
|
|
|
@ -70,6 +70,78 @@ class _Step2ViewState extends ConsumerState<Step2View> {
|
|||
|
||||
bool enableNext = false;
|
||||
|
||||
void _onRefundQrTapped() async {
|
||||
try {
|
||||
final qrResult = await scanner.scan();
|
||||
|
||||
final paymentData = AddressUtils.parsePaymentUri(
|
||||
qrResult.rawContent,
|
||||
logging: Logging.instance,
|
||||
);
|
||||
|
||||
if (paymentData != null) {
|
||||
// auto fill address
|
||||
_refundController.text = paymentData.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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _onToQrTapped() async {
|
||||
try {
|
||||
final qrResult = await scanner.scan();
|
||||
|
||||
final paymentData = AddressUtils.parsePaymentUri(
|
||||
qrResult.rawContent,
|
||||
logging: Logging.instance,
|
||||
);
|
||||
|
||||
if (paymentData != null) {
|
||||
// auto fill address
|
||||
_toController.text = paymentData.address;
|
||||
model.recipientAddress = _toController.text;
|
||||
|
||||
setState(() {
|
||||
enableNext = _toController.text.isNotEmpty &&
|
||||
(_refundController.text.isNotEmpty ||
|
||||
!ref.read(efExchangeProvider).supportsRefundAddress);
|
||||
});
|
||||
} else {
|
||||
_toController.text = qrResult.rawContent;
|
||||
model.recipientAddress = _toController.text;
|
||||
|
||||
setState(() {
|
||||
enableNext = _toController.text.isNotEmpty &&
|
||||
(_refundController.text.isNotEmpty ||
|
||||
!!ref.read(efExchangeProvider).supportsRefundAddress);
|
||||
});
|
||||
}
|
||||
} 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
model = widget.model;
|
||||
|
@ -137,7 +209,7 @@ class _Step2ViewState extends ConsumerState<Step2View> {
|
|||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 75));
|
||||
}
|
||||
if (mounted) {
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
|
@ -405,50 +477,7 @@ class _Step2ViewState extends ConsumerState<Step2View> {
|
|||
key: const Key(
|
||||
"sendViewScanQrButtonKey",
|
||||
),
|
||||
onTap: () async {
|
||||
try {
|
||||
final qrResult =
|
||||
await scanner.scan();
|
||||
|
||||
final results =
|
||||
AddressUtils.parseUri(
|
||||
qrResult.rawContent,
|
||||
);
|
||||
if (results.isNotEmpty) {
|
||||
// auto fill address
|
||||
_toController.text =
|
||||
results["address"] ?? "";
|
||||
model.recipientAddress =
|
||||
_toController.text;
|
||||
|
||||
setState(() {
|
||||
enableNext = _toController
|
||||
.text.isNotEmpty &&
|
||||
(_refundController.text
|
||||
.isNotEmpty ||
|
||||
!supportsRefund);
|
||||
});
|
||||
} else {
|
||||
_toController.text =
|
||||
qrResult.rawContent;
|
||||
model.recipientAddress =
|
||||
_toController.text;
|
||||
|
||||
setState(() {
|
||||
enableNext = _toController
|
||||
.text.isNotEmpty &&
|
||||
(_refundController.text
|
||||
.isNotEmpty ||
|
||||
!supportsRefund);
|
||||
});
|
||||
}
|
||||
} 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,
|
||||
);
|
||||
}
|
||||
},
|
||||
onTap: _onToQrTapped,
|
||||
child: const QrCodeIcon(),
|
||||
),
|
||||
],
|
||||
|
@ -685,51 +714,7 @@ class _Step2ViewState extends ConsumerState<Step2View> {
|
|||
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,
|
||||
);
|
||||
}
|
||||
},
|
||||
onTap: _onRefundQrTapped,
|
||||
child: const QrCodeIcon(),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -35,7 +35,7 @@ import '../../wallets/crypto_currency/crypto_currency.dart';
|
|||
import '../../wallets/isar/providers/wallet_info_provider.dart';
|
||||
import '../../wallets/models/tx_data.dart';
|
||||
import '../../wallets/wallet/impl/firo_wallet.dart';
|
||||
import '../../wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart';
|
||||
import '../../wallets/wallet/intermediate/lib_monero_wallet.dart';
|
||||
import '../../widgets/background.dart';
|
||||
import '../../widgets/conditional_parent.dart';
|
||||
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
|
@ -277,7 +277,7 @@ class _SendFromCardState extends ConsumerState<SendFromCard> {
|
|||
// access to this screen but this is needed to get past an error that
|
||||
// would occur only to lead to another error which is why xmr/wow wallets
|
||||
// don't have access to this screen currently
|
||||
if (wallet is CwBasedInterface) {
|
||||
if (wallet is LibMoneroWallet) {
|
||||
await wallet.init();
|
||||
await wallet.open();
|
||||
}
|
||||
|
|
|
@ -382,13 +382,15 @@ class _PaynymDetailsPopupState extends ConsumerState<PaynymDetailsPopup> {
|
|||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: PaynymFollowToggleButton(
|
||||
walletId: widget.walletId,
|
||||
paymentCodeStringToFollow: widget.accountLite.code,
|
||||
style: PaynymFollowToggleButtonStyle.detailsPopup,
|
||||
),
|
||||
),
|
||||
kDisableFollowing
|
||||
? const Spacer()
|
||||
: Expanded(
|
||||
child: PaynymFollowToggleButton(
|
||||
walletId: widget.walletId,
|
||||
paymentCodeStringToFollow: widget.accountLite.code,
|
||||
style: PaynymFollowToggleButtonStyle.detailsPopup,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
|
|
|
@ -13,10 +13,7 @@ import 'dart:async';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import '../../models/paynym/paynym_account.dart';
|
||||
import 'dialogs/claiming_paynym_dialog.dart';
|
||||
import 'paynym_home_view.dart';
|
||||
import '../wallet_view/wallet_view.dart';
|
||||
|
||||
import '../../providers/global/paynym_api_provider.dart';
|
||||
import '../../providers/global/wallets_provider.dart';
|
||||
import '../../providers/wallet/my_paynym_account_state_provider.dart';
|
||||
|
@ -30,6 +27,9 @@ import '../../widgets/custom_buttons/app_bar_icon_button.dart';
|
|||
import '../../widgets/desktop/desktop_app_bar.dart';
|
||||
import '../../widgets/desktop/desktop_scaffold.dart';
|
||||
import '../../widgets/desktop/primary_button.dart';
|
||||
import '../wallet_view/wallet_view.dart';
|
||||
import 'dialogs/claiming_paynym_dialog.dart';
|
||||
import 'paynym_home_view.dart';
|
||||
|
||||
class PaynymClaimView extends ConsumerStatefulWidget {
|
||||
const PaynymClaimView({
|
||||
|
@ -46,25 +46,25 @@ class PaynymClaimView extends ConsumerStatefulWidget {
|
|||
}
|
||||
|
||||
class _PaynymClaimViewState extends ConsumerState<PaynymClaimView> {
|
||||
Future<bool> _addSegwitCode(PaynymAccount myAccount) async {
|
||||
final wallet =
|
||||
ref.read(pWallets).getWallet(widget.walletId) as PaynymInterface;
|
||||
|
||||
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;
|
||||
}
|
||||
// Future<bool> _addSegwitCode(PaynymAccount myAccount) async {
|
||||
// final wallet =
|
||||
// ref.read(pWallets).getWallet(widget.walletId) as PaynymInterface;
|
||||
//
|
||||
// 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) {
|
||||
|
@ -197,7 +197,7 @@ class _PaynymClaimViewState extends ConsumerState<PaynymClaimView> {
|
|||
|
||||
if (shouldCancel) return;
|
||||
|
||||
// attempt to create new entry in paynym.is db
|
||||
// attempt to create new entry in [PaynymIsApi.baseURL] db
|
||||
final created = await ref
|
||||
.read(paynymAPIProvider)
|
||||
.create(pCode.toString());
|
||||
|
@ -209,16 +209,16 @@ class _PaynymClaimViewState extends ConsumerState<PaynymClaimView> {
|
|||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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) {
|
||||
|
@ -258,14 +258,14 @@ class _PaynymClaimViewState extends ConsumerState<PaynymClaimView> {
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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!;
|
||||
|
|
|
@ -27,6 +27,7 @@ import '../../utilities/text_styles.dart';
|
|||
import '../../utilities/util.dart';
|
||||
import '../../widgets/conditional_parent.dart';
|
||||
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../widgets/custom_buttons/paynym_follow_toggle_button.dart';
|
||||
import '../../widgets/desktop/desktop_app_bar.dart';
|
||||
import '../../widgets/desktop/desktop_scaffold.dart';
|
||||
import '../../widgets/desktop/secondary_button.dart';
|
||||
|
@ -121,72 +122,75 @@ class _PaynymHomeViewState extends ConsumerState<PaynymHomeView> {
|
|||
),
|
||||
],
|
||||
),
|
||||
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<void>(
|
||||
context: context,
|
||||
builder: (context) => AddNewPaynymFollowView(
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: RoundedContainer(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
color: _followButtonHoverState
|
||||
? Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.highlight
|
||||
: Colors.transparent,
|
||||
radiusMultiplier: 100,
|
||||
child: Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.svg.plus,
|
||||
width: 16,
|
||||
height: 16,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
"Follow",
|
||||
style:
|
||||
STextStyles.desktopButtonSecondaryEnabled(
|
||||
context,
|
||||
).copyWith(
|
||||
fontSize: 16,
|
||||
trailing: kDisableFollowing
|
||||
? null
|
||||
: 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<void>(
|
||||
context: context,
|
||||
builder: (context) => AddNewPaynymFollowView(
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: RoundedContainer(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
color: _followButtonHoverState
|
||||
? Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.highlight
|
||||
: Colors.transparent,
|
||||
radiusMultiplier: 100,
|
||||
child: Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.svg.plus,
|
||||
width: 16,
|
||||
height: 16,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
],
|
||||
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(
|
||||
|
@ -201,28 +205,29 @@ class _PaynymHomeViewState extends ConsumerState<PaynymHomeView> {
|
|||
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<StackColors>()!
|
||||
.accentColorDark,
|
||||
if (!kDisableFollowing)
|
||||
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<StackColors>()!
|
||||
.accentColorDark,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
AddNewPaynymFollowView.routeName,
|
||||
arguments: widget.walletId,
|
||||
);
|
||||
},
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
AddNewPaynymFollowView.routeName,
|
||||
arguments: widget.walletId,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||
child: AspectRatio(
|
||||
|
|
|
@ -284,13 +284,17 @@ class _PaynymDetailsPopupState extends ConsumerState<DesktopPaynymDetails> {
|
|||
const SizedBox(
|
||||
width: 20,
|
||||
),
|
||||
Expanded(
|
||||
child: PaynymFollowToggleButton(
|
||||
walletId: widget.walletId,
|
||||
paymentCodeStringToFollow: widget.accountLite.code,
|
||||
style: PaynymFollowToggleButtonStyle.detailsDesktop,
|
||||
),
|
||||
),
|
||||
kDisableFollowing
|
||||
? const Spacer()
|
||||
: Expanded(
|
||||
child: PaynymFollowToggleButton(
|
||||
walletId: widget.walletId,
|
||||
paymentCodeStringToFollow:
|
||||
widget.accountLite.code,
|
||||
style:
|
||||
PaynymFollowToggleButtonStyle.detailsDesktop,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_showInsufficientFundsInfo)
|
||||
|
|
|
@ -11,8 +11,10 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../networking/http.dart';
|
||||
import '../../../services/tor_service.dart';
|
||||
import '../../../utilities/paynym_is_api.dart';
|
||||
import '../../../utilities/prefs.dart';
|
||||
|
||||
class PayNymBot extends StatelessWidget {
|
||||
|
@ -50,7 +52,8 @@ class PayNymBot extends StatelessWidget {
|
|||
|
||||
Future<Uint8List> _fetchImage() async {
|
||||
final HTTP client = HTTP();
|
||||
final Uri uri = Uri.parse("https://paynym.is/$paymentCodeString/avatar");
|
||||
final Uri uri =
|
||||
Uri.parse("${PaynymIsApi.baseURL}/$paymentCodeString/avatar");
|
||||
|
||||
final response = await client.get(
|
||||
url: uri,
|
||||
|
|
|
@ -9,12 +9,13 @@
|
|||
*/
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'paynym_bot.dart';
|
||||
|
||||
import '../../../themes/stack_colors.dart';
|
||||
import '../../../utilities/format.dart';
|
||||
import '../../../utilities/text_styles.dart';
|
||||
import '../../../utilities/util.dart';
|
||||
import '../../../widgets/custom_buttons/paynym_follow_toggle_button.dart';
|
||||
import 'paynym_bot.dart';
|
||||
|
||||
class PaynymCard extends StatefulWidget {
|
||||
const PaynymCard({
|
||||
|
@ -84,10 +85,11 @@ class _PaynymCardState extends State<PaynymCard> {
|
|||
],
|
||||
),
|
||||
),
|
||||
PaynymFollowToggleButton(
|
||||
walletId: widget.walletId,
|
||||
paymentCodeStringToFollow: widget.paymentCodeString,
|
||||
),
|
||||
if (!kDisableFollowing)
|
||||
PaynymFollowToggleButton(
|
||||
walletId: widget.walletId,
|
||||
paymentCodeStringToFollow: widget.paymentCodeString,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
|
|
@ -16,6 +16,7 @@ import 'package:mutex/mutex.dart';
|
|||
|
||||
import '../../notifications/show_flush_bar.dart';
|
||||
// import 'package:stackwallet/providers/global/has_authenticated_start_state_provider.dart';
|
||||
import '../../providers/global/node_service_provider.dart';
|
||||
import '../../providers/global/prefs_provider.dart';
|
||||
import '../../providers/global/secure_store_provider.dart';
|
||||
import '../../providers/global/wallets_provider.dart';
|
||||
|
@ -25,8 +26,10 @@ import '../../utilities/assets.dart';
|
|||
import '../../utilities/biometrics.dart';
|
||||
import '../../utilities/flutter_secure_storage_interface.dart';
|
||||
import '../../utilities/show_loading.dart';
|
||||
import '../../utilities/show_node_tor_settings_mismatch.dart';
|
||||
import '../../utilities/text_styles.dart';
|
||||
import '../../wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart';
|
||||
import '../../utilities/util.dart';
|
||||
import '../../wallets/wallet/intermediate/lib_monero_wallet.dart';
|
||||
import '../../widgets/background.dart';
|
||||
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../widgets/custom_buttons/blue_text_button.dart';
|
||||
|
@ -101,8 +104,22 @@ class _LockscreenViewState extends ConsumerState<LockscreenView> {
|
|||
final walletId = widget.routeOnSuccessArguments as String;
|
||||
|
||||
final wallet = ref.read(pWallets).getWallet(walletId);
|
||||
|
||||
final canContinue = await checkShowNodeTorSettingsMismatch(
|
||||
context: context,
|
||||
currency: wallet.cryptoCurrency,
|
||||
prefs: ref.read(prefsChangeNotifierProvider),
|
||||
nodeService: ref.read(nodeServiceChangeNotifierProvider),
|
||||
allowCancel: false,
|
||||
rootNavigator: Util.isDesktop,
|
||||
);
|
||||
|
||||
if (!canContinue) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Future<void> loadFuture;
|
||||
if (wallet is CwBasedInterface) {
|
||||
if (wallet is LibMoneroWallet) {
|
||||
loadFuture =
|
||||
wallet.init().then((value) async => await (wallet).open());
|
||||
} else {
|
||||
|
|
|
@ -148,6 +148,7 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final coin = ref.watch(pWalletCoin(widget.walletId));
|
||||
final wallet = ref.watch(pWallets).getWallet(widget.walletId);
|
||||
return ConditionalParent(
|
||||
condition: !isDesktop,
|
||||
builder: (child) => Background(
|
||||
|
@ -383,13 +384,11 @@ class _AddressDetailsViewState extends ConsumerState<AddressDetailsView> {
|
|||
detail: address.zSafeFrost.toString(),
|
||||
button: Container(),
|
||||
),
|
||||
if (ref.watch(pWallets).getWallet(widget.walletId)
|
||||
is Bip39HDWallet)
|
||||
if (wallet is Bip39HDWallet && !wallet.isViewOnly)
|
||||
const _Div(
|
||||
height: 12,
|
||||
),
|
||||
if (ref.watch(pWallets).getWallet(widget.walletId)
|
||||
is Bip39HDWallet)
|
||||
if (wallet is Bip39HDWallet && !wallet.isViewOnly)
|
||||
AddressPrivateKey(
|
||||
walletId: widget.walletId,
|
||||
address: address,
|
||||
|
|
|
@ -97,7 +97,7 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
|
|||
initialDirectory: dir.path,
|
||||
);
|
||||
|
||||
if (path != null) {
|
||||
if (path != null && mounted) {
|
||||
final file = File(path);
|
||||
if (file.existsSync()) {
|
||||
unawaited(
|
||||
|
@ -109,13 +109,15 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
|
|||
);
|
||||
} else {
|
||||
await file.writeAsBytes(pngBytes);
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.success,
|
||||
message: "$path saved!",
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
if (mounted) {
|
||||
unawaited(
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.success,
|
||||
message: "$path saved!",
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -144,7 +146,18 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
|
|||
final amountString = amountController.text;
|
||||
final noteString = noteController.text;
|
||||
|
||||
if (amountString.isNotEmpty && Decimal.tryParse(amountString) == null) {
|
||||
// try "."
|
||||
Decimal? amount = Decimal.tryParse(amountString);
|
||||
if (amount == null) {
|
||||
// try single instance of ","
|
||||
final first = amountString.indexOf(",");
|
||||
final last = amountString.lastIndexOf(",");
|
||||
if (first == last) {
|
||||
amount = Decimal.tryParse(amountString.replaceFirst(",", "."));
|
||||
}
|
||||
}
|
||||
|
||||
if (amountString.isNotEmpty && amount == null) {
|
||||
showFloatingFlushBar(
|
||||
type: FlushBarType.warning,
|
||||
message: "Invalid amount",
|
||||
|
@ -156,7 +169,7 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
|
|||
final Map<String, String> queryParams = {};
|
||||
|
||||
if (amountString.isNotEmpty) {
|
||||
queryParams["amount"] = amountString;
|
||||
queryParams["amount"] = amount.toString();
|
||||
}
|
||||
if (noteString.isNotEmpty) {
|
||||
queryParams["message"] = noteString;
|
||||
|
@ -300,7 +313,7 @@ class _GenerateUriQrCodeViewState extends State<GenerateUriQrCodeView> {
|
|||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(const Duration(milliseconds: 70));
|
||||
}
|
||||
if (mounted) {
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
|
|
|
@ -18,6 +18,7 @@ import 'package:flutter_svg/flutter_svg.dart';
|
|||
import 'package:isar/isar.dart';
|
||||
|
||||
import '../../models/isar/models/isar_models.dart';
|
||||
import '../../models/keys/view_only_wallet_data.dart';
|
||||
import '../../notifications/show_flush_bar.dart';
|
||||
import '../../providers/db/main_db_provider.dart';
|
||||
import '../../providers/providers.dart';
|
||||
|
@ -34,8 +35,10 @@ import '../../wallets/isar/providers/wallet_info_provider.dart';
|
|||
import '../../wallets/wallet/impl/bitcoin_wallet.dart';
|
||||
import '../../wallets/wallet/intermediate/bip39_hd_wallet.dart';
|
||||
import '../../wallets/wallet/wallet_mixin_interfaces/bcash_interface.dart';
|
||||
import '../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
|
||||
import '../../wallets/wallet/wallet_mixin_interfaces/multi_address_interface.dart';
|
||||
import '../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
|
||||
import '../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
|
||||
import '../../widgets/background.dart';
|
||||
import '../../widgets/conditional_parent.dart';
|
||||
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
|
@ -107,15 +110,39 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
|
|||
|
||||
final Address? address;
|
||||
if (wallet is Bip39HDWallet && wallet is! BCashInterface) {
|
||||
final type = DerivePathType.values.firstWhere(
|
||||
(e) => e.getAddressType() == _walletAddressTypes[_currentIndex],
|
||||
);
|
||||
DerivePathType? type;
|
||||
if (wallet.isViewOnly && wallet is ExtendedKeysInterface) {
|
||||
final voData = await wallet.getViewOnlyWalletData()
|
||||
as ExtendedKeysViewOnlyWalletData;
|
||||
for (final t in wallet.cryptoCurrency.supportedDerivationPathTypes) {
|
||||
final testPath = wallet.cryptoCurrency.constructDerivePath(
|
||||
derivePathType: t,
|
||||
chain: 0,
|
||||
index: 0,
|
||||
);
|
||||
if (testPath.startsWith(voData.xPubs.first.path)) {
|
||||
type = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
type = DerivePathType.values.firstWhere(
|
||||
(e) => e.getAddressType() == _walletAddressTypes[_currentIndex],
|
||||
);
|
||||
}
|
||||
|
||||
address = await wallet.generateNextReceivingAddress(
|
||||
derivePathType: type,
|
||||
derivePathType: type!,
|
||||
);
|
||||
await ref.read(mainDBProvider).isar.writeTxn(() async {
|
||||
await ref.read(mainDBProvider).isar.addresses.put(address!);
|
||||
final isar = ref.read(mainDBProvider).isar;
|
||||
await isar.writeTxn(() async {
|
||||
await isar.addresses.put(address!);
|
||||
});
|
||||
final info = ref.read(pWalletInfo(walletId));
|
||||
await info.updateReceivingAddress(
|
||||
newAddress: address.value,
|
||||
isar: isar,
|
||||
);
|
||||
} else {
|
||||
await wallet.generateNewReceivingAddress();
|
||||
address = null;
|
||||
|
@ -183,10 +210,15 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
|
|||
clipboard = widget.clipboard;
|
||||
final wallet = ref.read(pWallets).getWallet(walletId);
|
||||
_supportsSpark = wallet is SparkInterface;
|
||||
_showMultiType = _supportsSpark ||
|
||||
(wallet is! BCashInterface &&
|
||||
wallet is Bip39HDWallet &&
|
||||
wallet.supportedAddressTypes.length > 1);
|
||||
|
||||
if (wallet is ViewOnlyOptionInterface && wallet.isViewOnly) {
|
||||
_showMultiType = false;
|
||||
} else {
|
||||
_showMultiType = _supportsSpark ||
|
||||
(wallet is! BCashInterface &&
|
||||
wallet is Bip39HDWallet &&
|
||||
wallet.supportedAddressTypes.length > 1);
|
||||
}
|
||||
|
||||
_walletAddressTypes.add(wallet.info.mainAddressType);
|
||||
|
||||
|
@ -259,6 +291,18 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
|
|||
address = ref.watch(pWalletReceivingAddress(walletId));
|
||||
}
|
||||
|
||||
final wallet =
|
||||
ref.watch(pWallets.select((value) => value.getWallet(walletId)));
|
||||
|
||||
final bool canGen;
|
||||
if (wallet is ViewOnlyOptionInterface &&
|
||||
wallet.isViewOnly &&
|
||||
wallet.viewOnlyType == ViewOnlyWalletType.addressOnly) {
|
||||
canGen = false;
|
||||
} else {
|
||||
canGen = (wallet is MultiAddressInterface || _supportsSpark);
|
||||
}
|
||||
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
|
||||
|
@ -547,17 +591,11 @@ class _ReceiveViewState extends ConsumerState<ReceiveView> {
|
|||
);
|
||||
},
|
||||
),
|
||||
if (ref.watch(
|
||||
pWallets.select((value) => value.getWallet(walletId)),
|
||||
) is MultiAddressInterface ||
|
||||
_supportsSpark)
|
||||
if (canGen)
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
if (ref.watch(
|
||||
pWallets.select((value) => value.getWallet(walletId)),
|
||||
) is MultiAddressInterface ||
|
||||
_supportsSpark)
|
||||
if (canGen)
|
||||
SecondaryButton(
|
||||
label: "Generate new address",
|
||||
onPressed: _supportsSpark &&
|
||||
|
|
|
@ -120,6 +120,69 @@ class _RecipientState extends ConsumerState<Recipient> {
|
|||
}
|
||||
}
|
||||
|
||||
void _onQrTapped() async {
|
||||
try {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(
|
||||
const Duration(
|
||||
milliseconds: 75,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final qrResult = await ref.read(pBarcodeScanner).scan();
|
||||
|
||||
Logging.instance.log(
|
||||
"qrResult content: ${qrResult.rawContent}",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
final paymentData = AddressUtils.parsePaymentUri(
|
||||
qrResult.rawContent,
|
||||
logging: Logging.instance,
|
||||
);
|
||||
|
||||
Logging.instance.log(
|
||||
"qrResult parsed: $paymentData",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
if (paymentData != null &&
|
||||
paymentData.coin?.uriScheme == widget.coin.uriScheme) {
|
||||
// auto fill address
|
||||
|
||||
addressController.text = paymentData.address.trim();
|
||||
|
||||
// autofill amount field
|
||||
if (paymentData.amount != null) {
|
||||
final Amount amount = Decimal.parse(paymentData.amount!).toAmount(
|
||||
fractionDigits: widget.coin.fractionDigits,
|
||||
);
|
||||
amountController.text =
|
||||
ref.read(pAmountFormatter(widget.coin)).format(
|
||||
amount,
|
||||
withUnitName: false,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
addressController.text = qrResult.rawContent.trim();
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_addressIsEmpty = addressController.text.isEmpty;
|
||||
});
|
||||
|
||||
_updateRecipientData();
|
||||
} 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
addressController = TextEditingController();
|
||||
|
@ -289,76 +352,7 @@ class _RecipientState extends ConsumerState<Recipient> {
|
|||
key: const Key(
|
||||
"sendViewScanQrButtonKey",
|
||||
),
|
||||
onTap: () async {
|
||||
try {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.delayed(
|
||||
const Duration(
|
||||
milliseconds: 75,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final qrResult =
|
||||
await ref.read(pBarcodeScanner).scan();
|
||||
|
||||
Logging.instance.log(
|
||||
"qrResult content: ${qrResult.rawContent}",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
/// TODO: deal with address utils
|
||||
final results =
|
||||
AddressUtils.parseUri(qrResult.rawContent);
|
||||
|
||||
Logging.instance.log(
|
||||
"qrResult parsed: $results",
|
||||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
if (results.isNotEmpty &&
|
||||
results["scheme"] ==
|
||||
widget.coin.uriScheme) {
|
||||
// auto fill address
|
||||
|
||||
addressController.text =
|
||||
(results["address"] ?? "").trim();
|
||||
|
||||
// autofill amount field
|
||||
if (results["amount"] != null) {
|
||||
final Amount amount =
|
||||
Decimal.parse(results["amount"]!)
|
||||
.toAmount(
|
||||
fractionDigits:
|
||||
widget.coin.fractionDigits,
|
||||
);
|
||||
amountController.text = ref
|
||||
.read(pAmountFormatter(widget.coin))
|
||||
.format(
|
||||
amount,
|
||||
withUnitName: false,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
addressController.text =
|
||||
qrResult.rawContent.trim();
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_addressIsEmpty =
|
||||
addressController.text.isEmpty;
|
||||
});
|
||||
|
||||
_updateRecipientData();
|
||||
} 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,
|
||||
);
|
||||
}
|
||||
},
|
||||
onTap: _onQrTapped,
|
||||
child: const QrCodeIcon(),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:cw_core/monero_transaction_priority.dart';
|
||||
import 'package:cs_monero/cs_monero.dart' as lib_monero;
|
||||
import 'package:decimal/decimal.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
@ -163,9 +163,13 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
final paymentData = AddressUtils.parsePaymentUri(qrResult.rawContent);
|
||||
final paymentData = AddressUtils.parsePaymentUri(
|
||||
qrResult.rawContent,
|
||||
logging: Logging.instance,
|
||||
);
|
||||
|
||||
if (paymentData.coin.uriScheme == coin.uriScheme) {
|
||||
if (paymentData != null &&
|
||||
paymentData.coin?.uriScheme == coin.uriScheme) {
|
||||
// auto fill address
|
||||
_address = paymentData.address.trim();
|
||||
sendToController.text = _address!;
|
||||
|
@ -195,12 +199,8 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
});
|
||||
|
||||
// now check for non standard encoded basic address
|
||||
} else if (ref
|
||||
.read(pWallets)
|
||||
.getWallet(walletId)
|
||||
.cryptoCurrency
|
||||
.validateAddress(qrResult.rawContent)) {
|
||||
_address = qrResult.rawContent.trim();
|
||||
} else {
|
||||
_address = qrResult.rawContent.split("\n").first.trim();
|
||||
sendToController.text = _address ?? "";
|
||||
|
||||
_setValidAddressProviders(_address);
|
||||
|
@ -470,22 +470,22 @@ class _SendViewState extends ConsumerState<SendView> {
|
|||
|
||||
Amount fee;
|
||||
if (coin is Monero) {
|
||||
MoneroTransactionPriority specialMoneroId;
|
||||
lib_monero.TransactionPriority specialMoneroId;
|
||||
switch (ref.read(feeRateTypeStateProvider.state).state) {
|
||||
case FeeRateType.fast:
|
||||
specialMoneroId = MoneroTransactionPriority.fast;
|
||||
specialMoneroId = lib_monero.TransactionPriority.high;
|
||||
break;
|
||||
case FeeRateType.average:
|
||||
specialMoneroId = MoneroTransactionPriority.regular;
|
||||
specialMoneroId = lib_monero.TransactionPriority.medium;
|
||||
break;
|
||||
case FeeRateType.slow:
|
||||
specialMoneroId = MoneroTransactionPriority.slow;
|
||||
specialMoneroId = lib_monero.TransactionPriority.normal;
|
||||
break;
|
||||
default:
|
||||
throw ArgumentError("custom fee not available for monero");
|
||||
}
|
||||
|
||||
fee = await wallet.estimateFeeFor(amount, specialMoneroId.raw!);
|
||||
fee = await wallet.estimateFeeFor(amount, specialMoneroId.value);
|
||||
cachedFees[amount] = ref.read(pAmountFormatter(coin)).format(
|
||||
fee,
|
||||
withUnitName: true,
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import 'package:cw_core/monero_transaction_priority.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cs_monero/cs_monero.dart' as lib_monero;
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../models/paymint/fee_object_model.dart';
|
||||
import '../../../providers/providers.dart';
|
||||
import '../../../providers/ui/fee_rate_type_state_provider.dart';
|
||||
|
@ -90,7 +91,7 @@ class _TransactionFeeSelectionSheetState
|
|||
if (coin is Monero || coin is Wownero) {
|
||||
final fee = await wallet.estimateFeeFor(
|
||||
amount,
|
||||
MoneroTransactionPriority.fast.raw!,
|
||||
lib_monero.TransactionPriority.high.value,
|
||||
);
|
||||
ref.read(feeSheetSessionCacheProvider).fast[amount] = fee;
|
||||
} else if (coin is Firo) {
|
||||
|
@ -127,7 +128,7 @@ class _TransactionFeeSelectionSheetState
|
|||
if (coin is Monero || coin is Wownero) {
|
||||
final fee = await wallet.estimateFeeFor(
|
||||
amount,
|
||||
MoneroTransactionPriority.regular.raw!,
|
||||
lib_monero.TransactionPriority.medium.value,
|
||||
);
|
||||
ref.read(feeSheetSessionCacheProvider).average[amount] = fee;
|
||||
} else if (coin is Firo) {
|
||||
|
@ -163,7 +164,7 @@ class _TransactionFeeSelectionSheetState
|
|||
if (coin is Monero || coin is Wownero) {
|
||||
final fee = await wallet.estimateFeeFor(
|
||||
amount,
|
||||
MoneroTransactionPriority.slow.raw!,
|
||||
lib_monero.TransactionPriority.normal.value,
|
||||
);
|
||||
ref.read(feeSheetSessionCacheProvider).slow[amount] = fee;
|
||||
} else if (coin is Firo) {
|
||||
|
|
|
@ -27,6 +27,7 @@ import '../../utilities/address_utils.dart';
|
|||
import '../../utilities/amount/amount.dart';
|
||||
import '../../utilities/amount/amount_formatter.dart';
|
||||
import '../../utilities/amount/amount_input_formatter.dart';
|
||||
import '../../utilities/amount/amount_unit.dart';
|
||||
import '../../utilities/assets.dart';
|
||||
import '../../utilities/barcode_scanner_interface.dart';
|
||||
import '../../utilities/clipboard_interface.dart';
|
||||
|
@ -163,25 +164,30 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
|
|||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
final results = AddressUtils.parseUri(qrResult.rawContent);
|
||||
final paymentData = AddressUtils.parsePaymentUri(
|
||||
qrResult.rawContent,
|
||||
logging: Logging.instance,
|
||||
);
|
||||
|
||||
Logging.instance.log("qrResult parsed: $results", level: LogLevel.Info);
|
||||
Logging.instance
|
||||
.log("qrResult parsed: $paymentData", level: LogLevel.Info);
|
||||
|
||||
if (results.isNotEmpty && results["scheme"] == coin.uriScheme) {
|
||||
if (paymentData != null &&
|
||||
paymentData.coin?.uriScheme == coin.uriScheme) {
|
||||
// auto fill address
|
||||
_address = (results["address"] ?? "").trim();
|
||||
_address = paymentData.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"]!;
|
||||
if (paymentData.message != null) {
|
||||
noteController.text = paymentData.message!;
|
||||
} else if (paymentData.label != null) {
|
||||
noteController.text = paymentData.label!;
|
||||
}
|
||||
|
||||
// autofill amount field
|
||||
if (results["amount"] != null) {
|
||||
final Amount amount = Decimal.parse(results["amount"]!).toAmount(
|
||||
if (paymentData.amount != null) {
|
||||
final Amount amount = Decimal.parse(paymentData.amount!).toAmount(
|
||||
fractionDigits: tokenContract.decimals,
|
||||
);
|
||||
cryptoAmountController.text = ref.read(pAmountFormatter(coin)).format(
|
||||
|
@ -198,12 +204,8 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
|
|||
});
|
||||
|
||||
// now check for non standard encoded basic address
|
||||
} else if (ref
|
||||
.read(pWallets)
|
||||
.getWallet(walletId)
|
||||
.cryptoCurrency
|
||||
.validateAddress(qrResult.rawContent)) {
|
||||
_address = qrResult.rawContent.trim();
|
||||
} else {
|
||||
_address = qrResult.rawContent.split("\n").first.trim();
|
||||
sendToController.text = _address ?? "";
|
||||
|
||||
_updatePreviewButtonState(_address, _amountToSend);
|
||||
|
@ -1032,7 +1034,9 @@ class _TokenSendViewState extends ConsumerState<TokenSendView> {
|
|||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(
|
||||
tokenContract.symbol,
|
||||
ref
|
||||
.watch(pAmountUnit(coin))
|
||||
.unitForContract(tokenContract),
|
||||
style: STextStyles.smallMed14(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
|
|
|
@ -272,48 +272,48 @@ class AboutView extends ConsumerWidget {
|
|||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
if (AppConfig.coins.whereType<Monero>().isNotEmpty)
|
||||
FutureBuilder(
|
||||
future: GitStatus.getMoneroCommitStatus(),
|
||||
builder: (
|
||||
context,
|
||||
AsyncSnapshot<CommitStatus> snapshot,
|
||||
) {
|
||||
CommitStatus stateOfCommit =
|
||||
CommitStatus.notLoaded;
|
||||
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.done &&
|
||||
snapshot.hasData) {
|
||||
stateOfCommit = snapshot.data!;
|
||||
}
|
||||
return RoundedWhiteContainer(
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
"Monero Build Commit",
|
||||
style: STextStyles.titleBold12(context),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 4,
|
||||
),
|
||||
SelectableText(
|
||||
GitStatus.moneroCommit,
|
||||
style: GitStatus.styleForStatus(
|
||||
stateOfCommit,
|
||||
context,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
// if (AppConfig.coins.whereType<Monero>().isNotEmpty)
|
||||
// FutureBuilder(
|
||||
// future: GitStatus.getMoneroCommitStatus(),
|
||||
// builder: (
|
||||
// context,
|
||||
// AsyncSnapshot<CommitStatus> snapshot,
|
||||
// ) {
|
||||
// CommitStatus stateOfCommit =
|
||||
// CommitStatus.notLoaded;
|
||||
//
|
||||
// if (snapshot.connectionState ==
|
||||
// ConnectionState.done &&
|
||||
// snapshot.hasData) {
|
||||
// stateOfCommit = snapshot.data!;
|
||||
// }
|
||||
// return RoundedWhiteContainer(
|
||||
// child: Column(
|
||||
// crossAxisAlignment:
|
||||
// CrossAxisAlignment.stretch,
|
||||
// children: [
|
||||
// Text(
|
||||
// "Monero Build Commit",
|
||||
// style: STextStyles.titleBold12(context),
|
||||
// ),
|
||||
// const SizedBox(
|
||||
// height: 4,
|
||||
// ),
|
||||
// SelectableText(
|
||||
// GitStatus.moneroCommit,
|
||||
// style: GitStatus.styleForStatus(
|
||||
// stateOfCommit,
|
||||
// context,
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// const SizedBox(
|
||||
// height: 12,
|
||||
// ),
|
||||
RoundedWhiteContainer(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
|
|
@ -184,54 +184,56 @@ class AdvancedSettingsView extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
// showExchange pref.
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
RoundedWhiteContainer(
|
||||
child: Consumer(
|
||||
builder: (_, ref, __) {
|
||||
return RawMaterialButton(
|
||||
// splashColor: Theme.of(context).extension<StackColors>()!.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 exchange features",
|
||||
style: STextStyles.titleBold12(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
width: 40,
|
||||
child: DraggableSwitchButton(
|
||||
isOn: ref.watch(
|
||||
prefsChangeNotifierProvider.select(
|
||||
(value) => value.enableExchange,
|
||||
),
|
||||
),
|
||||
onValueChanged: (newValue) {
|
||||
ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.enableExchange = newValue;
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
if (Constants.enableExchange)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (Constants.enableExchange)
|
||||
RoundedWhiteContainer(
|
||||
child: Consumer(
|
||||
builder: (_, ref, __) {
|
||||
return RawMaterialButton(
|
||||
// splashColor: Theme.of(context).extension<StackColors>()!.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 exchange features",
|
||||
style: STextStyles.titleBold12(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
width: 40,
|
||||
child: DraggableSwitchButton(
|
||||
isOn: ref.watch(
|
||||
prefsChangeNotifierProvider.select(
|
||||
(value) => value.enableExchange,
|
||||
),
|
||||
),
|
||||
onValueChanged: (newValue) {
|
||||
ref
|
||||
.read(prefsChangeNotifierProvider)
|
||||
.enableExchange = newValue;
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
|
|
|
@ -18,7 +18,7 @@ import 'package:file_picker/file_picker.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_libepiccash/git_versions.dart' as EPIC_VERSIONS;
|
||||
import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS;
|
||||
// import 'package:flutter_libmonero/git_versions.dart' as MONERO_VERSIONS;
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:lelantus/git_versions.dart' as FIRO_VERSIONS;
|
||||
|
@ -313,8 +313,8 @@ class _DebugViewState extends ConsumerState<DebugView> {
|
|||
FIRO_VERSIONS.getPluginVersion();
|
||||
final String epicCashCommit =
|
||||
EPIC_VERSIONS.getPluginVersion();
|
||||
final String moneroCommit =
|
||||
MONERO_VERSIONS.getPluginVersion();
|
||||
// final String moneroCommit =
|
||||
// MONERO_VERSIONS.getPluginVersion();
|
||||
final DeviceInfoPlugin deviceInfoPlugin =
|
||||
DeviceInfoPlugin();
|
||||
final deviceInfo =
|
||||
|
@ -347,7 +347,7 @@ class _DebugViewState extends ConsumerState<DebugView> {
|
|||
"appName": appName,
|
||||
"firoCommit": firoCommit,
|
||||
"epicCashCommit": epicCashCommit,
|
||||
"moneroCommit": moneroCommit,
|
||||
// "moneroCommit": moneroCommit,
|
||||
"deviceInfoMap": deviceInfoMap,
|
||||
"errorLogs": errorLogs,
|
||||
};
|
||||
|
|
|
@ -3,9 +3,9 @@ import 'dart:io';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'edit_coin_units_view.dart';
|
||||
import '../../../../../providers/global/prefs_provider.dart';
|
||||
|
||||
import '../../../../../app_config.dart';
|
||||
import '../../../../../providers/global/prefs_provider.dart';
|
||||
import '../../../../../themes/coin_icon_provider.dart';
|
||||
import '../../../../../themes/stack_colors.dart';
|
||||
import '../../../../../utilities/assets.dart';
|
||||
|
@ -18,6 +18,7 @@ import '../../../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
|||
import '../../../../../widgets/desktop/desktop_dialog.dart';
|
||||
import '../../../../../widgets/desktop/desktop_dialog_close_button.dart';
|
||||
import '../../../../../widgets/rounded_white_container.dart';
|
||||
import 'edit_coin_units_view.dart';
|
||||
|
||||
class ManageCoinUnitsView extends ConsumerWidget {
|
||||
const ManageCoinUnitsView({super.key});
|
||||
|
@ -44,13 +45,9 @@ class ManageCoinUnitsView extends ConsumerWidget {
|
|||
prefsChangeNotifierProvider.select((value) => value.showTestNetCoins),
|
||||
);
|
||||
|
||||
final _coins = AppConfig.coins
|
||||
.where((e) => e is! Firo && e.network != CryptoCurrencyNetwork.test)
|
||||
.toList();
|
||||
|
||||
final coins = showTestNet
|
||||
? _coins
|
||||
: _coins.where((e) => e.network != CryptoCurrencyNetwork.test).toList();
|
||||
? AppConfig.coins
|
||||
: AppConfig.coins.where((e) => !e.network.isTestNet).toList();
|
||||
|
||||
return ConditionalParent(
|
||||
condition: Util.isDesktop,
|
||||
|
|
|
@ -18,18 +18,21 @@ import 'package:uuid/uuid.dart';
|
|||
|
||||
import '../../../../models/node_model.dart';
|
||||
import '../../../../notifications/show_flush_bar.dart';
|
||||
import '../../../../providers/global/active_wallet_provider.dart';
|
||||
import '../../../../providers/global/secure_store_provider.dart';
|
||||
import '../../../../providers/providers.dart';
|
||||
import '../../../../themes/stack_colors.dart';
|
||||
import '../../../../utilities/assets.dart';
|
||||
import '../../../../utilities/constants.dart';
|
||||
import '../../../../utilities/enums/sync_type_enum.dart';
|
||||
import '../../../../utilities/flutter_secure_storage_interface.dart';
|
||||
import '../../../../utilities/test_node_connection.dart';
|
||||
import '../../../../utilities/text_styles.dart';
|
||||
import '../../../../utilities/tor_plain_net_option_enum.dart';
|
||||
import '../../../../utilities/util.dart';
|
||||
import '../../../../wallets/crypto_currency/crypto_currency.dart';
|
||||
import '../../../../wallets/crypto_currency/intermediate/cryptonote_currency.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart';
|
||||
import '../../../../wallets/wallet/intermediate/lib_monero_wallet.dart';
|
||||
import '../../../../widgets/background.dart';
|
||||
import '../../../../widgets/conditional_parent.dart';
|
||||
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
|
@ -222,13 +225,18 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
|
|||
|
||||
// strip unused path
|
||||
String address = formData.host!;
|
||||
if (coin is CwBasedInterface) {
|
||||
if (coin is LibMoneroWallet) {
|
||||
if (address.startsWith("http")) {
|
||||
final uri = Uri.parse(address);
|
||||
address = "${uri.scheme}://${uri.host}";
|
||||
}
|
||||
}
|
||||
|
||||
final torEnabled = formData.netOption == TorPlainNetworkOption.tor ||
|
||||
formData.netOption == TorPlainNetworkOption.both;
|
||||
final plainEnabled = formData.netOption == TorPlainNetworkOption.clear ||
|
||||
formData.netOption == TorPlainNetworkOption.both;
|
||||
|
||||
switch (viewType) {
|
||||
case AddEditNodeViewType.add:
|
||||
final NodeModel node = NodeModel(
|
||||
|
@ -243,6 +251,8 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
|
|||
isFailover: formData.isFailover!,
|
||||
trusted: formData.trusted!,
|
||||
isDown: false,
|
||||
torEnabled: torEnabled,
|
||||
clearnetEnabled: plainEnabled,
|
||||
);
|
||||
|
||||
await ref.read(nodeServiceChangeNotifierProvider).add(
|
||||
|
@ -250,6 +260,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
|
|||
formData.password,
|
||||
true,
|
||||
);
|
||||
await _notifyWalletsOfUpdatedNode();
|
||||
if (mounted) {
|
||||
Navigator.of(context)
|
||||
.popUntil(ModalRoute.withName(widget.routeOnSuccessOrDelete));
|
||||
|
@ -268,6 +279,8 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
|
|||
isFailover: formData.isFailover!,
|
||||
trusted: formData.trusted!,
|
||||
isDown: false,
|
||||
torEnabled: torEnabled,
|
||||
clearnetEnabled: plainEnabled,
|
||||
);
|
||||
|
||||
await ref.read(nodeServiceChangeNotifierProvider).add(
|
||||
|
@ -275,6 +288,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
|
|||
formData.password,
|
||||
true,
|
||||
);
|
||||
await _notifyWalletsOfUpdatedNode();
|
||||
if (mounted) {
|
||||
Navigator.of(context)
|
||||
.popUntil(ModalRoute.withName(widget.routeOnSuccessOrDelete));
|
||||
|
@ -283,6 +297,39 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _notifyWalletsOfUpdatedNode() async {
|
||||
final wallets =
|
||||
ref.read(pWallets).wallets.where((e) => e.info.coin == widget.coin);
|
||||
final prefs = ref.read(prefsChangeNotifierProvider);
|
||||
|
||||
switch (prefs.syncType) {
|
||||
case SyncingType.currentWalletOnly:
|
||||
for (final wallet in wallets) {
|
||||
if (ref.read(currentWalletIdProvider) == wallet.walletId) {
|
||||
unawaited(wallet.updateNode().then((value) => wallet.refresh()));
|
||||
} else {
|
||||
unawaited(wallet.updateNode());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SyncingType.selectedWalletsAtStartup:
|
||||
final List<String> walletIdsToSync = prefs.walletIdsSyncOnStartup;
|
||||
for (final wallet in wallets) {
|
||||
if (walletIdsToSync.contains(wallet.walletId)) {
|
||||
unawaited(wallet.updateNode().then((value) => wallet.refresh()));
|
||||
} else {
|
||||
unawaited(wallet.updateNode());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SyncingType.allWalletsOnStartup:
|
||||
for (final wallet in wallets) {
|
||||
unawaited(wallet.updateNode().then((value) => wallet.refresh()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
isDesktop = Util.isDesktop;
|
||||
|
@ -422,7 +469,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
|
|||
condition: isDesktop,
|
||||
builder: (child) => DesktopDialog(
|
||||
maxWidth: 580,
|
||||
maxHeight: 500,
|
||||
maxHeight: double.infinity,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
|
@ -568,10 +615,11 @@ class NodeFormData {
|
|||
String? name, host, login, password;
|
||||
int? port;
|
||||
bool? useSSL, isFailover, trusted;
|
||||
TorPlainNetworkOption? netOption;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return "{ name: $name, host: $host, port: $port, useSSL: $useSSL, trusted: $trusted }";
|
||||
return "{ name: $name, host: $host, port: $port, useSSL: $useSSL, trusted: $trusted, netOption: $netOption }";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -615,6 +663,7 @@ class _NodeFormState extends ConsumerState<NodeForm> {
|
|||
bool _trusted = false;
|
||||
int? port;
|
||||
late bool enableSSLCheckbox;
|
||||
late TorPlainNetworkOption netOption;
|
||||
|
||||
late final bool enableAuthFields;
|
||||
|
||||
|
@ -672,6 +721,7 @@ class _NodeFormState extends ConsumerState<NodeForm> {
|
|||
ref.read(nodeFormDataProvider).useSSL = _useSSL;
|
||||
ref.read(nodeFormDataProvider).isFailover = _isFailover;
|
||||
ref.read(nodeFormDataProvider).trusted = _trusted;
|
||||
ref.read(nodeFormDataProvider).netOption = netOption;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -704,6 +754,15 @@ class _NodeFormState extends ConsumerState<NodeForm> {
|
|||
_useSSL = node.useSSL;
|
||||
_isFailover = node.isFailover;
|
||||
_trusted = node.trusted ?? false;
|
||||
|
||||
if (node.torEnabled && !node.clearnetEnabled) {
|
||||
netOption = TorPlainNetworkOption.tor;
|
||||
} else if (node.clearnetEnabled && !node.torEnabled) {
|
||||
netOption = TorPlainNetworkOption.clear;
|
||||
} else {
|
||||
netOption = TorPlainNetworkOption.both;
|
||||
}
|
||||
|
||||
if (widget.coin is Epiccash) {
|
||||
enableSSLCheckbox = !node.host.startsWith("http");
|
||||
} else {
|
||||
|
@ -716,6 +775,7 @@ class _NodeFormState extends ConsumerState<NodeForm> {
|
|||
});
|
||||
} else {
|
||||
enableSSLCheckbox = true;
|
||||
netOption = TorPlainNetworkOption.both;
|
||||
// default to port 3413
|
||||
// _portController.text = "3413";
|
||||
}
|
||||
|
@ -837,7 +897,7 @@ class _NodeFormState extends ConsumerState<NodeForm> {
|
|||
} else {
|
||||
enableSSLCheckbox = true;
|
||||
}
|
||||
} else if (widget.coin is CwBasedInterface) {
|
||||
} else if (widget.coin is LibMoneroWallet) {
|
||||
if (newValue.startsWith("https://")) {
|
||||
_useSSL = true;
|
||||
} else if (newValue.startsWith("http://")) {
|
||||
|
@ -1052,7 +1112,7 @@ class _NodeFormState extends ConsumerState<NodeForm> {
|
|||
),
|
||||
],
|
||||
),
|
||||
if (widget.coin is CwBasedInterface)
|
||||
if (widget.coin is LibMoneroWallet)
|
||||
Row(
|
||||
children: [
|
||||
GestureDetector(
|
||||
|
@ -1168,7 +1228,145 @@ class _NodeFormState extends ConsumerState<NodeForm> {
|
|||
),
|
||||
],
|
||||
),
|
||||
if (widget.coin is! Ethereum)
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
if (widget.coin is! Ethereum)
|
||||
Row(
|
||||
children: [
|
||||
RadioTextButton(
|
||||
label: "Only TOR traffic",
|
||||
enabled: !widget.readOnly,
|
||||
value: TorPlainNetworkOption.tor,
|
||||
groupValue: netOption,
|
||||
onChanged: (value) {
|
||||
if (!widget.readOnly) {
|
||||
setState(
|
||||
() => netOption = TorPlainNetworkOption.tor,
|
||||
);
|
||||
_updateState();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
if (widget.coin is! Ethereum)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (widget.coin is! Ethereum)
|
||||
Row(
|
||||
children: [
|
||||
RadioTextButton(
|
||||
label: "Only non-TOR traffic",
|
||||
enabled: !widget.readOnly,
|
||||
value: TorPlainNetworkOption.clear,
|
||||
groupValue: netOption,
|
||||
onChanged: (value) {
|
||||
if (!widget.readOnly) {
|
||||
setState(
|
||||
() => netOption = TorPlainNetworkOption.clear,
|
||||
);
|
||||
_updateState();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
if (widget.coin is! Ethereum)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (widget.coin is! Ethereum)
|
||||
Row(
|
||||
children: [
|
||||
RadioTextButton(
|
||||
label: "Allow both",
|
||||
enabled: !widget.readOnly,
|
||||
value: TorPlainNetworkOption.both,
|
||||
groupValue: netOption,
|
||||
onChanged: (value) {
|
||||
if (!widget.readOnly) {
|
||||
setState(
|
||||
() => netOption = TorPlainNetworkOption.both,
|
||||
);
|
||||
_updateState();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class RadioTextButton<T> extends StatelessWidget {
|
||||
const RadioTextButton({
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.label,
|
||||
required this.groupValue,
|
||||
required this.onChanged,
|
||||
this.enabled = true,
|
||||
});
|
||||
|
||||
final T value;
|
||||
final String label;
|
||||
final T groupValue;
|
||||
final bool enabled;
|
||||
final void Function(T) onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ConditionalParent(
|
||||
condition: Util.isDesktop,
|
||||
builder: (child) => MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: child,
|
||||
),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
if (value != groupValue) {
|
||||
onChanged.call(value);
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: Radio<T>(
|
||||
activeColor: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.radioButtonIconEnabled,
|
||||
value: value,
|
||||
groupValue: groupValue,
|
||||
onChanged: !enabled
|
||||
? null
|
||||
: (_) {
|
||||
if (value != groupValue) {
|
||||
onChanged.call(value);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 14,
|
||||
),
|
||||
Text(
|
||||
label,
|
||||
style: STextStyles.w500_14(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ class _ManageNodesViewState extends ConsumerState<ManageNodesView> {
|
|||
|
||||
final coins = showTestNet
|
||||
? _coins
|
||||
: _coins.where((e) => e.network != CryptoCurrencyNetwork.test).toList();
|
||||
: _coins.where((e) => !e.network.isTestNet).toList();
|
||||
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
|
|
|
@ -13,15 +13,19 @@ import 'dart:async';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import '../../../../notifications/show_flush_bar.dart';
|
||||
import 'add_edit_node_view.dart';
|
||||
import '../../../../providers/global/active_wallet_provider.dart';
|
||||
import '../../../../providers/global/secure_store_provider.dart';
|
||||
import '../../../../providers/providers.dart';
|
||||
import '../../../../themes/stack_colors.dart';
|
||||
import '../../../../utilities/assets.dart';
|
||||
import '../../../../utilities/enums/sync_type_enum.dart';
|
||||
import '../../../../utilities/flutter_secure_storage_interface.dart';
|
||||
import '../../../../utilities/test_node_connection.dart';
|
||||
import '../../../../utilities/text_styles.dart';
|
||||
import '../../../../utilities/tor_plain_net_option_enum.dart';
|
||||
import '../../../../utilities/util.dart';
|
||||
import '../../../../wallets/crypto_currency/crypto_currency.dart';
|
||||
import '../../../../widgets/background.dart';
|
||||
|
@ -31,7 +35,7 @@ import '../../../../widgets/desktop/delete_button.dart';
|
|||
import '../../../../widgets/desktop/desktop_dialog.dart';
|
||||
import '../../../../widgets/desktop/primary_button.dart';
|
||||
import '../../../../widgets/desktop/secondary_button.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'add_edit_node_view.dart';
|
||||
|
||||
class NodeDetailsView extends ConsumerStatefulWidget {
|
||||
const NodeDetailsView({
|
||||
|
@ -59,6 +63,39 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
|
|||
|
||||
bool _desktopReadOnly = true;
|
||||
|
||||
Future<void> _notifyWalletsOfUpdatedNode() async {
|
||||
final wallets =
|
||||
ref.read(pWallets).wallets.where((e) => e.info.coin == widget.coin);
|
||||
final prefs = ref.read(prefsChangeNotifierProvider);
|
||||
|
||||
switch (prefs.syncType) {
|
||||
case SyncingType.currentWalletOnly:
|
||||
for (final wallet in wallets) {
|
||||
if (ref.read(currentWalletIdProvider) == wallet.walletId) {
|
||||
unawaited(wallet.updateNode().then((value) => wallet.refresh()));
|
||||
} else {
|
||||
unawaited(wallet.updateNode());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SyncingType.selectedWalletsAtStartup:
|
||||
final List<String> walletIdsToSync = prefs.walletIdsSyncOnStartup;
|
||||
for (final wallet in wallets) {
|
||||
if (walletIdsToSync.contains(wallet.walletId)) {
|
||||
unawaited(wallet.updateNode().then((value) => wallet.refresh()));
|
||||
} else {
|
||||
unawaited(wallet.updateNode());
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SyncingType.allWalletsOnStartup:
|
||||
for (final wallet in wallets) {
|
||||
unawaited(wallet.updateNode().then((value) => wallet.refresh()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
initState() {
|
||||
secureStore = ref.read(secureStoreProvider);
|
||||
|
@ -265,6 +302,16 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
|
|||
.read(nodeServiceChangeNotifierProvider)
|
||||
.getNodeById(id: nodeId)!;
|
||||
|
||||
final TorPlainNetworkOption netOption;
|
||||
if (ref.read(nodeFormDataProvider).netOption != null) {
|
||||
netOption = ref.read(nodeFormDataProvider).netOption!;
|
||||
} else {
|
||||
netOption = TorPlainNetworkOption.fromNodeData(
|
||||
node.torEnabled,
|
||||
node.clearnetEnabled,
|
||||
);
|
||||
}
|
||||
|
||||
final nodeFormData = NodeFormData()
|
||||
..useSSL = node.useSSL
|
||||
..trusted = node.trusted
|
||||
|
@ -272,7 +319,8 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
|
|||
..host = node.host
|
||||
..login = node.loginName
|
||||
..port = node.port
|
||||
..isFailover = node.isFailover;
|
||||
..isFailover = node.isFailover
|
||||
..netOption = netOption;
|
||||
nodeFormData.password = await node.getPassword(
|
||||
ref.read(secureStoreProvider),
|
||||
);
|
||||
|
@ -338,6 +386,16 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
|
|||
loginName: ref.read(nodeFormDataProvider).login,
|
||||
isFailover:
|
||||
ref.read(nodeFormDataProvider).isFailover,
|
||||
torEnabled:
|
||||
ref.read(nodeFormDataProvider).netOption ==
|
||||
TorPlainNetworkOption.tor ||
|
||||
ref.read(nodeFormDataProvider).netOption ==
|
||||
TorPlainNetworkOption.both,
|
||||
clearnetEnabled:
|
||||
ref.read(nodeFormDataProvider).netOption ==
|
||||
TorPlainNetworkOption.clear ||
|
||||
ref.read(nodeFormDataProvider).netOption ==
|
||||
TorPlainNetworkOption.both,
|
||||
);
|
||||
|
||||
await ref
|
||||
|
@ -347,6 +405,7 @@ class _NodeDetailsViewState extends ConsumerState<NodeDetailsView> {
|
|||
ref.read(nodeFormDataProvider).password,
|
||||
true,
|
||||
);
|
||||
await _notifyWalletsOfUpdatedNode();
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
|
@ -27,6 +27,7 @@ import '../../../../../models/exchange/change_now/exchange_transaction.dart';
|
|||
import '../../../../../models/exchange/response_objects/trade.dart';
|
||||
import '../../../../../models/isar/models/contact_entry.dart';
|
||||
import '../../../../../models/isar/models/transaction_note.dart';
|
||||
import '../../../../../models/keys/view_only_wallet_data.dart';
|
||||
import '../../../../../models/node_model.dart';
|
||||
import '../../../../../models/stack_restoring_ui_state.dart';
|
||||
import '../../../../../models/trade_wallet_lookup.dart';
|
||||
|
@ -54,10 +55,11 @@ import '../../../../../wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
|||
import '../../../../../wallets/wallet/impl/epiccash_wallet.dart';
|
||||
import '../../../../../wallets/wallet/impl/monero_wallet.dart';
|
||||
import '../../../../../wallets/wallet/impl/wownero_wallet.dart';
|
||||
import '../../../../../wallets/wallet/intermediate/lib_monero_wallet.dart';
|
||||
import '../../../../../wallets/wallet/wallet.dart';
|
||||
import '../../../../../wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart';
|
||||
import '../../../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart';
|
||||
import '../../../../../wallets/wallet/wallet_mixin_interfaces/private_key_interface.dart';
|
||||
import '../../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
|
||||
|
||||
class PreRestoreState {
|
||||
final Set<String> walletIds;
|
||||
|
@ -312,7 +314,10 @@ abstract class SWB {
|
|||
backupWallet['isFavorite'] = wallet.info.isFavourite;
|
||||
backupWallet['otherDataJsonString'] = wallet.info.otherDataJsonString;
|
||||
|
||||
if (wallet is MnemonicInterface) {
|
||||
if (wallet is ViewOnlyOptionInterface && wallet.isViewOnly) {
|
||||
backupWallet['viewOnlyWalletDataKey'] =
|
||||
(await wallet.getViewOnlyWalletData()).toJsonEncodedString();
|
||||
} else if (wallet is MnemonicInterface) {
|
||||
backupWallet['mnemonic'] = await wallet.getMnemonic();
|
||||
backupWallet['mnemonicPassphrase'] =
|
||||
await wallet.getMnemonicPassphrase();
|
||||
|
@ -419,7 +424,16 @@ abstract class SWB {
|
|||
|
||||
String? mnemonic, mnemonicPassphrase, privateKey;
|
||||
|
||||
if (walletbackup['mnemonic'] == null) {
|
||||
ViewOnlyWalletData? viewOnlyData;
|
||||
if (info.isViewOnly) {
|
||||
final viewOnlyDataEncoded =
|
||||
walletbackup['viewOnlyWalletDataKey'] as String;
|
||||
|
||||
viewOnlyData = ViewOnlyWalletData.fromJsonEncodedString(
|
||||
viewOnlyDataEncoded,
|
||||
walletId: info.walletId,
|
||||
);
|
||||
} else if (walletbackup['mnemonic'] == null) {
|
||||
// probably private key based
|
||||
if (walletbackup['privateKey'] != null) {
|
||||
privateKey = walletbackup['privateKey'] as String;
|
||||
|
@ -486,33 +500,44 @@ abstract class SWB {
|
|||
mnemonic: mnemonic,
|
||||
mnemonicPassphrase: mnemonicPassphrase,
|
||||
privateKey: privateKey,
|
||||
viewOnlyData: viewOnlyData,
|
||||
);
|
||||
|
||||
if (wallet is MoneroWallet /*|| wallet is WowneroWallet doesn't work.*/) {
|
||||
await wallet.init(isRestore: true);
|
||||
} else if (wallet is WowneroWallet) {
|
||||
await wallet.init(isRestore: true);
|
||||
} else {
|
||||
await wallet.init();
|
||||
switch (wallet.runtimeType) {
|
||||
case const (EpiccashWallet):
|
||||
await (wallet as EpiccashWallet).init(isRestore: true);
|
||||
break;
|
||||
|
||||
case const (MoneroWallet):
|
||||
await (wallet as MoneroWallet).init(isRestore: true);
|
||||
break;
|
||||
|
||||
case const (WowneroWallet):
|
||||
await (wallet as WowneroWallet).init(isRestore: true);
|
||||
break;
|
||||
|
||||
default:
|
||||
await wallet.init();
|
||||
}
|
||||
|
||||
int restoreHeight = walletbackup['restoreHeight'] as int? ?? 0;
|
||||
if (restoreHeight <= 0) {
|
||||
restoreHeight = walletbackup['storedChainHeight'] as int? ?? 0;
|
||||
if (wallet is EpiccashWallet || wallet is LibMoneroWallet) {
|
||||
restoreHeight = 0;
|
||||
} else {
|
||||
restoreHeight = walletbackup['storedChainHeight'] as int? ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void>? restoringFuture;
|
||||
|
||||
if (!(wallet is CwBasedInterface || wallet is EpiccashWallet)) {
|
||||
if (wallet is BitcoinFrostWallet) {
|
||||
restoringFuture = wallet.recover(
|
||||
isRescan: false,
|
||||
multisigConfig: multisigConfig!,
|
||||
serializedKeys: serializedKeys!,
|
||||
);
|
||||
} else {
|
||||
restoringFuture = wallet.recover(isRescan: false);
|
||||
}
|
||||
final Future<void>? restoringFuture;
|
||||
if (wallet is BitcoinFrostWallet) {
|
||||
restoringFuture = wallet.recover(
|
||||
isRescan: false,
|
||||
multisigConfig: multisigConfig!,
|
||||
serializedKeys: serializedKeys!,
|
||||
);
|
||||
} else {
|
||||
restoringFuture = wallet.recover(isRescan: false);
|
||||
}
|
||||
|
||||
uiState?.update(
|
||||
|
@ -1257,6 +1282,8 @@ abstract class SWB {
|
|||
loginName: node['loginName'] as String?,
|
||||
isFailover: node['isFailover'] as bool,
|
||||
isDown: node['isDown'] as bool,
|
||||
torEnabled: node['torEnabled'] as bool? ?? true,
|
||||
clearnetEnabled: node['plainEnabled'] as bool? ?? true,
|
||||
),
|
||||
node["password"] as String?,
|
||||
true,
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../models/keys/view_only_wallet_data.dart';
|
||||
import '../../../utilities/util.dart';
|
||||
import '../../../widgets/custom_buttons/simple_copy_button.dart';
|
||||
import '../../../widgets/detail_item.dart';
|
||||
import '../../wallet_view/transaction_views/transaction_details_view.dart';
|
||||
|
||||
class ViewOnlyWalletDataWidget extends StatelessWidget {
|
||||
const ViewOnlyWalletDataWidget({
|
||||
super.key,
|
||||
required this.data,
|
||||
});
|
||||
|
||||
final ViewOnlyWalletData data;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return switch (data) {
|
||||
final CryptonoteViewOnlyWalletData e => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
DetailItem(
|
||||
title: "Address",
|
||||
detail: e.address,
|
||||
button: Util.isDesktop
|
||||
? IconCopyButton(
|
||||
data: e.address,
|
||||
)
|
||||
: SimpleCopyButton(
|
||||
data: e.address,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
DetailItem(
|
||||
title: "Private view key",
|
||||
detail: e.privateViewKey,
|
||||
button: Util.isDesktop
|
||||
? IconCopyButton(
|
||||
data: e.privateViewKey,
|
||||
)
|
||||
: SimpleCopyButton(
|
||||
data: e.privateViewKey,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
final AddressViewOnlyWalletData e => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
DetailItem(
|
||||
title: "Address",
|
||||
detail: e.address,
|
||||
button: Util.isDesktop
|
||||
? IconCopyButton(
|
||||
data: e.address,
|
||||
)
|
||||
: SimpleCopyButton(
|
||||
data: e.address,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
final ExtendedKeysViewOnlyWalletData e => Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
...e.xPubs.map(
|
||||
(xPub) => DetailItem(
|
||||
title: xPub.path,
|
||||
detail: xPub.encoded,
|
||||
button: Util.isDesktop
|
||||
? IconCopyButton(
|
||||
data: xPub.encoded,
|
||||
)
|
||||
: SimpleCopyButton(
|
||||
data: xPub.encoded,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||
import '../../../../app_config.dart';
|
||||
import '../../../../models/keys/cw_key_data.dart';
|
||||
import '../../../../models/keys/key_data_interface.dart';
|
||||
import '../../../../models/keys/view_only_wallet_data.dart';
|
||||
import '../../../../models/keys/xpriv_data.dart';
|
||||
import '../../../../notifications/show_flush_bar.dart';
|
||||
import '../../../../themes/stack_colors.dart';
|
||||
|
@ -39,6 +40,7 @@ import '../../../../widgets/rounded_white_container.dart';
|
|||
import '../../../../widgets/stack_dialog.dart';
|
||||
import '../../../add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart';
|
||||
import '../../../wallet_view/transaction_views/transaction_details_view.dart';
|
||||
import '../../sub_widgets/view_only_wallet_data_widget.dart';
|
||||
import 'cn_wallet_keys.dart';
|
||||
import 'wallet_xprivs.dart';
|
||||
|
||||
|
@ -87,9 +89,10 @@ class WalletBackupView extends ConsumerWidget {
|
|||
Padding(
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: CustomTextButton(
|
||||
text: switch (keyData.runtimeType) {
|
||||
const (XPrivData) => "xpriv(s)",
|
||||
const (CWKeyData) => "keys",
|
||||
text: switch (keyData) {
|
||||
final XPrivData _ => "xpriv(s)",
|
||||
final CWKeyData _ => "keys",
|
||||
final ViewOnlyWalletData _ => "keys",
|
||||
_ => throw UnimplementedError(
|
||||
"Don't forget to add your KeyDataInterface here! ${keyData.runtimeType}",
|
||||
),
|
||||
|
@ -426,7 +429,7 @@ class _FrostKeys extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class MobileKeyDataView extends StatelessWidget {
|
||||
class MobileKeyDataView extends ConsumerWidget {
|
||||
const MobileKeyDataView({
|
||||
super.key,
|
||||
required this.walletId,
|
||||
|
@ -441,7 +444,7 @@ class MobileKeyDataView extends StatelessWidget {
|
|||
final KeyDataInterface keyData;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
|
||||
|
@ -452,9 +455,10 @@ class MobileKeyDataView extends StatelessWidget {
|
|||
},
|
||||
),
|
||||
title: Text(
|
||||
"Wallet ${switch (keyData.runtimeType) {
|
||||
const (XPrivData) => "xpriv(s)",
|
||||
const (CWKeyData) => "keys",
|
||||
"Wallet ${switch (keyData) {
|
||||
final XPrivData _ => "xpriv(s)",
|
||||
final CWKeyData _ => "keys",
|
||||
final ViewOnlyWalletData _ => "keys",
|
||||
_ => throw UnimplementedError(
|
||||
"Don't forget to add your KeyDataInterface here!",
|
||||
),
|
||||
|
@ -474,14 +478,18 @@ class MobileKeyDataView extends StatelessWidget {
|
|||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: switch (keyData.runtimeType) {
|
||||
const (XPrivData) => WalletXPrivs(
|
||||
child: switch (keyData) {
|
||||
final XPrivData e => WalletXPrivs(
|
||||
walletId: walletId,
|
||||
xprivData: keyData as XPrivData,
|
||||
xprivData: e,
|
||||
),
|
||||
const (CWKeyData) => CNWalletKeys(
|
||||
final CWKeyData e => CNWalletKeys(
|
||||
walletId: walletId,
|
||||
cwKeyData: keyData as CWKeyData,
|
||||
cwKeyData: e,
|
||||
),
|
||||
final ViewOnlyWalletData e =>
|
||||
ViewOnlyWalletDataWidget(
|
||||
data: e,
|
||||
),
|
||||
_ => throw UnimplementedError(
|
||||
"Don't forget to add your KeyDataInterface here!",
|
||||
|
|
|
@ -49,7 +49,7 @@ class WalletXPrivsState extends ConsumerState<WalletXPrivs> {
|
|||
late String _currentDropDownValue;
|
||||
|
||||
String _current(String key) =>
|
||||
widget.xprivData.xprivs.firstWhere((e) => e.path == key).xpriv;
|
||||
widget.xprivData.xprivs.firstWhere((e) => e.path == key).encoded;
|
||||
|
||||
Future<void> _copy() async {
|
||||
await widget.clipboardInterface.setData(
|
||||
|
|
|
@ -19,6 +19,7 @@ import '../../../db/hive/db.dart';
|
|||
import '../../../db/sqlite/firo_cache.dart';
|
||||
import '../../../models/epicbox_config_model.dart';
|
||||
import '../../../models/keys/key_data_interface.dart';
|
||||
import '../../../models/keys/view_only_wallet_data.dart';
|
||||
import '../../../notifications/show_flush_bar.dart';
|
||||
import '../../../providers/global/wallets_provider.dart';
|
||||
import '../../../providers/ui/transaction_filter_provider.dart';
|
||||
|
@ -36,9 +37,10 @@ import '../../../wallets/crypto_currency/intermediate/frost_currency.dart';
|
|||
import '../../../wallets/crypto_currency/intermediate/nano_currency.dart';
|
||||
import '../../../wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
||||
import '../../../wallets/wallet/impl/epiccash_wallet.dart';
|
||||
import '../../../wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart';
|
||||
import '../../../wallets/wallet/intermediate/lib_monero_wallet.dart';
|
||||
import '../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
|
||||
import '../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart';
|
||||
import '../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
|
||||
import '../../../widgets/background.dart';
|
||||
import '../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../../widgets/desktop/secondary_button.dart';
|
||||
|
@ -98,8 +100,13 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
|
|||
void initState() {
|
||||
walletId = widget.walletId;
|
||||
coin = widget.coin;
|
||||
xPubEnabled =
|
||||
ref.read(pWallets).getWallet(walletId) is ExtendedKeysInterface;
|
||||
|
||||
final wallet = ref.read(pWallets).getWallet(walletId);
|
||||
if (wallet is ViewOnlyOptionInterface && wallet.isViewOnly) {
|
||||
xPubEnabled = false;
|
||||
} else {
|
||||
xPubEnabled = wallet is ExtendedKeysInterface;
|
||||
}
|
||||
|
||||
xpub = "";
|
||||
|
||||
|
@ -165,6 +172,15 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint("BUILD: $runtimeType");
|
||||
final wallet = ref.read(pWallets).getWallet(widget.walletId);
|
||||
|
||||
bool canBackup = true;
|
||||
if (wallet is ViewOnlyOptionInterface &&
|
||||
wallet.isViewOnly &&
|
||||
wallet.viewOnlyType == ViewOnlyWalletType.addressOnly) {
|
||||
canBackup = false;
|
||||
}
|
||||
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
|
||||
|
@ -247,106 +263,155 @@ class _WalletSettingsViewState extends ConsumerState<WalletSettingsView> {
|
|||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
Consumer(
|
||||
builder: (_, ref, __) {
|
||||
return SettingsListButton(
|
||||
iconAssetName: Assets.svg.lock,
|
||||
iconSize: 16,
|
||||
title: "Wallet backup",
|
||||
onPressed: () async {
|
||||
final wallet = ref
|
||||
.read(pWallets)
|
||||
.getWallet(widget.walletId);
|
||||
if (canBackup)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (canBackup)
|
||||
Consumer(
|
||||
builder: (_, ref, __) {
|
||||
return SettingsListButton(
|
||||
iconAssetName: Assets.svg.lock,
|
||||
iconSize: 16,
|
||||
title: "Wallet backup",
|
||||
onPressed: () async {
|
||||
// TODO: [prio=med] take wallets that don't have a mnemonic into account
|
||||
|
||||
// TODO: [prio=med] take wallets that don't have a mnemonic into account
|
||||
|
||||
List<String>? mnemonic;
|
||||
({
|
||||
String myName,
|
||||
String config,
|
||||
String keys,
|
||||
List<String>? mnemonic;
|
||||
({
|
||||
String myName,
|
||||
String config,
|
||||
String keys
|
||||
})? prevGen,
|
||||
})? frostWalletData;
|
||||
if (wallet is BitcoinFrostWallet) {
|
||||
final futures = [
|
||||
wallet.getSerializedKeys(),
|
||||
wallet.getMultisigConfig(),
|
||||
wallet.getSerializedKeysPrevGen(),
|
||||
wallet.getMultisigConfigPrevGen(),
|
||||
];
|
||||
String keys,
|
||||
({
|
||||
String config,
|
||||
String keys
|
||||
})? prevGen,
|
||||
})? frostWalletData;
|
||||
if (wallet is BitcoinFrostWallet) {
|
||||
final futures = [
|
||||
wallet.getSerializedKeys(),
|
||||
wallet.getMultisigConfig(),
|
||||
wallet.getSerializedKeysPrevGen(),
|
||||
wallet.getMultisigConfigPrevGen(),
|
||||
];
|
||||
|
||||
final results =
|
||||
await Future.wait(futures);
|
||||
final results =
|
||||
await Future.wait(futures);
|
||||
|
||||
if (results.length == 4) {
|
||||
frostWalletData = (
|
||||
myName: wallet.frostInfo.myName,
|
||||
config: results[1]!,
|
||||
keys: results[0]!,
|
||||
prevGen: results[2] == null ||
|
||||
results[3] == null
|
||||
? null
|
||||
: (
|
||||
config: results[3]!,
|
||||
keys: results[2]!,
|
||||
),
|
||||
);
|
||||
if (results.length == 4) {
|
||||
frostWalletData = (
|
||||
myName: wallet.frostInfo.myName,
|
||||
config: results[1]!,
|
||||
keys: results[0]!,
|
||||
prevGen: results[2] == null ||
|
||||
results[3] == null
|
||||
? null
|
||||
: (
|
||||
config: results[3]!,
|
||||
keys: results[2]!,
|
||||
),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (wallet is MnemonicInterface) {
|
||||
if (wallet
|
||||
is ViewOnlyOptionInterface &&
|
||||
!(wallet
|
||||
as ViewOnlyOptionInterface)
|
||||
.isViewOnly) {
|
||||
mnemonic = await wallet
|
||||
.getMnemonicAsWords();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (wallet
|
||||
is MnemonicInterface) {
|
||||
mnemonic =
|
||||
await wallet.getMnemonicAsWords();
|
||||
}
|
||||
|
||||
KeyDataInterface? keyData;
|
||||
if (wallet is ExtendedKeysInterface) {
|
||||
keyData = await wallet.getXPrivs();
|
||||
} else if (wallet is CwBasedInterface) {
|
||||
keyData = await wallet.getKeys();
|
||||
}
|
||||
KeyDataInterface? keyData;
|
||||
if (wallet
|
||||
is ViewOnlyOptionInterface &&
|
||||
wallet.isViewOnly) {
|
||||
keyData = await wallet
|
||||
.getViewOnlyWalletData();
|
||||
} else if (wallet
|
||||
is ExtendedKeysInterface) {
|
||||
keyData = await wallet.getXPrivs();
|
||||
} else if (wallet
|
||||
is LibMoneroWallet) {
|
||||
keyData = await wallet.getKeys();
|
||||
}
|
||||
|
||||
if (context.mounted) {
|
||||
await Navigator.push(
|
||||
context,
|
||||
RouteGenerator.getRoute(
|
||||
shouldUseMaterialRoute:
|
||||
RouteGenerator
|
||||
.useMaterialPageRoute,
|
||||
builder: (_) => LockscreenView(
|
||||
routeOnSuccessArguments: (
|
||||
walletId: walletId,
|
||||
mnemonic: mnemonic ?? [],
|
||||
frostWalletData:
|
||||
frostWalletData,
|
||||
keyData: keyData,
|
||||
if (context.mounted) {
|
||||
if (keyData != null &&
|
||||
wallet
|
||||
is ViewOnlyOptionInterface &&
|
||||
wallet.isViewOnly) {
|
||||
await Navigator.push(
|
||||
context,
|
||||
RouteGenerator.getRoute(
|
||||
shouldUseMaterialRoute:
|
||||
RouteGenerator
|
||||
.useMaterialPageRoute,
|
||||
builder: (_) =>
|
||||
LockscreenView(
|
||||
routeOnSuccessArguments: (
|
||||
walletId: walletId,
|
||||
keyData: keyData,
|
||||
),
|
||||
showBackButton: true,
|
||||
routeOnSuccess:
|
||||
MobileKeyDataView
|
||||
.routeName,
|
||||
biometricsCancelButtonString:
|
||||
"CANCEL",
|
||||
biometricsLocalizedReason:
|
||||
"Authenticate to view recovery data",
|
||||
biometricsAuthenticationTitle:
|
||||
"View recovery data",
|
||||
),
|
||||
settings: const RouteSettings(
|
||||
name:
|
||||
"/viewRecoveryDataLockscreen",
|
||||
),
|
||||
),
|
||||
showBackButton: true,
|
||||
routeOnSuccess:
|
||||
WalletBackupView.routeName,
|
||||
biometricsCancelButtonString:
|
||||
"CANCEL",
|
||||
biometricsLocalizedReason:
|
||||
"Authenticate to view recovery phrase",
|
||||
biometricsAuthenticationTitle:
|
||||
"View recovery phrase",
|
||||
),
|
||||
settings: const RouteSettings(
|
||||
name:
|
||||
"/viewRecoverPhraseLockscreen",
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await Navigator.push(
|
||||
context,
|
||||
RouteGenerator.getRoute(
|
||||
shouldUseMaterialRoute:
|
||||
RouteGenerator
|
||||
.useMaterialPageRoute,
|
||||
builder: (_) =>
|
||||
LockscreenView(
|
||||
routeOnSuccessArguments: (
|
||||
walletId: walletId,
|
||||
mnemonic: mnemonic ?? [],
|
||||
frostWalletData:
|
||||
frostWalletData,
|
||||
keyData: keyData,
|
||||
),
|
||||
showBackButton: true,
|
||||
routeOnSuccess:
|
||||
WalletBackupView
|
||||
.routeName,
|
||||
biometricsCancelButtonString:
|
||||
"CANCEL",
|
||||
biometricsLocalizedReason:
|
||||
"Authenticate to view recovery phrase",
|
||||
biometricsAuthenticationTitle:
|
||||
"View recovery phrase",
|
||||
),
|
||||
settings: const RouteSettings(
|
||||
name:
|
||||
"/viewRecoverPhraseLockscreen",
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../../app_config.dart';
|
||||
import '../../../../models/keys/view_only_wallet_data.dart';
|
||||
import '../../../../pages_desktop_specific/my_stack_view/wallet_view/sub_widgets/delete_wallet_keys_popup.dart';
|
||||
import '../../../../providers/global/secure_store_provider.dart';
|
||||
import '../../../../providers/global/wallets_provider.dart';
|
||||
import '../../../../route_generator.dart';
|
||||
import '../../../../themes/stack_colors.dart';
|
||||
import '../../../../utilities/text_styles.dart';
|
||||
import '../../../../utilities/util.dart';
|
||||
import '../../../../wallets/isar/providers/wallet_info_provider.dart';
|
||||
import '../../../../widgets/conditional_parent.dart';
|
||||
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../../../widgets/desktop/primary_button.dart';
|
||||
import '../../../../widgets/rounded_white_container.dart';
|
||||
import '../../../../widgets/stack_dialog.dart';
|
||||
import '../../../home_view/home_view.dart';
|
||||
import '../../sub_widgets/view_only_wallet_data_widget.dart';
|
||||
|
||||
class DeleteViewOnlyWalletKeysView extends ConsumerStatefulWidget {
|
||||
const DeleteViewOnlyWalletKeysView({
|
||||
super.key,
|
||||
required this.walletId,
|
||||
required this.data,
|
||||
});
|
||||
|
||||
static const routeName = "/deleteWalletViewOnlyData";
|
||||
|
||||
final String walletId;
|
||||
final ViewOnlyWalletData data;
|
||||
|
||||
@override
|
||||
ConsumerState<DeleteViewOnlyWalletKeysView> createState() =>
|
||||
_DeleteViewOnlyWalletKeysViewState();
|
||||
}
|
||||
|
||||
class _DeleteViewOnlyWalletKeysViewState
|
||||
extends ConsumerState<DeleteViewOnlyWalletKeysView> {
|
||||
bool _lock = false;
|
||||
void _continuePressed() async {
|
||||
if (_lock) {
|
||||
return;
|
||||
}
|
||||
_lock = true;
|
||||
try {
|
||||
if (Util.isDesktop) {
|
||||
await Navigator.of(context).push(
|
||||
RouteGenerator.getRoute(
|
||||
builder: (context) {
|
||||
return ConfirmDelete(
|
||||
walletId: widget.walletId,
|
||||
);
|
||||
},
|
||||
settings: const RouteSettings(
|
||||
name: "/desktopConfirmDelete",
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await showDialog<dynamic>(
|
||||
barrierDismissible: true,
|
||||
context: context,
|
||||
builder: (_) => StackDialog(
|
||||
title: "Thanks! Your wallet will be deleted.",
|
||||
leftButton: TextButton(
|
||||
style: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getSecondaryEnabledButtonStyle(context),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(
|
||||
"Cancel",
|
||||
style: STextStyles.button(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.accentColorDark,
|
||||
),
|
||||
),
|
||||
),
|
||||
rightButton: TextButton(
|
||||
style: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.getPrimaryEnabledButtonStyle(context),
|
||||
onPressed: () async {
|
||||
await ref.read(pWallets).deleteWallet(
|
||||
ref.read(pWalletInfo(widget.walletId)),
|
||||
ref.read(secureStoreProvider),
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
Navigator.of(context).popUntil(
|
||||
ModalRoute.withName(HomeView.routeName),
|
||||
);
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
"Ok",
|
||||
style: STextStyles.button(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
_lock = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ConditionalParent(
|
||||
condition: !Util.isDesktop,
|
||||
builder: (child) => Scaffold(
|
||||
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
|
||||
appBar: AppBar(
|
||||
leading: AppBarBackButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
body: SafeArea(
|
||||
child: LayoutBuilder(
|
||||
builder: (context, cons) {
|
||||
return SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(minHeight: cons.maxHeight),
|
||||
child: IntrinsicHeight(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
RoundedWhiteContainer(
|
||||
child: Text(
|
||||
"Please write down your backup data. Keep it safe and "
|
||||
"never share it with anyone. "
|
||||
"Your backup data is the only way you can access your "
|
||||
"wallet if you forget your PIN, lose your phone, etc."
|
||||
"\n\n"
|
||||
"${AppConfig.appName} does not keep nor is able to restore "
|
||||
"your backup data. "
|
||||
"Only you have access to your wallet.",
|
||||
style: STextStyles.label(context),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
ViewOnlyWalletDataWidget(
|
||||
data: widget.data,
|
||||
),
|
||||
if (!Util.isDesktop) const Spacer(),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
PrimaryButton(
|
||||
label: "Continue",
|
||||
onPressed: _continuePressed,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -14,11 +14,9 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
import '../../../../app_config.dart';
|
||||
import '../../../../notifications/show_flush_bar.dart';
|
||||
import '../../../add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart';
|
||||
import '../../../home_view/home_view.dart';
|
||||
import '../../../wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart';
|
||||
import '../../../../providers/global/secure_store_provider.dart';
|
||||
import '../../../../providers/global/wallets_provider.dart';
|
||||
import '../../../../themes/stack_colors.dart';
|
||||
|
@ -35,6 +33,9 @@ import '../../../../widgets/desktop/primary_button.dart';
|
|||
import '../../../../widgets/detail_item.dart';
|
||||
import '../../../../widgets/rounded_white_container.dart';
|
||||
import '../../../../widgets/stack_dialog.dart';
|
||||
import '../../../add_wallet_views/new_wallet_recovery_phrase_view/sub_widgets/mnemonic_table.dart';
|
||||
import '../../../home_view/home_view.dart';
|
||||
import '../../../wallet_view/transaction_views/tx_v2/transaction_v2_details_view.dart';
|
||||
|
||||
class DeleteWalletRecoveryPhraseView extends ConsumerStatefulWidget {
|
||||
const DeleteWalletRecoveryPhraseView({
|
||||
|
@ -69,7 +70,6 @@ class _DeleteWalletRecoveryPhraseViewState
|
|||
late ClipboardInterface _clipboardInterface;
|
||||
|
||||
bool _lock = false;
|
||||
|
||||
void _continuePressed() {
|
||||
if (_lock) {
|
||||
return;
|
||||
|
|
|
@ -12,14 +12,17 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../../app_config.dart';
|
||||
import '../../../../models/keys/view_only_wallet_data.dart';
|
||||
import '../../../../providers/providers.dart';
|
||||
import '../../../../themes/stack_colors.dart';
|
||||
import '../../../../utilities/text_styles.dart';
|
||||
import '../../../../wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
|
||||
import '../../../../widgets/background.dart';
|
||||
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../../../widgets/rounded_container.dart';
|
||||
import 'delete_view_only_wallet_keys_view.dart';
|
||||
import 'delete_wallet_recovery_phrase_view.dart';
|
||||
|
||||
class DeleteWalletWarningView extends ConsumerWidget {
|
||||
|
@ -118,6 +121,7 @@ class DeleteWalletWarningView extends ConsumerWidget {
|
|||
String keys,
|
||||
({String config, String keys})? prevGen,
|
||||
})? frostWalletData;
|
||||
ViewOnlyWalletData? viewOnlyData;
|
||||
|
||||
if (wallet is BitcoinFrostWallet) {
|
||||
final futures = [
|
||||
|
@ -142,18 +146,33 @@ class DeleteWalletWarningView extends ConsumerWidget {
|
|||
),
|
||||
);
|
||||
}
|
||||
} else if (wallet is MnemonicInterface) {
|
||||
mnemonic = await wallet.getMnemonicAsWords();
|
||||
} else {
|
||||
if (wallet is ViewOnlyOptionInterface &&
|
||||
wallet.isViewOnly) {
|
||||
viewOnlyData = await wallet.getViewOnlyWalletData();
|
||||
} else if (wallet is MnemonicInterface) {
|
||||
mnemonic = await wallet.getMnemonicAsWords();
|
||||
}
|
||||
}
|
||||
if (context.mounted) {
|
||||
await Navigator.of(context).pushNamed(
|
||||
DeleteWalletRecoveryPhraseView.routeName,
|
||||
arguments: (
|
||||
walletId: walletId,
|
||||
mnemonicWords: mnemonic ?? [],
|
||||
frostWalletData: frostWalletData,
|
||||
),
|
||||
);
|
||||
if (viewOnlyData != null) {
|
||||
await Navigator.of(context).pushNamed(
|
||||
DeleteViewOnlyWalletKeysView.routeName,
|
||||
arguments: (
|
||||
walletId: walletId,
|
||||
data: viewOnlyData,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
await Navigator.of(context).pushNamed(
|
||||
DeleteWalletRecoveryPhraseView.routeName,
|
||||
arguments: (
|
||||
walletId: walletId,
|
||||
mnemonicWords: mnemonic ?? [],
|
||||
frostWalletData: frostWalletData,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
child: Text(
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../../models/keys/view_only_wallet_data.dart';
|
||||
import '../../../../providers/db/main_db_provider.dart';
|
||||
import '../../../../providers/providers.dart';
|
||||
import '../../../../route_generator.dart';
|
||||
|
@ -23,6 +24,7 @@ import '../../../../wallets/wallet/wallet_mixin_interfaces/lelantus_interface.da
|
|||
import '../../../../wallets/wallet/wallet_mixin_interfaces/multi_address_interface.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/rbf_interface.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
|
||||
import '../../../../widgets/background.dart';
|
||||
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../../../widgets/custom_buttons/draggable_switch_button.dart';
|
||||
|
@ -133,6 +135,12 @@ class _WalletSettingsWalletSettingsViewState
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final wallet = ref.watch(pWallets).getWallet(widget.walletId);
|
||||
|
||||
final isViewOnlyNoAddressGen = wallet is ViewOnlyOptionInterface &&
|
||||
wallet.isViewOnly &&
|
||||
wallet.viewOnlyType == ViewOnlyWalletType.addressOnly;
|
||||
|
||||
return Background(
|
||||
child: Scaffold(
|
||||
backgroundColor: Theme.of(context).extension<StackColors>()!.background,
|
||||
|
@ -189,13 +197,11 @@ class _WalletSettingsWalletSettingsViewState
|
|||
),
|
||||
),
|
||||
),
|
||||
if (ref.watch(pWallets).getWallet(widget.walletId)
|
||||
is RbfInterface)
|
||||
if (wallet is RbfInterface)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (ref.watch(pWallets).getWallet(widget.walletId)
|
||||
is RbfInterface)
|
||||
if (wallet is RbfInterface)
|
||||
RoundedWhiteContainer(
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: RawMaterialButton(
|
||||
|
@ -227,13 +233,11 @@ class _WalletSettingsWalletSettingsViewState
|
|||
),
|
||||
),
|
||||
),
|
||||
if (ref.watch(pWallets).getWallet(widget.walletId)
|
||||
is MultiAddressInterface)
|
||||
if (wallet is MultiAddressInterface && !isViewOnlyNoAddressGen)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (ref.watch(pWallets).getWallet(widget.walletId)
|
||||
is MultiAddressInterface)
|
||||
if (wallet is MultiAddressInterface && !isViewOnlyNoAddressGen)
|
||||
RoundedWhiteContainer(
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: RawMaterialButton(
|
||||
|
@ -278,13 +282,11 @@ class _WalletSettingsWalletSettingsViewState
|
|||
),
|
||||
),
|
||||
),
|
||||
if (ref.watch(pWallets).getWallet(widget.walletId)
|
||||
is LelantusInterface)
|
||||
if (wallet is LelantusInterface && !wallet.isViewOnly)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (ref.watch(pWallets).getWallet(widget.walletId)
|
||||
is LelantusInterface)
|
||||
if (wallet is LelantusInterface && !wallet.isViewOnly)
|
||||
RoundedWhiteContainer(
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: RawMaterialButton(
|
||||
|
@ -316,13 +318,11 @@ class _WalletSettingsWalletSettingsViewState
|
|||
),
|
||||
),
|
||||
),
|
||||
if (ref.watch(pWallets).getWallet(widget.walletId)
|
||||
is SparkInterface)
|
||||
if (wallet is SparkInterface && !wallet.isViewOnly)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (ref.watch(pWallets).getWallet(widget.walletId)
|
||||
is SparkInterface)
|
||||
if (wallet is SparkInterface && !wallet.isViewOnly)
|
||||
RoundedWhiteContainer(
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: RawMaterialButton(
|
||||
|
@ -369,7 +369,7 @@ class _WalletSettingsWalletSettingsViewState
|
|||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
padding: const EdgeInsets.all(0),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
showDialog<void>(
|
||||
barrierDismissible: true,
|
||||
context: context,
|
||||
builder: (_) => StackDialog(
|
||||
|
|
|
@ -57,7 +57,7 @@ class XPubViewState extends ConsumerState<XPubView> {
|
|||
late String _currentDropDownValue;
|
||||
|
||||
String _current(String key) =>
|
||||
widget.xpubData.xpubs.firstWhere((e) => e.path == key).xpub;
|
||||
widget.xpubData.xpubs.firstWhere((e) => e.path == key).encoded;
|
||||
|
||||
Future<void> _copy() async {
|
||||
await widget.clipboardInterface.setData(
|
||||
|
|
|
@ -12,7 +12,7 @@ import '../../utilities/assets.dart';
|
|||
import '../../utilities/text_styles.dart';
|
||||
import '../../utilities/util.dart';
|
||||
import '../../wallets/isar/providers/wallet_info_provider.dart';
|
||||
import '../../wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart';
|
||||
import '../../wallets/wallet/intermediate/lib_monero_wallet.dart';
|
||||
import '../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
|
||||
import '../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart';
|
||||
import '../../widgets/background.dart';
|
||||
|
@ -270,7 +270,7 @@ class _FiroRescanRecoveryErrorViewState
|
|||
KeyDataInterface? keyData;
|
||||
if (wallet is ExtendedKeysInterface) {
|
||||
keyData = await wallet.getXPrivs();
|
||||
} else if (wallet is CwBasedInterface) {
|
||||
} else if (wallet is LibMoneroWallet) {
|
||||
keyData = await wallet.getKeys();
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import 'dart:io';
|
|||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
import '../../../models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||
import '../../../models/isar/models/isar_models.dart';
|
||||
import '../../../models/isar/stack_theme.dart';
|
||||
|
@ -106,6 +107,11 @@ class TxIcon extends ConsumerWidget {
|
|||
!tx.isConfirmed(
|
||||
currentHeight,
|
||||
ref.watch(pWallets).getWallet(tx.walletId).cryptoCurrency.minConfirms,
|
||||
ref
|
||||
.watch(pWallets)
|
||||
.getWallet(tx.walletId)
|
||||
.cryptoCurrency
|
||||
.minCoinbaseConfirms,
|
||||
),
|
||||
tx.subType,
|
||||
tx.type,
|
||||
|
|
|
@ -186,6 +186,21 @@ class WalletSummaryInfo extends ConsumerWidget {
|
|||
),
|
||||
),
|
||||
const Spacer(),
|
||||
if (ref.watch(pWalletInfo(walletId)).isViewOnly)
|
||||
FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: SelectableText(
|
||||
"(View only)",
|
||||
style: STextStyles.pageTitleH1(context).copyWith(
|
||||
fontSize: 18,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFavoriteCard
|
||||
.withOpacity(0.7),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: SelectableText(
|
||||
|
|
|
@ -15,14 +15,11 @@ import 'package:flutter/material.dart';
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
import '../../../../models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||
import '../../../../models/isar/models/contact_entry.dart';
|
||||
import '../../../../models/isar/models/isar_models.dart';
|
||||
import '../../../../models/transaction_filter.dart';
|
||||
import '../../sub_widgets/tx_icon.dart';
|
||||
import '../transaction_search_filter_view.dart';
|
||||
import 'transaction_v2_card.dart';
|
||||
import 'transaction_v2_details_view.dart';
|
||||
import '../../../../providers/db/main_db_provider.dart';
|
||||
import '../../../../providers/global/address_book_service_provider.dart';
|
||||
import '../../../../providers/providers.dart';
|
||||
|
@ -49,6 +46,10 @@ import '../../../../widgets/loading_indicator.dart';
|
|||
import '../../../../widgets/rounded_white_container.dart';
|
||||
import '../../../../widgets/stack_text_field.dart';
|
||||
import '../../../../widgets/textfield_icon_button.dart';
|
||||
import '../../sub_widgets/tx_icon.dart';
|
||||
import '../transaction_search_filter_view.dart';
|
||||
import 'transaction_v2_card.dart';
|
||||
import 'transaction_v2_details_view.dart';
|
||||
|
||||
typedef _GroupedTransactions = ({
|
||||
String label,
|
||||
|
@ -866,6 +867,11 @@ class _DesktopTransactionCardRowState
|
|||
String whatIsIt(TransactionV2 tx, int height) => tx.statusLabel(
|
||||
currentChainHeight: height,
|
||||
minConfirms: minConfirms,
|
||||
minCoinbaseConfirms: ref
|
||||
.read(pWallets)
|
||||
.getWallet(widget.walletId)
|
||||
.cryptoCurrency
|
||||
.minCoinbaseConfirms,
|
||||
);
|
||||
|
||||
@override
|
||||
|
|
|
@ -60,6 +60,11 @@ class _TransactionCardStateV2 extends ConsumerState<TransactionCardV2> {
|
|||
.getWallet(walletId)
|
||||
.cryptoCurrency
|
||||
.minConfirms,
|
||||
minCoinbaseConfirms: ref
|
||||
.read(pWallets)
|
||||
.getWallet(walletId)
|
||||
.cryptoCurrency
|
||||
.minCoinbaseConfirms,
|
||||
);
|
||||
|
||||
@override
|
||||
|
|
|
@ -48,6 +48,7 @@ import '../../../../widgets/background.dart';
|
|||
import '../../../../widgets/conditional_parent.dart';
|
||||
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../../../widgets/custom_buttons/blue_text_button.dart';
|
||||
import '../../../../widgets/custom_buttons/simple_copy_button.dart';
|
||||
import '../../../../widgets/desktop/desktop_dialog.dart';
|
||||
import '../../../../widgets/desktop/desktop_dialog_close_button.dart';
|
||||
import '../../../../widgets/desktop/primary_button.dart';
|
||||
|
@ -373,6 +374,11 @@ class _TransactionV2DetailsViewState
|
|||
String whatIsIt(TransactionV2 tx, int height) => tx.statusLabel(
|
||||
currentChainHeight: height,
|
||||
minConfirms: minConfirms,
|
||||
minCoinbaseConfirms: ref
|
||||
.read(pWallets)
|
||||
.getWallet(walletId)
|
||||
.cryptoCurrency
|
||||
.minCoinbaseConfirms,
|
||||
);
|
||||
|
||||
Future<String> fetchContactNameFor(String address) async {
|
||||
|
@ -567,6 +573,7 @@ class _TransactionV2DetailsViewState
|
|||
final confirmedTxn = _transaction.isConfirmed(
|
||||
currentHeight,
|
||||
coin.minConfirms,
|
||||
coin.minCoinbaseConfirms,
|
||||
);
|
||||
|
||||
return ConditionalParent(
|
||||
|
@ -1367,6 +1374,7 @@ class _TransactionV2DetailsViewState
|
|||
? _transaction.isConfirmed(
|
||||
currentHeight,
|
||||
minConfirms,
|
||||
coin.minCoinbaseConfirms,
|
||||
)
|
||||
? ref
|
||||
.watch(pAmountFormatter(coin))
|
||||
|
@ -1484,9 +1492,9 @@ class _TransactionV2DetailsViewState
|
|||
height = "Unknown";
|
||||
} else {
|
||||
final confirmed = _transaction.isConfirmed(
|
||||
currentHeight,
|
||||
minConfirms,
|
||||
);
|
||||
currentHeight,
|
||||
minConfirms,
|
||||
coin.minCoinbaseConfirms);
|
||||
if (widget.coin is! Epiccash && confirmed) {
|
||||
height =
|
||||
"${_transaction.height == 0 ? "Unknown" : _transaction.height}";
|
||||
|
@ -1715,16 +1723,27 @@ class _TransactionV2DetailsViewState
|
|||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Transaction ID",
|
||||
style: isDesktop
|
||||
? STextStyles
|
||||
.desktopTextExtraExtraSmall(
|
||||
context,
|
||||
)
|
||||
: STextStyles.itemSubtitle(
|
||||
context,
|
||||
),
|
||||
ConditionalParent(
|
||||
condition: !isDesktop,
|
||||
builder: (child) => Row(
|
||||
children: [
|
||||
Expanded(child: child),
|
||||
SimpleCopyButton(
|
||||
data: _transaction.txid,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Text(
|
||||
"Transaction ID",
|
||||
style: isDesktop
|
||||
? STextStyles
|
||||
.desktopTextExtraExtraSmall(
|
||||
context,
|
||||
)
|
||||
: STextStyles.itemSubtitle(
|
||||
context,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
|
|
|
@ -50,11 +50,13 @@ import '../../wallets/crypto_currency/intermediate/frost_currency.dart';
|
|||
import '../../wallets/isar/providers/wallet_info_provider.dart';
|
||||
import '../../wallets/wallet/impl/bitcoin_frost_wallet.dart';
|
||||
import '../../wallets/wallet/impl/firo_wallet.dart';
|
||||
import '../../wallets/wallet/intermediate/lib_monero_wallet.dart';
|
||||
import '../../wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface.dart';
|
||||
import '../../wallets/wallet/wallet_mixin_interfaces/coin_control_interface.dart';
|
||||
import '../../wallets/wallet/wallet_mixin_interfaces/ordinals_interface.dart';
|
||||
import '../../wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart';
|
||||
import '../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
|
||||
import '../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
|
||||
import '../../widgets/background.dart';
|
||||
import '../../widgets/conditional_parent.dart';
|
||||
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
|
@ -66,6 +68,7 @@ import '../../widgets/loading_indicator.dart';
|
|||
import '../../widgets/small_tor_icon.dart';
|
||||
import '../../widgets/stack_dialog.dart';
|
||||
import '../../widgets/wallet_navigation_bar/components/icons/buy_nav_icon.dart';
|
||||
import '../../widgets/wallet_navigation_bar/components/icons/churn_nav_icon.dart';
|
||||
import '../../widgets/wallet_navigation_bar/components/icons/coin_control_nav_icon.dart';
|
||||
import '../../widgets/wallet_navigation_bar/components/icons/exchange_nav_icon.dart';
|
||||
import '../../widgets/wallet_navigation_bar/components/icons/frost_sign_nav_icon.dart';
|
||||
|
@ -78,6 +81,7 @@ import '../../widgets/wallet_navigation_bar/components/wallet_navigation_bar_ite
|
|||
import '../../widgets/wallet_navigation_bar/wallet_navigation_bar.dart';
|
||||
import '../buy_view/buy_in_wallet_view.dart';
|
||||
import '../cashfusion/cashfusion_view.dart';
|
||||
import '../churning/churning_view.dart';
|
||||
import '../coin_control/coin_control_view.dart';
|
||||
import '../exchange_view/wallet_initiated_exchange_view.dart';
|
||||
import '../monkey/monkey_view.dart';
|
||||
|
@ -521,6 +525,10 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
final prefs = ref.watch(prefsChangeNotifierProvider);
|
||||
final showExchange = prefs.enableExchange;
|
||||
|
||||
final wallet = ref.watch(pWallets).getWallet(walletId);
|
||||
|
||||
final viewOnly = wallet is ViewOnlyOptionInterface && wallet.isViewOnly;
|
||||
|
||||
return ConditionalParent(
|
||||
condition: _rescanningOnOpen,
|
||||
builder: (child) {
|
||||
|
@ -1023,38 +1031,39 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
icon: const FrostSignNavIcon(),
|
||||
onTap: () => _onFrostSignPressed(context),
|
||||
),
|
||||
WalletNavigationBarItemData(
|
||||
label: "Send",
|
||||
icon: const SendNavIcon(),
|
||||
onTap: () {
|
||||
// not sure what this is supposed to accomplish?
|
||||
// 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(
|
||||
ref.read(pWallets).getWallet(walletId)
|
||||
is BitcoinFrostWallet
|
||||
? FrostSendView.routeName
|
||||
: SendView.routeName,
|
||||
arguments: (
|
||||
walletId: walletId,
|
||||
coin: coin,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (Constants.enableExchange &&
|
||||
if (!viewOnly)
|
||||
WalletNavigationBarItemData(
|
||||
label: "Send",
|
||||
icon: const SendNavIcon(),
|
||||
onTap: () {
|
||||
// not sure what this is supposed to accomplish?
|
||||
// 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(
|
||||
wallet is BitcoinFrostWallet
|
||||
? FrostSendView.routeName
|
||||
: SendView.routeName,
|
||||
arguments: (
|
||||
walletId: walletId,
|
||||
coin: coin,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if (!viewOnly &&
|
||||
Constants.enableExchange &&
|
||||
ref.watch(pWalletCoin(walletId)) is! FrostCurrency &&
|
||||
AppConfig.hasFeature(AppFeature.swap) &&
|
||||
showExchange)
|
||||
|
@ -1110,12 +1119,7 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
);
|
||||
},
|
||||
),
|
||||
if (ref.watch(
|
||||
pWallets.select(
|
||||
(value) => value.getWallet(widget.walletId)
|
||||
is CoinControlInterface,
|
||||
),
|
||||
) &&
|
||||
if (wallet is CoinControlInterface &&
|
||||
ref.watch(
|
||||
prefsChangeNotifierProvider.select(
|
||||
(value) => value.enableCoinControl,
|
||||
|
@ -1134,12 +1138,7 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
);
|
||||
},
|
||||
),
|
||||
if (ref.watch(
|
||||
pWallets.select(
|
||||
(value) =>
|
||||
value.getWallet(widget.walletId) is PaynymInterface,
|
||||
),
|
||||
))
|
||||
if (!viewOnly && wallet is PaynymInterface)
|
||||
WalletNavigationBarItemData(
|
||||
label: "PayNym",
|
||||
icon: const PaynymNavIcon(),
|
||||
|
@ -1176,8 +1175,10 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
|
||||
// check if account exists and for matching code to see if claimed
|
||||
if (account.value != null &&
|
||||
account.value!.nonSegwitPaymentCode.claimed &&
|
||||
account.value!.segwit) {
|
||||
account.value!.nonSegwitPaymentCode.claimed
|
||||
// &&
|
||||
// account.value!.segwit
|
||||
) {
|
||||
ref.read(myPaynymAccountStateProvider.state).state =
|
||||
account.value!;
|
||||
|
||||
|
@ -1210,12 +1211,7 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
);
|
||||
},
|
||||
),
|
||||
if (ref.watch(
|
||||
pWallets.select(
|
||||
(value) => value.getWallet(widget.walletId)
|
||||
is CashFusionInterface,
|
||||
),
|
||||
))
|
||||
if (wallet is CashFusionInterface && !viewOnly)
|
||||
WalletNavigationBarItemData(
|
||||
label: "Fusion",
|
||||
icon: const FusionNavIcon(),
|
||||
|
@ -1226,6 +1222,17 @@ class _WalletViewState extends ConsumerState<WalletView> {
|
|||
);
|
||||
},
|
||||
),
|
||||
if (wallet is LibMoneroWallet && !viewOnly)
|
||||
WalletNavigationBarItemData(
|
||||
label: "Churn",
|
||||
icon: const ChurnNavIcon(),
|
||||
onTap: () {
|
||||
Navigator.of(context).pushNamed(
|
||||
ChurningView.routeName,
|
||||
arguments: walletId,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
|
|
@ -13,7 +13,7 @@ import 'dart:io';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import '../../wallet_view/wallet_view.dart';
|
||||
|
||||
import '../../../pages_desktop_specific/my_stack_view/wallet_view/desktop_wallet_view.dart';
|
||||
import '../../../providers/providers.dart';
|
||||
import '../../../themes/coin_icon_provider.dart';
|
||||
|
@ -22,13 +22,15 @@ import '../../../utilities/amount/amount.dart';
|
|||
import '../../../utilities/amount/amount_formatter.dart';
|
||||
import '../../../utilities/constants.dart';
|
||||
import '../../../utilities/show_loading.dart';
|
||||
import '../../../utilities/show_node_tor_settings_mismatch.dart';
|
||||
import '../../../utilities/text_styles.dart';
|
||||
import '../../../utilities/util.dart';
|
||||
import '../../../wallets/crypto_currency/coins/firo.dart';
|
||||
import '../../../wallets/isar/providers/wallet_info_provider.dart';
|
||||
import '../../../wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart';
|
||||
import '../../../wallets/wallet/intermediate/lib_monero_wallet.dart';
|
||||
import '../../../widgets/coin_card.dart';
|
||||
import '../../../widgets/conditional_parent.dart';
|
||||
import '../../wallet_view/wallet_view.dart';
|
||||
|
||||
class FavoriteCard extends ConsumerStatefulWidget {
|
||||
const FavoriteCard({
|
||||
|
@ -116,8 +118,21 @@ class _FavoriteCardState extends ConsumerState<FavoriteCard> {
|
|||
onTap: () async {
|
||||
final wallet = ref.read(pWallets).getWallet(walletId);
|
||||
|
||||
final canContinue = await checkShowNodeTorSettingsMismatch(
|
||||
context: context,
|
||||
currency: coin,
|
||||
prefs: ref.read(prefsChangeNotifierProvider),
|
||||
nodeService: ref.read(nodeServiceChangeNotifierProvider),
|
||||
allowCancel: true,
|
||||
rootNavigator: Util.isDesktop,
|
||||
);
|
||||
|
||||
if (!canContinue) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Future<void> loadFuture;
|
||||
if (wallet is CwBasedInterface) {
|
||||
if (wallet is LibMoneroWallet) {
|
||||
loadFuture =
|
||||
wallet.init().then((value) async => await (wallet).open());
|
||||
} else {
|
||||
|
|
|
@ -21,10 +21,11 @@ import '../../../themes/stack_colors.dart';
|
|||
import '../../../utilities/amount/amount.dart';
|
||||
import '../../../utilities/constants.dart';
|
||||
import '../../../utilities/show_loading.dart';
|
||||
import '../../../utilities/show_node_tor_settings_mismatch.dart';
|
||||
import '../../../utilities/text_styles.dart';
|
||||
import '../../../utilities/util.dart';
|
||||
import '../../../wallets/crypto_currency/crypto_currency.dart';
|
||||
import '../../../wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart';
|
||||
import '../../../wallets/wallet/intermediate/lib_monero_wallet.dart';
|
||||
import '../../../widgets/dialogs/tor_warning_dialog.dart';
|
||||
import '../../../widgets/rounded_white_container.dart';
|
||||
import '../../wallet_view/wallet_view.dart';
|
||||
|
@ -83,8 +84,22 @@ class WalletListItem extends ConsumerWidget {
|
|||
.read(pWallets)
|
||||
.wallets
|
||||
.firstWhere((e) => e.info.coin == coin);
|
||||
|
||||
final canContinue = await checkShowNodeTorSettingsMismatch(
|
||||
context: context,
|
||||
currency: coin,
|
||||
prefs: ref.read(prefsChangeNotifierProvider),
|
||||
nodeService: ref.read(nodeServiceChangeNotifierProvider),
|
||||
allowCancel: true,
|
||||
rootNavigator: Util.isDesktop,
|
||||
);
|
||||
|
||||
if (!canContinue) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Future<void> loadFuture;
|
||||
if (wallet is CwBasedInterface) {
|
||||
if (wallet is LibMoneroWallet) {
|
||||
loadFuture =
|
||||
wallet.init().then((value) async => await (wallet).open());
|
||||
} else {
|
||||
|
|
424
lib/pages_desktop_specific/churning/desktop_churning_view.dart
Normal file
424
lib/pages_desktop_specific/churning/desktop_churning_view.dart
Normal file
|
@ -0,0 +1,424 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:flutter/gestures.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 '../../pages/churning/churning_rounds_selection_sheet.dart';
|
||||
import '../../providers/churning/churning_service_provider.dart';
|
||||
import '../../themes/stack_colors.dart';
|
||||
import '../../utilities/assets.dart';
|
||||
import '../../utilities/constants.dart';
|
||||
import '../../utilities/extensions/extensions.dart';
|
||||
import '../../utilities/text_styles.dart';
|
||||
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../widgets/custom_buttons/checkbox_text_button.dart';
|
||||
import '../../widgets/desktop/desktop_app_bar.dart';
|
||||
import '../../widgets/desktop/desktop_dialog.dart';
|
||||
import '../../widgets/desktop/desktop_dialog_close_button.dart';
|
||||
import '../../widgets/desktop/desktop_scaffold.dart';
|
||||
import '../../widgets/desktop/primary_button.dart';
|
||||
import '../../widgets/rounded_white_container.dart';
|
||||
import '../../widgets/stack_text_field.dart';
|
||||
import 'sub_widgets/churning_dialog.dart';
|
||||
|
||||
class DesktopChurningView extends ConsumerStatefulWidget {
|
||||
const DesktopChurningView({
|
||||
super.key,
|
||||
required this.walletId,
|
||||
});
|
||||
|
||||
static const String routeName = "/desktopChurningView";
|
||||
|
||||
final String walletId;
|
||||
|
||||
@override
|
||||
ConsumerState<DesktopChurningView> createState() => _DesktopChurning();
|
||||
}
|
||||
|
||||
class _DesktopChurning extends ConsumerState<DesktopChurningView> {
|
||||
late final TextEditingController churningRoundController;
|
||||
late final FocusNode churningRoundFocusNode;
|
||||
|
||||
bool _enableStartButton = false;
|
||||
|
||||
ChurnOption _option = ChurnOption.continuous;
|
||||
|
||||
Future<void> _startChurn() async {
|
||||
final churningService = ref.read(pChurningService(widget.walletId));
|
||||
|
||||
final int rounds = _option == ChurnOption.continuous
|
||||
? 0
|
||||
: int.parse(churningRoundController.text);
|
||||
|
||||
churningService.rounds = rounds;
|
||||
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) {
|
||||
return ChurnDialogView(
|
||||
walletId: widget.walletId,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
churningRoundController = TextEditingController();
|
||||
|
||||
churningRoundFocusNode = FocusNode();
|
||||
|
||||
final rounds = ref.read(pChurningService(widget.walletId)).rounds;
|
||||
|
||||
_option = rounds == 0 ? ChurnOption.continuous : ChurnOption.custom;
|
||||
churningRoundController.text = rounds.toString();
|
||||
|
||||
_enableStartButton = churningRoundController.text.isNotEmpty;
|
||||
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
churningRoundController.dispose();
|
||||
churningRoundFocusNode.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
debugPrint("BUILD: $runtimeType");
|
||||
|
||||
return DesktopScaffold(
|
||||
appBar: DesktopAppBar(
|
||||
background: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
isCompactHeight: true,
|
||||
useSpacers: false,
|
||||
leading: Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(24.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
// const SizedBox(
|
||||
// width: 32,
|
||||
// ),
|
||||
AppBarIconButton(
|
||||
size: 32,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldDefaultBG,
|
||||
shadows: const [],
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.arrowLeft,
|
||||
width: 18,
|
||||
height: 18,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.topNavIconPrimary,
|
||||
),
|
||||
onPressed: Navigator.of(context).pop,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 15,
|
||||
),
|
||||
SvgPicture.asset(
|
||||
Assets.svg.churn,
|
||||
width: 32,
|
||||
height: 32,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Text(
|
||||
"Churning",
|
||||
style: STextStyles.desktopH3(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: () {},
|
||||
child: Row(
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
Assets.svg.circleQuestion,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.radioButtonIconBorder,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
text: "What is churning?",
|
||||
style: STextStyles.richLink(context).copyWith(
|
||||
fontSize: 16,
|
||||
),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
showDialog<dynamic>(
|
||||
context: context,
|
||||
useSafeArea: false,
|
||||
barrierDismissible: true,
|
||||
builder: (context) {
|
||||
return DesktopDialog(
|
||||
maxWidth: 580,
|
||||
maxHeight: double.infinity,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 10,
|
||||
left: 20,
|
||||
bottom: 20,
|
||||
right: 10,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment
|
||||
.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"What is churning?",
|
||||
style: STextStyles.desktopH2(
|
||||
context,
|
||||
),
|
||||
),
|
||||
DesktopDialogCloseButton(
|
||||
onPressedOverride: () =>
|
||||
Navigator.of(context)
|
||||
.pop(true),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Text(
|
||||
"Churning in a Monero wallet involves"
|
||||
" sending Monero to oneself in multiple"
|
||||
" transactions, which can enhance privacy"
|
||||
" by making it harder for observers to "
|
||||
"link your transactions. This process"
|
||||
" re-mixes the funds within the network,"
|
||||
" helping obscure transaction history. "
|
||||
"Churning is optional and mainly beneficial"
|
||||
" in scenarios where maximum privacy is"
|
||||
" desired or if you received the Monero from"
|
||||
" a source from which you'd like to disassociate.",
|
||||
style:
|
||||
STextStyles.desktopTextMedium(
|
||||
context,
|
||||
).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark3,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
body: Row(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 460,
|
||||
child: RoundedWhiteContainer(
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
"Churning helps anonymize your coins by mixing them.",
|
||||
style:
|
||||
STextStyles.desktopTextExtraExtraSmall(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
SizedBox(
|
||||
width: 460,
|
||||
child: RoundedWhiteContainer(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Configuration",
|
||||
style:
|
||||
STextStyles.desktopTextExtraExtraSmall(context),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
DropdownButtonHideUnderline(
|
||||
child: DropdownButton2<ChurnOption>(
|
||||
value: _option,
|
||||
items: [
|
||||
...ChurnOption.values.map(
|
||||
(e) => DropdownMenuItem(
|
||||
value: e,
|
||||
child: Text(
|
||||
e.name.capitalize(),
|
||||
style: STextStyles.smallMed14(context)
|
||||
.copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textDark,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
onChanged: (value) {
|
||||
if (value is ChurnOption) {
|
||||
setState(() {
|
||||
_option = value;
|
||||
});
|
||||
}
|
||||
},
|
||||
isExpanded: true,
|
||||
iconStyleData: IconStyleData(
|
||||
icon: SvgPicture.asset(
|
||||
Assets.svg.chevronDown,
|
||||
width: 12,
|
||||
height: 6,
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldActiveSearchIconRight,
|
||||
),
|
||||
),
|
||||
dropdownStyleData: DropdownStyleData(
|
||||
offset: const Offset(0, -10),
|
||||
elevation: 0,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldActiveBG,
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
),
|
||||
),
|
||||
menuItemStyleData: const MenuItemStyleData(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_option == ChurnOption.custom)
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
if (_option == ChurnOption.custom)
|
||||
SizedBox(
|
||||
width: 460,
|
||||
child: RoundedWhiteContainer(
|
||||
padding: EdgeInsets.zero,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(
|
||||
Constants.size.circularBorderRadius,
|
||||
),
|
||||
child: TextField(
|
||||
autocorrect: false,
|
||||
enableSuggestions: false,
|
||||
controller: churningRoundController,
|
||||
focusNode: churningRoundFocusNode,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
],
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_enableStartButton = value.isNotEmpty;
|
||||
});
|
||||
},
|
||||
style: STextStyles.field(context),
|
||||
decoration: standardInputDecoration(
|
||||
"Number of churns",
|
||||
churningRoundFocusNode,
|
||||
context,
|
||||
desktopMed: true,
|
||||
).copyWith(
|
||||
labelText: "Enter number of churns..",
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CheckboxTextButton(
|
||||
label: "Pause on errors",
|
||||
initialValue: !ref
|
||||
.read(pChurningService(widget.walletId))
|
||||
.ignoreErrors,
|
||||
onChanged: (value) {
|
||||
ref
|
||||
.read(pChurningService(widget.walletId))
|
||||
.ignoreErrors = !value;
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
PrimaryButton(
|
||||
label: "Start",
|
||||
enabled: _enableStartButton,
|
||||
buttonHeight: ButtonHeight.l,
|
||||
onPressed: _startChurn,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,328 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
|
||||
import '../../../pages/churning/churn_error_dialog.dart';
|
||||
import '../../../providers/churning/churning_service_provider.dart';
|
||||
import '../../../themes/stack_colors.dart';
|
||||
import '../../../utilities/assets.dart';
|
||||
import '../../../utilities/text_styles.dart';
|
||||
import '../../../widgets/churning/churn_progress_item.dart';
|
||||
import '../../../widgets/desktop/desktop_dialog.dart';
|
||||
import '../../../widgets/desktop/desktop_dialog_close_button.dart';
|
||||
import '../../../widgets/desktop/primary_button.dart';
|
||||
import '../../../widgets/desktop/secondary_button.dart';
|
||||
import '../../../widgets/monero_chan_dance.dart';
|
||||
import '../../../widgets/rounded_container.dart';
|
||||
import '../../../widgets/rounded_white_container.dart';
|
||||
|
||||
class ChurnDialogView extends ConsumerStatefulWidget {
|
||||
const ChurnDialogView({
|
||||
super.key,
|
||||
required this.walletId,
|
||||
});
|
||||
|
||||
final String walletId;
|
||||
|
||||
@override
|
||||
ConsumerState<ChurnDialogView> createState() => _ChurnDialogViewState();
|
||||
}
|
||||
|
||||
class _ChurnDialogViewState extends ConsumerState<ChurnDialogView> {
|
||||
Future<bool> _requestAndProcessCancel() async {
|
||||
final bool? shouldCancel = await showDialog<bool?>(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (_) => DesktopDialog(
|
||||
maxWidth: 580,
|
||||
maxHeight: double.infinity,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 32,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
"Cancel churning?",
|
||||
style: STextStyles.desktopH3(context),
|
||||
),
|
||||
DesktopDialogCloseButton(
|
||||
onPressedOverride: () => Navigator.of(context).pop(false),
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 0,
|
||||
right: 32,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Do you really want to cancel the churning process?",
|
||||
style: STextStyles.smallMed14(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: SecondaryButton(
|
||||
label: "No",
|
||||
buttonHeight: ButtonHeight.l,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
label: "Yes",
|
||||
buttonHeight: ButtonHeight.l,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (shouldCancel == true && mounted) {
|
||||
ref.read(pChurningService(widget.walletId)).stopChurning();
|
||||
|
||||
await WakelockPlus.disable();
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted) ref.read(pChurningService(widget.walletId)).churn();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
dispose() {
|
||||
WakelockPlus.disable();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bool _succeeded = ref.watch(
|
||||
pChurningService(widget.walletId).select((s) => s.done),
|
||||
);
|
||||
|
||||
final int _roundsCompleted = ref.watch(
|
||||
pChurningService(widget.walletId).select((s) => s.roundsCompleted),
|
||||
);
|
||||
|
||||
if (!Platform.isLinux) {
|
||||
WakelockPlus.enable();
|
||||
}
|
||||
|
||||
ref.listen(
|
||||
pChurningService(widget.walletId).select((s) => s.lastSeenError),
|
||||
(p, n) {
|
||||
if (!ref.read(pChurningService(widget.walletId)).ignoreErrors &&
|
||||
n != null) {
|
||||
if (context.mounted) {
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) => ChurnErrorDialog(
|
||||
error: n.toString(),
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return DesktopDialog(
|
||||
maxHeight: 600,
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 32),
|
||||
child: Text(
|
||||
"Churn progress",
|
||||
style: STextStyles.desktopH2(context),
|
||||
),
|
||||
),
|
||||
DesktopDialogCloseButton(
|
||||
onPressedOverride: () async {
|
||||
if (_succeeded) {
|
||||
Navigator.of(context).pop();
|
||||
} else {
|
||||
if (await _requestAndProcessCancel()) {
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 20,
|
||||
left: 32,
|
||||
right: 32,
|
||||
bottom: 32,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_roundsCompleted > 0
|
||||
? RoundedWhiteContainer(
|
||||
child: Text(
|
||||
"Churn rounds completed: $_roundsCompleted",
|
||||
style: STextStyles.w500_14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textSubtitle1,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
)
|
||||
: RoundedContainer(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.snackBarBackError,
|
||||
child: Text(
|
||||
"Do not close this window. If you exit, "
|
||||
"the process will be canceled.",
|
||||
style: STextStyles.smallMed14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.snackBarTextError,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
const MoneroChanDance(),
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
ProgressItem(
|
||||
iconAsset: Assets.svg.alertCircle,
|
||||
label: "Waiting for balance to unlock ${ref.watch(
|
||||
pChurningService(widget.walletId)
|
||||
.select((s) => s.confirmsInfo),
|
||||
) ?? ""}",
|
||||
status: ref.watch(
|
||||
pChurningService(widget.walletId)
|
||||
.select((s) => s.waitingForUnlockedBalance),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
ProgressItem(
|
||||
iconAsset: Assets.svg.churn,
|
||||
label: "Creating churn transaction",
|
||||
status: ref.watch(
|
||||
pChurningService(widget.walletId)
|
||||
.select((s) => s.makingChurnTransaction),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
ProgressItem(
|
||||
iconAsset: Assets.svg.checkCircle,
|
||||
label: "Complete",
|
||||
status: ref.watch(
|
||||
pChurningService(widget.walletId)
|
||||
.select((s) => s.completedStatus),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
if (_succeeded)
|
||||
Expanded(
|
||||
child: PrimaryButton(
|
||||
buttonHeight: ButtonHeight.m,
|
||||
label: "Churn again",
|
||||
onPressed: ref
|
||||
.read(pChurningService(widget.walletId))
|
||||
.churn,
|
||||
),
|
||||
),
|
||||
if (_succeeded)
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
if (!_succeeded) const Spacer(),
|
||||
if (!_succeeded)
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
Expanded(
|
||||
child: SecondaryButton(
|
||||
buttonHeight: ButtonHeight.m,
|
||||
enabled: true,
|
||||
label: _succeeded ? "Done" : "Cancel",
|
||||
onPressed: () async {
|
||||
if (_succeeded) {
|
||||
Navigator.of(context).pop();
|
||||
} else {
|
||||
if (await _requestAndProcessCancel()) {
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:isar/isar.dart';
|
||||
|
||||
import '../../db/isar/main_db.dart';
|
||||
import '../../models/isar/models/isar_models.dart';
|
||||
import '../../pages/coin_control/utxo_details_view.dart';
|
||||
|
@ -141,6 +142,11 @@ class _UtxoRowState extends ConsumerState<UtxoRow> {
|
|||
.getWallet(widget.walletId)
|
||||
.cryptoCurrency
|
||||
.minConfirms,
|
||||
ref
|
||||
.watch(pWallets)
|
||||
.getWallet(widget.walletId)
|
||||
.cryptoCurrency
|
||||
.minCoinbaseConfirms,
|
||||
)
|
||||
? UTXOStatusIconStatus.confirmed
|
||||
: UTXOStatusIconStatus.unconfirmed,
|
||||
|
|
|
@ -10,17 +10,21 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'wallet_view/desktop_wallet_view.dart';
|
||||
|
||||
import '../../providers/global/active_wallet_provider.dart';
|
||||
import '../../providers/global/node_service_provider.dart';
|
||||
import '../../providers/global/prefs_provider.dart';
|
||||
import '../../providers/global/wallets_provider.dart';
|
||||
import '../../themes/stack_colors.dart';
|
||||
import '../../utilities/constants.dart';
|
||||
import '../../utilities/show_loading.dart';
|
||||
import '../../utilities/show_node_tor_settings_mismatch.dart';
|
||||
import '../../utilities/util.dart';
|
||||
import '../../wallets/crypto_currency/crypto_currency.dart';
|
||||
import '../../wallets/wallet/wallet_mixin_interfaces/cw_based_interface.dart';
|
||||
import '../../wallets/wallet/intermediate/lib_monero_wallet.dart';
|
||||
import '../../widgets/rounded_container.dart';
|
||||
import '../../widgets/wallet_info_row/wallet_info_row.dart';
|
||||
import 'wallet_view/desktop_wallet_view.dart';
|
||||
|
||||
class CoinWalletsTable extends ConsumerWidget {
|
||||
const CoinWalletsTable({
|
||||
|
@ -80,8 +84,24 @@ class CoinWalletsTable extends ConsumerWidget {
|
|||
|
||||
final wallet =
|
||||
ref.read(pWallets).getWallet(walletIds[i]);
|
||||
|
||||
final canContinue =
|
||||
await checkShowNodeTorSettingsMismatch(
|
||||
context: context,
|
||||
currency: coin,
|
||||
prefs: ref.read(prefsChangeNotifierProvider),
|
||||
nodeService:
|
||||
ref.read(nodeServiceChangeNotifierProvider),
|
||||
allowCancel: true,
|
||||
rootNavigator: Util.isDesktop,
|
||||
);
|
||||
|
||||
if (!canContinue) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Future<void> loadFuture;
|
||||
if (wallet is CwBasedInterface) {
|
||||
if (wallet is LibMoneroWallet) {
|
||||
loadFuture = wallet
|
||||
.init()
|
||||
.then((value) async => await (wallet).open());
|
||||
|
|
|
@ -19,15 +19,20 @@ import '../../providers/providers.dart';
|
|||
import '../../themes/coin_icon_provider.dart';
|
||||
import '../../themes/stack_colors.dart';
|
||||
import '../../utilities/amount/amount.dart';
|
||||
import '../../utilities/show_loading.dart';
|
||||
import '../../utilities/show_node_tor_settings_mismatch.dart';
|
||||
import '../../utilities/text_styles.dart';
|
||||
import '../../utilities/util.dart';
|
||||
import '../../wallets/crypto_currency/crypto_currency.dart';
|
||||
import '../../wallets/isar/providers/all_wallets_info_provider.dart';
|
||||
import '../../wallets/wallet/intermediate/lib_monero_wallet.dart';
|
||||
import '../../widgets/breathing.dart';
|
||||
import '../../widgets/conditional_parent.dart';
|
||||
import '../../widgets/desktop/desktop_dialog.dart';
|
||||
import '../../widgets/desktop/desktop_dialog_close_button.dart';
|
||||
import '../../widgets/dialogs/tor_warning_dialog.dart';
|
||||
import '../../widgets/rounded_white_container.dart';
|
||||
import 'wallet_view/desktop_wallet_view.dart';
|
||||
|
||||
class WalletSummaryTable extends ConsumerStatefulWidget {
|
||||
const WalletSummaryTable({super.key});
|
||||
|
@ -86,10 +91,7 @@ class DesktopWalletSummaryRow extends ConsumerStatefulWidget {
|
|||
|
||||
class _DesktopWalletSummaryRowState
|
||||
extends ConsumerState<DesktopWalletSummaryRow> {
|
||||
bool _hovering = false;
|
||||
|
||||
void _onPressed() async {
|
||||
// Check if Tor is enabled...
|
||||
Future<void> _checkTor() async {
|
||||
if (ref.read(prefsChangeNotifierProvider).useTor) {
|
||||
// ... and if the coin supports Tor.
|
||||
if (!widget.coin.torSupport) {
|
||||
|
@ -106,44 +108,110 @@ class _DesktopWalletSummaryRowState
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => DesktopDialog(
|
||||
maxHeight: 600,
|
||||
maxWidth: 700,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
bool get goStraightIntoWallet =>
|
||||
Util.isDesktop && widget.walletCount == 1 && !widget.coin.hasTokenSupport;
|
||||
|
||||
bool _buttonLock = false;
|
||||
Future<void> _onPressedSingleWalletDesktop() async {
|
||||
if (_buttonLock) return;
|
||||
_buttonLock = true;
|
||||
try {
|
||||
await _checkTor();
|
||||
|
||||
if (mounted) {
|
||||
final wallet = ref.read(pWallets).wallets.firstWhere(
|
||||
(e) => e.cryptoCurrency.identifier == widget.coin.identifier);
|
||||
|
||||
final canContinue = await checkShowNodeTorSettingsMismatch(
|
||||
context: context,
|
||||
currency: wallet.cryptoCurrency,
|
||||
prefs: ref.read(prefsChangeNotifierProvider),
|
||||
nodeService: ref.read(nodeServiceChangeNotifierProvider),
|
||||
allowCancel: true,
|
||||
rootNavigator: Util.isDesktop,
|
||||
);
|
||||
|
||||
if (!canContinue) {
|
||||
return;
|
||||
}
|
||||
|
||||
final Future<void> loadFuture;
|
||||
if (wallet is LibMoneroWallet) {
|
||||
loadFuture =
|
||||
wallet.init().then((value) async => await (wallet).open());
|
||||
} else {
|
||||
loadFuture = wallet.init();
|
||||
}
|
||||
await showLoading(
|
||||
whileFuture: loadFuture,
|
||||
context: context,
|
||||
message: 'Opening ${wallet.info.name}',
|
||||
rootNavigator: Util.isDesktop,
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
await Navigator.of(context).pushNamed(
|
||||
DesktopWalletView.routeName,
|
||||
arguments: wallet.walletId,
|
||||
);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
_buttonLock = false;
|
||||
}
|
||||
}
|
||||
|
||||
void _onPressed() async {
|
||||
if (_buttonLock) return;
|
||||
_buttonLock = true;
|
||||
try {
|
||||
// Check if Tor is enabled...
|
||||
await _checkTor();
|
||||
|
||||
if (mounted) {
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) => DesktopDialog(
|
||||
maxHeight: 600,
|
||||
maxWidth: 700,
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 32),
|
||||
child: Text(
|
||||
"${widget.coin.prettyName} (${widget.coin.ticker}) wallets",
|
||||
style: STextStyles.desktopH3(context),
|
||||
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),
|
||||
),
|
||||
),
|
||||
const DesktopDialogCloseButton(),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
right: 32,
|
||||
bottom: 32,
|
||||
),
|
||||
child: WalletsOverview(
|
||||
coin: widget.coin,
|
||||
navigatorState: Navigator.of(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
const DesktopDialogCloseButton(),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
right: 32,
|
||||
bottom: 32,
|
||||
),
|
||||
child: WalletsOverview(
|
||||
coin: widget.coin,
|
||||
navigatorState: Navigator.of(context),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
_buttonLock = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -152,7 +220,8 @@ class _DesktopWalletSummaryRowState
|
|||
child: RoundedWhiteContainer(
|
||||
padding: const EdgeInsets.all(20),
|
||||
hoverColor: Colors.transparent,
|
||||
onPressed: _onPressed,
|
||||
onPressed:
|
||||
goStraightIntoWallet ? _onPressedSingleWalletDesktop : _onPressed,
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
|
|
|
@ -21,6 +21,7 @@ import 'package:isar/isar.dart';
|
|||
import '../../../db/sqlite/firo_cache.dart';
|
||||
import '../../../models/isar/models/blockchain_data/v2/transaction_v2.dart';
|
||||
import '../../../models/isar/models/isar_models.dart';
|
||||
import '../../../models/keys/view_only_wallet_data.dart';
|
||||
import '../../../pages/add_wallet_views/add_token_view/edit_wallet_tokens_view.dart';
|
||||
import '../../../pages/token_view/my_tokens_view.dart';
|
||||
import '../../../pages/wallet_view/sub_widgets/transactions_list.dart';
|
||||
|
@ -44,6 +45,7 @@ import '../../../utilities/wallet_tools.dart';
|
|||
import '../../../wallets/isar/providers/wallet_info_provider.dart';
|
||||
import '../../../wallets/wallet/impl/banano_wallet.dart';
|
||||
import '../../../wallets/wallet/impl/firo_wallet.dart';
|
||||
import '../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
|
||||
import '../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../../widgets/custom_buttons/blue_text_button.dart';
|
||||
import '../../../widgets/desktop/desktop_app_bar.dart';
|
||||
|
@ -168,6 +170,11 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
|
|||
|
||||
final monke = wallet is BananoWallet ? wallet.getMonkeyImageBytes() : null;
|
||||
|
||||
// if the view only wallet watches a single address there are no keys of any kind
|
||||
final showKeysButton = !(wallet is ViewOnlyOptionInterface &&
|
||||
wallet.isViewOnly &&
|
||||
wallet.viewOnlyType == ViewOnlyWalletType.addressOnly);
|
||||
|
||||
return DesktopScaffold(
|
||||
appBar: DesktopAppBar(
|
||||
background: Theme.of(context).extension<StackColors>()!.popupBG,
|
||||
|
@ -216,6 +223,19 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (ref.watch(pWalletInfo(widget.walletId)).isViewOnly)
|
||||
const SizedBox(
|
||||
width: 20,
|
||||
),
|
||||
if (ref.watch(pWalletInfo(widget.walletId)).isViewOnly)
|
||||
Text(
|
||||
"(View only)",
|
||||
style: STextStyles.desktopTextExtraSmall(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
.textFieldActiveSearchIconLeft,
|
||||
),
|
||||
),
|
||||
if (kDebugMode) const Spacer(),
|
||||
if (kDebugMode)
|
||||
Column(
|
||||
|
@ -312,12 +332,14 @@ class _DesktopWalletViewState extends ConsumerState<DesktopWalletView> {
|
|||
walletId: widget.walletId,
|
||||
eventBus: eventBus,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 2,
|
||||
),
|
||||
WalletKeysButton(
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
if (showKeysButton)
|
||||
const SizedBox(
|
||||
width: 2,
|
||||
),
|
||||
if (showKeysButton)
|
||||
WalletKeysButton(
|
||||
walletId: widget.walletId,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 2,
|
||||
),
|
||||
|
|
|
@ -10,18 +10,21 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import '../../../../app_config.dart';
|
||||
import 'delete_wallet_keys_popup.dart';
|
||||
import '../../../../pages/settings_views/wallet_settings_view/wallet_settings_wallet_settings/delete_view_only_wallet_keys_view.dart';
|
||||
import '../../../../providers/global/wallets_provider.dart';
|
||||
import '../../../../themes/stack_colors.dart';
|
||||
import '../../../../utilities/text_styles.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/mnemonic_interface.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
|
||||
import '../../../../widgets/desktop/desktop_dialog.dart';
|
||||
import '../../../../widgets/desktop/desktop_dialog_close_button.dart';
|
||||
import '../../../../widgets/desktop/primary_button.dart';
|
||||
import '../../../../widgets/desktop/secondary_button.dart';
|
||||
import '../../../../widgets/rounded_container.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'delete_wallet_keys_popup.dart';
|
||||
|
||||
class DesktopAttentionDeleteWallet extends ConsumerStatefulWidget {
|
||||
const DesktopAttentionDeleteWallet({
|
||||
|
@ -114,6 +117,58 @@ class _DesktopAttentionDeleteWallet
|
|||
onPressed: () async {
|
||||
final wallet =
|
||||
ref.read(pWallets).getWallet(widget.walletId);
|
||||
|
||||
if (wallet is ViewOnlyOptionInterface &&
|
||||
wallet.isViewOnly) {
|
||||
final data = await wallet.getViewOnlyWalletData();
|
||||
if (context.mounted) {
|
||||
await Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (builder) => DesktopDialog(
|
||||
maxWidth: 614,
|
||||
maxHeight: double.infinity,
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 32,
|
||||
),
|
||||
child: Text(
|
||||
"Wallet keys",
|
||||
style: STextStyles.desktopH3(
|
||||
context,
|
||||
),
|
||||
),
|
||||
),
|
||||
DesktopDialogCloseButton(
|
||||
onPressedOverride: () {
|
||||
Navigator.of(
|
||||
context,
|
||||
rootNavigator: true,
|
||||
).pop();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: DeleteViewOnlyWalletKeysView(
|
||||
walletId: widget.walletId,
|
||||
data: data,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
} else
|
||||
|
||||
// TODO: [prio=med] handle other types wallet deletion
|
||||
// All wallets currently are mnemonic based
|
||||
if (wallet is MnemonicInterface) {
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import 'package:cw_core/monero_transaction_priority.dart';
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cs_monero/cs_monero.dart' as lib_monero;
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
|
@ -86,7 +86,7 @@ class _DesktopFeeDropDownState extends ConsumerState<DesktopFeeDropDown> {
|
|||
if (coin is Monero || coin is Wownero) {
|
||||
final fee = await wallet.estimateFeeFor(
|
||||
amount,
|
||||
MoneroTransactionPriority.fast.raw!,
|
||||
lib_monero.TransactionPriority.high.value,
|
||||
);
|
||||
ref.read(feeSheetSessionCacheProvider).fast[amount] = fee;
|
||||
} else if (coin is Firo) {
|
||||
|
@ -136,7 +136,7 @@ class _DesktopFeeDropDownState extends ConsumerState<DesktopFeeDropDown> {
|
|||
if (coin is Monero || coin is Wownero) {
|
||||
final fee = await wallet.estimateFeeFor(
|
||||
amount,
|
||||
MoneroTransactionPriority.regular.raw!,
|
||||
lib_monero.TransactionPriority.medium.value,
|
||||
);
|
||||
ref.read(feeSheetSessionCacheProvider).average[amount] = fee;
|
||||
} else if (coin is Firo) {
|
||||
|
@ -186,7 +186,7 @@ class _DesktopFeeDropDownState extends ConsumerState<DesktopFeeDropDown> {
|
|||
if (coin is Monero || coin is Wownero) {
|
||||
final fee = await wallet.estimateFeeFor(
|
||||
amount,
|
||||
MoneroTransactionPriority.slow.raw!,
|
||||
lib_monero.TransactionPriority.normal.value,
|
||||
);
|
||||
ref.read(feeSheetSessionCacheProvider).slow[amount] = fee;
|
||||
} else if (coin is Firo) {
|
||||
|
|
|
@ -19,6 +19,7 @@ import 'package:isar/isar.dart';
|
|||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import '../../../../models/isar/models/isar_models.dart';
|
||||
import '../../../../models/keys/view_only_wallet_data.dart';
|
||||
import '../../../../notifications/show_flush_bar.dart';
|
||||
import '../../../../pages/receive_view/generate_receiving_uri_qr_code_view.dart';
|
||||
import '../../../../providers/db/main_db_provider.dart';
|
||||
|
@ -38,8 +39,10 @@ import '../../../../wallets/isar/providers/wallet_info_provider.dart';
|
|||
import '../../../../wallets/wallet/impl/bitcoin_wallet.dart';
|
||||
import '../../../../wallets/wallet/intermediate/bip39_hd_wallet.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/bcash_interface.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/extended_keys_interface.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/multi_address_interface.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/spark_interface.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
|
||||
import '../../../../widgets/conditional_parent.dart';
|
||||
import '../../../../widgets/custom_buttons/app_bar_icon_button.dart';
|
||||
import '../../../../widgets/custom_loading_overlay.dart';
|
||||
|
@ -104,15 +107,38 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
|
|||
|
||||
final Address? address;
|
||||
if (wallet is Bip39HDWallet && wallet is! BCashInterface) {
|
||||
final type = DerivePathType.values.firstWhere(
|
||||
(e) => e.getAddressType() == _walletAddressTypes[_currentIndex],
|
||||
);
|
||||
DerivePathType? type;
|
||||
if (wallet.isViewOnly && wallet is ExtendedKeysInterface) {
|
||||
final voData = await wallet.getViewOnlyWalletData()
|
||||
as ExtendedKeysViewOnlyWalletData;
|
||||
for (final t in wallet.cryptoCurrency.supportedDerivationPathTypes) {
|
||||
final testPath = wallet.cryptoCurrency.constructDerivePath(
|
||||
derivePathType: t,
|
||||
chain: 0,
|
||||
index: 0,
|
||||
);
|
||||
if (testPath.startsWith(voData.xPubs.first.path)) {
|
||||
type = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
type = DerivePathType.values.firstWhere(
|
||||
(e) => e.getAddressType() == _walletAddressTypes[_currentIndex],
|
||||
);
|
||||
}
|
||||
address = await wallet.generateNextReceivingAddress(
|
||||
derivePathType: type,
|
||||
derivePathType: type!,
|
||||
);
|
||||
await ref.read(mainDBProvider).isar.writeTxn(() async {
|
||||
await ref.read(mainDBProvider).isar.addresses.put(address!);
|
||||
final isar = ref.read(mainDBProvider).isar;
|
||||
await isar.writeTxn(() async {
|
||||
await isar.addresses.put(address!);
|
||||
});
|
||||
final info = ref.read(pWalletInfo(walletId));
|
||||
await info.updateReceivingAddress(
|
||||
newAddress: address.value,
|
||||
isar: isar,
|
||||
);
|
||||
} else {
|
||||
await wallet.generateNewReceivingAddress();
|
||||
address = null;
|
||||
|
@ -172,8 +198,6 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
|
|||
}
|
||||
}
|
||||
|
||||
StreamSubscription<Address?>? _streamSub;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
walletId = widget.walletId;
|
||||
|
@ -181,10 +205,15 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
|
|||
clipboard = widget.clipboard;
|
||||
final wallet = ref.read(pWallets).getWallet(walletId);
|
||||
supportsSpark = ref.read(pWallets).getWallet(walletId) is SparkInterface;
|
||||
showMultiType = supportsSpark ||
|
||||
(wallet is! BCashInterface &&
|
||||
wallet is Bip39HDWallet &&
|
||||
wallet.supportedAddressTypes.length > 1);
|
||||
|
||||
if (wallet is ViewOnlyOptionInterface && wallet.isViewOnly) {
|
||||
showMultiType = false;
|
||||
} else {
|
||||
showMultiType = supportsSpark ||
|
||||
(wallet is! BCashInterface &&
|
||||
wallet is Bip39HDWallet &&
|
||||
wallet.supportedAddressTypes.length > 1);
|
||||
}
|
||||
|
||||
_walletAddressTypes.add(wallet.info.mainAddressType);
|
||||
|
||||
|
@ -238,7 +267,9 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
_streamSub?.cancel();
|
||||
for (final subscription in _addressSubMap.values) {
|
||||
subscription.cancel();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
|
@ -253,6 +284,18 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
|
|||
address = ref.watch(pWalletReceivingAddress(walletId));
|
||||
}
|
||||
|
||||
final wallet =
|
||||
ref.watch(pWallets.select((value) => value.getWallet(walletId)));
|
||||
|
||||
final bool canGen;
|
||||
if (wallet is ViewOnlyOptionInterface &&
|
||||
wallet.isViewOnly &&
|
||||
wallet.viewOnlyType == ViewOnlyWalletType.addressOnly) {
|
||||
canGen = false;
|
||||
} else {
|
||||
canGen = (wallet is MultiAddressInterface || supportsSpark);
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
|
@ -424,16 +467,12 @@ class _DesktopReceiveState extends ConsumerState<DesktopReceive> {
|
|||
),
|
||||
),
|
||||
|
||||
if (ref.watch(pWallets.select((value) => value.getWallet(walletId)))
|
||||
is MultiAddressInterface ||
|
||||
supportsSpark)
|
||||
if (canGen)
|
||||
const SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
|
||||
if (ref.watch(pWallets.select((value) => value.getWallet(walletId)))
|
||||
is MultiAddressInterface ||
|
||||
supportsSpark)
|
||||
if (canGen)
|
||||
SecondaryButton(
|
||||
buttonHeight: ButtonHeight.l,
|
||||
onPressed: supportsSpark &&
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:cw_core/monero_transaction_priority.dart';
|
||||
import 'package:cs_monero/cs_monero.dart' as lib_monero;
|
||||
import 'package:decimal/decimal.dart';
|
||||
import 'package:dropdown_button2/dropdown_button2.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -145,7 +145,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
|
||||
Future<void> scanWebcam() async {
|
||||
try {
|
||||
await showDialog(
|
||||
await showDialog<void>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return QrCodeScannerDialog(
|
||||
|
@ -153,16 +153,20 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
try {
|
||||
_processQrCodeData(qrCodeData);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Error processing QR code data: $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
Logging.instance.log(
|
||||
"Error processing QR code data: $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log("Error opening QR code scanner dialog: $e\n$s",
|
||||
level: LogLevel.Error);
|
||||
Logging.instance.log(
|
||||
"Error opening QR code scanner dialog: $e\n$s",
|
||||
level: LogLevel.Error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -655,84 +659,15 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
// return null;
|
||||
// }
|
||||
|
||||
Future<void> scanQr() async {
|
||||
try {
|
||||
if (FocusScope.of(context).hasFocus) {
|
||||
FocusScope.of(context).unfocus();
|
||||
await Future<void>.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: coin.fractionDigits,
|
||||
);
|
||||
cryptoAmountController.text = ref
|
||||
.read(pAmountFormatter(coin))
|
||||
.format(amount, withUnitName: false);
|
||||
ref.read(pSendAmount.notifier).state = amount;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_addressToggleFlag = sendToController.text.isNotEmpty;
|
||||
});
|
||||
|
||||
// now check for non standard encoded basic address
|
||||
} else if (ref
|
||||
.read(pWallets)
|
||||
.getWallet(walletId)
|
||||
.cryptoCurrency
|
||||
.validateAddress(qrResult.rawContent)) {
|
||||
_address = qrResult.rawContent;
|
||||
sendToController.text = _address ?? "";
|
||||
|
||||
_setValidAddressProviders(_address);
|
||||
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,
|
||||
);
|
||||
} catch (e, s) {
|
||||
Logging.instance.log(
|
||||
"Failed to scan qr code in SendView: $e\n$s",
|
||||
level: LogLevel.Warning,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _processQrCodeData(String qrCodeData) {
|
||||
try {
|
||||
final paymentData = AddressUtils.parsePaymentUri(qrCodeData);
|
||||
if (paymentData.coin.uriScheme == coin.uriScheme) {
|
||||
final paymentData = AddressUtils.parsePaymentUri(
|
||||
qrCodeData,
|
||||
logging: Logging.instance,
|
||||
);
|
||||
|
||||
if (paymentData != null &&
|
||||
paymentData.coin?.uriScheme == coin.uriScheme) {
|
||||
// Auto fill address.
|
||||
_address = paymentData.address.trim();
|
||||
sendToController.text = _address!;
|
||||
|
@ -756,6 +691,14 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
_note = paymentData.label;
|
||||
}
|
||||
|
||||
_setValidAddressProviders(_address);
|
||||
setState(() {
|
||||
_addressToggleFlag = sendToController.text.isNotEmpty;
|
||||
});
|
||||
} else {
|
||||
_address = qrCodeData.split("\n").first.trim();
|
||||
sendToController.text = _address ?? "";
|
||||
|
||||
_setValidAddressProviders(_address);
|
||||
setState(() {
|
||||
_addressToggleFlag = sendToController.text.isNotEmpty;
|
||||
|
@ -807,8 +750,12 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
}
|
||||
|
||||
try {
|
||||
final paymentData = AddressUtils.parsePaymentUri(content);
|
||||
if (paymentData.coin.uriScheme == coin.uriScheme) {
|
||||
final paymentData = AddressUtils.parsePaymentUri(
|
||||
content,
|
||||
logging: Logging.instance,
|
||||
);
|
||||
if (paymentData != null &&
|
||||
paymentData.coin?.uriScheme == coin.uriScheme) {
|
||||
// auto fill address
|
||||
_address = paymentData.address;
|
||||
sendToController.text = _address!;
|
||||
|
@ -837,6 +784,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
_addressToggleFlag = sendToController.text.isNotEmpty;
|
||||
});
|
||||
} else {
|
||||
content = content.split("\n").first.trim();
|
||||
if (coin is Epiccash) {
|
||||
content = AddressUtils().formatAddress(content);
|
||||
}
|
||||
|
@ -1860,7 +1808,7 @@ class _DesktopSendState extends ConsumerState<DesktopSend> {
|
|||
if (coin is Monero || coin is Wownero) {
|
||||
final fee = await wallet.estimateFeeFor(
|
||||
amount,
|
||||
MoneroTransactionPriority.regular.raw!,
|
||||
lib_monero.TransactionPriority.medium.value,
|
||||
);
|
||||
ref
|
||||
.read(feeSheetSessionCacheProvider)
|
||||
|
|
|
@ -14,14 +14,12 @@ import 'package:decimal/decimal.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../../models/isar/models/contact_entry.dart';
|
||||
import '../../../../models/paynym/paynym_account_lite.dart';
|
||||
import '../../../../models/send_view_auto_fill_data.dart';
|
||||
import '../../../../pages/send_view/confirm_transaction_view.dart';
|
||||
import '../../../../pages/send_view/sub_widgets/building_transaction_dialog.dart';
|
||||
import '../../../desktop_home_view.dart';
|
||||
import 'address_book_address_chooser/address_book_address_chooser.dart';
|
||||
import 'desktop_fee_dropdown.dart';
|
||||
import '../../../../providers/providers.dart';
|
||||
import '../../../../providers/ui/fee_rate_type_state_provider.dart';
|
||||
import '../../../../providers/ui/preview_tx_button_state_provider.dart';
|
||||
|
@ -30,6 +28,7 @@ import '../../../../utilities/address_utils.dart';
|
|||
import '../../../../utilities/amount/amount.dart';
|
||||
import '../../../../utilities/amount/amount_formatter.dart';
|
||||
import '../../../../utilities/amount/amount_input_formatter.dart';
|
||||
import '../../../../utilities/amount/amount_unit.dart';
|
||||
import '../../../../utilities/barcode_scanner_interface.dart';
|
||||
import '../../../../utilities/clipboard_interface.dart';
|
||||
import '../../../../utilities/constants.dart';
|
||||
|
@ -51,6 +50,9 @@ import '../../../../widgets/icon_widgets/clipboard_icon.dart';
|
|||
import '../../../../widgets/icon_widgets/x_icon.dart';
|
||||
import '../../../../widgets/stack_text_field.dart';
|
||||
import '../../../../widgets/textfield_icon_button.dart';
|
||||
import '../../../desktop_home_view.dart';
|
||||
import 'address_book_address_chooser/address_book_address_chooser.dart';
|
||||
import 'desktop_fee_dropdown.dart';
|
||||
|
||||
// const _kCryptoAmountRegex = r'^([0-9]*[,.]?[0-9]{0,8}|[,.][0-9]{0,8})$';
|
||||
|
||||
|
@ -393,19 +395,13 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
|
|||
|
||||
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(pCurrentTokenWallet)!.tokenContract.decimals,
|
||||
)
|
||||
: Decimal.parse(cryptoAmount).toAmount(
|
||||
fractionDigits:
|
||||
ref.read(pCurrentTokenWallet)!.tokenContract.decimals,
|
||||
);
|
||||
final cryptoAmount = ref.read(pAmountFormatter(coin)).tryParse(
|
||||
cryptoAmountController.text,
|
||||
ethContract: ref.read(pCurrentTokenWallet)!.tokenContract,
|
||||
);
|
||||
|
||||
if (cryptoAmount != null) {
|
||||
_amountToSend = cryptoAmount;
|
||||
if (_cachedAmountToSend != null &&
|
||||
_cachedAmountToSend == _amountToSend) {
|
||||
return;
|
||||
|
@ -480,25 +476,30 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
|
|||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
final results = AddressUtils.parseUri(qrResult.rawContent);
|
||||
final paymentData = AddressUtils.parsePaymentUri(
|
||||
qrResult.rawContent,
|
||||
logging: Logging.instance,
|
||||
);
|
||||
|
||||
Logging.instance.log("qrResult parsed: $results", level: LogLevel.Info);
|
||||
Logging.instance
|
||||
.log("qrResult parsed: $paymentData", level: LogLevel.Info);
|
||||
|
||||
if (results.isNotEmpty && results["scheme"] == coin.uriScheme) {
|
||||
if (paymentData != null &&
|
||||
paymentData.coin?.uriScheme == coin.uriScheme) {
|
||||
// auto fill address
|
||||
_address = results["address"] ?? "";
|
||||
_address = paymentData.address.trim();
|
||||
sendToController.text = _address!;
|
||||
|
||||
// autofill notes field
|
||||
if (results["message"] != null) {
|
||||
_note = results["message"]!;
|
||||
} else if (results["label"] != null) {
|
||||
_note = results["label"]!;
|
||||
if (paymentData.message != null) {
|
||||
_note = paymentData.message!;
|
||||
} else if (paymentData.label != null) {
|
||||
_note = paymentData.label!;
|
||||
}
|
||||
|
||||
// autofill amount field
|
||||
if (results["amount"] != null) {
|
||||
final amount = Decimal.parse(results["amount"]!).toAmount(
|
||||
if (paymentData.amount != null) {
|
||||
final Amount amount = Decimal.parse(paymentData.amount!).toAmount(
|
||||
fractionDigits:
|
||||
ref.read(pCurrentTokenWallet)!.tokenContract.decimals,
|
||||
);
|
||||
|
@ -516,12 +517,8 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
|
|||
});
|
||||
|
||||
// now check for non standard encoded basic address
|
||||
} else if (ref
|
||||
.read(pWallets)
|
||||
.getWallet(walletId)
|
||||
.cryptoCurrency
|
||||
.validateAddress(qrResult.rawContent)) {
|
||||
_address = qrResult.rawContent;
|
||||
} else {
|
||||
_address = qrResult.rawContent.split("\n").first.trim();
|
||||
sendToController.text = _address ?? "";
|
||||
|
||||
_updatePreviewButtonState(_address, _amountToSend);
|
||||
|
@ -803,7 +800,7 @@ class _DesktopTokenSendState extends ConsumerState<DesktopTokenSend> {
|
|||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: Text(
|
||||
tokenContract.symbol,
|
||||
ref.watch(pAmountUnit(coin)).unitForContract(tokenContract),
|
||||
style: STextStyles.smallMed14(context).copyWith(
|
||||
color: Theme.of(context)
|
||||
.extension<StackColors>()!
|
||||
|
|
|
@ -39,12 +39,14 @@ import '../../../../wallets/wallet/wallet_mixin_interfaces/cash_fusion_interface
|
|||
import '../../../../wallets/wallet/wallet_mixin_interfaces/coin_control_interface.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/ordinals_interface.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/paynym_interface.dart';
|
||||
import '../../../../wallets/wallet/wallet_mixin_interfaces/view_only_option_interface.dart';
|
||||
import '../../../../widgets/custom_loading_overlay.dart';
|
||||
import '../../../../widgets/desktop/desktop_dialog.dart';
|
||||
import '../../../../widgets/desktop/primary_button.dart';
|
||||
import '../../../../widgets/desktop/secondary_button.dart';
|
||||
import '../../../../widgets/loading_indicator.dart';
|
||||
import '../../../cashfusion/desktop_cashfusion_view.dart';
|
||||
import '../../../churning/desktop_churning_view.dart';
|
||||
import '../../../coin_control/desktop_coin_control_view.dart';
|
||||
import '../../../desktop_menu.dart';
|
||||
import '../../../ordinals/desktop_ordinals_view.dart';
|
||||
|
@ -92,6 +94,7 @@ class _DesktopWalletFeaturesState extends ConsumerState<DesktopWalletFeatures> {
|
|||
onOrdinalsPressed: _onOrdinalsPressed,
|
||||
onMonkeyPressed: _onMonkeyPressed,
|
||||
onFusionPressed: _onFusionPressed,
|
||||
onChurnPressed: _onChurnPressed,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -116,7 +119,7 @@ class _DesktopWalletFeaturesState extends ConsumerState<DesktopWalletFeatures> {
|
|||
barrierDismissible: false,
|
||||
builder: (context) => DesktopDialog(
|
||||
maxWidth: 500,
|
||||
maxHeight: 210,
|
||||
maxHeight: double.infinity,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 32, vertical: 20),
|
||||
child: Column(
|
||||
|
@ -204,7 +207,7 @@ class _DesktopWalletFeaturesState extends ConsumerState<DesktopWalletFeatures> {
|
|||
// await firoWallet.anonymizeAllLelantus();
|
||||
await firoWallet.anonymizeAllSpark();
|
||||
shouldPop = true;
|
||||
if (context.mounted) {
|
||||
if (mounted) {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
Navigator.of(context).popUntil(
|
||||
ModalRoute.withName(DesktopWalletView.routeName),
|
||||
|
@ -219,7 +222,7 @@ class _DesktopWalletFeaturesState extends ConsumerState<DesktopWalletFeatures> {
|
|||
}
|
||||
} catch (e) {
|
||||
shouldPop = true;
|
||||
if (context.mounted) {
|
||||
if (mounted) {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
Navigator.of(context).popUntil(
|
||||
ModalRoute.withName(DesktopWalletView.routeName),
|
||||
|
@ -299,13 +302,14 @@ class _DesktopWalletFeaturesState extends ConsumerState<DesktopWalletFeatures> {
|
|||
level: LogLevel.Info,
|
||||
);
|
||||
|
||||
if (context.mounted) {
|
||||
if (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) {
|
||||
if (account.value != null && account.value!.nonSegwitPaymentCode.claimed
|
||||
// &&
|
||||
// account.value!.segwit
|
||||
) {
|
||||
ref.read(myPaynymAccountStateProvider.state).state = account.value!;
|
||||
|
||||
await Navigator.of(context).pushNamed(
|
||||
|
@ -348,6 +352,15 @@ class _DesktopWalletFeaturesState extends ConsumerState<DesktopWalletFeatures> {
|
|||
);
|
||||
}
|
||||
|
||||
void _onChurnPressed() {
|
||||
Navigator.of(context, rootNavigator: true).pop();
|
||||
|
||||
Navigator.of(context).pushNamed(
|
||||
DesktopChurningView.routeName,
|
||||
arguments: widget.walletId,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final wallet = ref.watch(pWallets).getWallet(widget.walletId);
|
||||
|
@ -369,9 +382,12 @@ class _DesktopWalletFeaturesState extends ConsumerState<DesktopWalletFeatures> {
|
|||
wallet is OrdinalsInterface ||
|
||||
wallet is CashFusionInterface;
|
||||
|
||||
final isViewOnly = wallet is ViewOnlyOptionInterface && wallet.isViewOnly;
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
if (Constants.enableExchange &&
|
||||
if (!isViewOnly &&
|
||||
Constants.enableExchange &&
|
||||
AppConfig.hasFeature(AppFeature.swap) &&
|
||||
showExchange)
|
||||
SecondaryButton(
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue